static evaluation domain provider must be public so it can be accessed by the dynamic eval assemblies

namespace UnityScript.Steps

import System
import Boo.Lang.Environments
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.TypeSystem
import Boo.Lang.Compiler.TypeSystem.Services
import UnityScript.Macros
import UnityScript.Scripting

class EvaluationDomainProviderImplementor(AbstractCompilerComponent):

	def ImplementIEvaluationDomainProviderOn(node as ClassDefinition):
		domainField = ReferenceExpression(Context.GetUniqueName("domain"))
		evaluationDomainProviderImpl = [|
			class _($IEvaluationDomainProvider):

				private $domainField as $EvaluationDomain

				def GetEvaluationDomain():
					return $domainField or ($domainField = $EvaluationDomain())

				def GetImports():
					return $(ImportsArrayFor(node))

				def GetAssemblyReferences():
					return $(AssemblyReferencesArray())
		|]

		my(CodeReifier).MergeInto(node, evaluationDomainProviderImpl)

	def StaticEvaluationDomainProviderFor(node as ClassDefinition) as IField:
		perNode node:
			return CreateStaticEvaluationDomainProviderReferenceOn(node)

	private def AssemblyReferencesArray() as Expression:
		perNode CompileUnit:
			return CodeBuilder.CreateReference(CreateAssemblyReferencesArray())

	private def ImportsArrayFor(node as ClassDefinition):
		result = [| (of $string:,) |]
		result.Items.Extend([| $(i.Namespace) |] for i in node.EnclosingModule.Imports)
		return result

	private def CreateStaticEvaluationDomainProviderReferenceOn(node as ClassDefinition):
		evaluationDomainProviderImpl = [|
			public class StaticEvaluationDomainProvider($SimpleEvaluationDomainProvider):

				public static final Instance = StaticEvaluationDomainProvider()

				def constructor():
					super($(ImportsArrayFor(node)))

				override def GetAssemblyReferences():
					return $(AssemblyReferencesArray())
		|]
		my(CodeReifier).ReifyInto(node, evaluationDomainProviderImpl)
		return evaluationDomainProviderImpl.Members["Instance"].Entity

	private def CreateAssemblyReferencesArray() as IField:
		references = [| (System.Reflection.Assembly.GetExecutingAssembly(),) |]

		for assembly in ReferencedAssemblies():
			publicType = AnyPublicTypeOf(assembly)
			continue if publicType is null
			references.Items.Add([| typeof($publicType).Assembly |])

		arrayHolder = [|
			internal class EvalAssemblyReferences:
				public static final Value = $references
		|]
		my(CodeReifier).ReifyInto(CompileUnit.Modules[0], arrayHolder)
		return arrayHolder.Members["Value"].Entity

	private def AnyPublicTypeOf(assembly as System.Reflection.Assembly):
		for t in assembly.GetTypes():
			return t if t.IsPublic and not t.IsGenericType

	private def ReferencedAssemblies():
		collector = ReferencedAssemblyCollector()
		CompileUnit.Accept(collector)
		return collector.ReferencedAssemblies

class ReferencedAssemblyCollector(DepthFirstVisitor):

	[getter(ReferencedAssemblies)]
	_assemblies = Boo.Lang.Compiler.Util.Set of Assembly()

	override def OnReferenceExpression(node as ReferenceExpression):
		CheckTypeReference(node)

	override def LeaveMemberReferenceExpression(node as MemberReferenceExpression):
		CheckTypeReference(node)

	override def OnSimpleTypeReference(node as SimpleTypeReference):
		CheckTypeReference(node)

	def CheckTypeReference(node as Node):
		type = node.Entity as Boo.Lang.Compiler.TypeSystem.Reflection.ExternalType
		if type is null: return
		_assemblies.Add(type.ActualType.Assembly)

namespace UnityScript.Steps

import System
import Boo.Lang.Environments
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.TypeSystem
import Boo.Lang.Compiler.TypeSystem.Services
import UnityScript.Macros
import UnityScript.Scripting

class EvaluationDomainProviderImplementor(AbstractCompilerComponent):

	def ImplementIEvaluationDomainProviderOn(node as ClassDefinition):
		domainField = ReferenceExpression(Context.GetUniqueName("domain"))
		evaluationDomainProviderImpl = [|
			class _($IEvaluationDomainProvider):

				private $domainField as $EvaluationDomain

				def GetEvaluationDomain():
					return $domainField or ($domainField = $EvaluationDomain())

				def GetImports():
					return $(ImportsArrayFor(node))

				def GetAssemblyReferences():
					return $(AssemblyReferencesArray())
		|]

		my(CodeReifier).MergeInto(node, evaluationDomainProviderImpl)

	def StaticEvaluationDomainProviderFor(node as ClassDefinition) as IField:
		perNode node:
			return CreateStaticEvaluationDomainProviderReferenceOn(node)

	private def AssemblyReferencesArray() as Expression:
		perNode CompileUnit:
			return CodeBuilder.CreateReference(CreateAssemblyReferencesArray())

	private def ImportsArrayFor(node as ClassDefinition):
		result = [| (of $string:,) |]
		result.Items.Extend([| $(i.Namespace) |] for i in node.EnclosingModule.Imports)
		return result

	private def CreateStaticEvaluationDomainProviderReferenceOn(node as ClassDefinition):
		evaluationDomainProviderImpl = [|
			internal class StaticEvaluationDomainProvider($SimpleEvaluationDomainProvider):

				public static final Instance = StaticEvaluationDomainProvider()

				def constructor():
					super($(ImportsArrayFor(node)))

				override def GetAssemblyReferences():
					return $(AssemblyReferencesArray())
		|]
		my(CodeReifier).ReifyInto(node, evaluationDomainProviderImpl)
		return evaluationDomainProviderImpl.Members["Instance"].Entity

	private def CreateAssemblyReferencesArray() as IField:
		references = [| (System.Reflection.Assembly.GetExecutingAssembly(),) |]

		for assembly in ReferencedAssemblies():
			publicType = AnyPublicTypeOf(assembly)
			continue if publicType is null
			references.Items.Add([| typeof($publicType).Assembly |])

		arrayHolder = [|
			internal class EvalAssemblyReferences:
				public static final Value = $references
		|]
		my(CodeReifier).ReifyInto(CompileUnit.Modules[0], arrayHolder)
		return arrayHolder.Members["Value"].Entity

	private def AnyPublicTypeOf(assembly as System.Reflection.Assembly):
		for t in assembly.GetTypes():
			return t if t.IsPublic and not t.IsGenericType

	private def ReferencedAssemblies():
		collector = ReferencedAssemblyCollector()
		CompileUnit.Accept(collector)
		return collector.ReferencedAssemblies

class ReferencedAssemblyCollector(DepthFirstVisitor):

	[getter(ReferencedAssemblies)]
	_assemblies = Boo.Lang.Compiler.Util.Set of Assembly()

	override def OnReferenceExpression(node as ReferenceExpression):
		CheckTypeReference(node)

	override def LeaveMemberReferenceExpression(node as MemberReferenceExpression):
		CheckTypeReference(node)

	override def OnSimpleTypeReference(node as SimpleTypeReference):
		CheckTypeReference(node)

	def CheckTypeReference(node as Node):
		type = node.Entity as Boo.Lang.Compiler.TypeSystem.Reflection.ExternalType
		if type is null: return
		_assemblies.Add(type.ActualType.Assembly)


Publishing new version

Publishing new version

import System.IO
import System.Reflection

solution_file = "NExtra.sln"
build_folder  = "_tmpbuild_/"
build_version  = env('build.version')
build_config  = env('build.config')

test_assemblies = (
   "NExtra.Tests/bin/${build_config}/NExtra.Tests.dll",
   "NExtra.Web.Tests/bin/${build_config}/NExtra.Web.Tests.dll",
   "NExtra.Mvc.Tests/bin/${build_config}/NExtra.Mvc.Tests.dll",
   "NExtra.WPF.Tests/bin/${build_config}/NExtra.WPF.Tests.dll",
   "NExtra.WebForms.Tests/bin/${build_config}/NExtra.WebForms.Tests.dll",
   "NExtra.WinForms.Tests/bin/${build_config}/NExtra.WinForms.Tests.dll",
)

target default:
   pass

target zip, (compile, test, copy):
   zip("${build_folder}", "NExtra.${build_version}.zip")
   rmdir(build_folder)

target deploy, (compile, test, copy):
   with FileList(build_folder):
    .Include("**/**")
    .ForEach def(file):
      file.CopyToDirectory("NExtra.${build_version}")
   rmdir(build_folder)

target publish, (zip, publish_nuget, publish_github):
   pass

target compile:
   msbuild(file: solution_file, configuration: build_config, version: "4")

target test:
   nunit(assemblies: test_assemblies, enableTeamCity: true, toolPath: "resources/phantom/lib/nunit/nunit-console.exe", teamCityArgs: "v4.0 x86 NUnit-2.5.5")
   exec("del TestResult.xml")

target copy:
   rmdir(build_folder)
   mkdir(build_folder)

   File.Copy("README.md", "${build_folder}/README.txt", true)
   File.Copy("Release-notes.md", "${build_folder}/Release-notes.txt", true)

   with FileList(""):
    .Include("**/bin/${build_config}/*.dll")
    .Include("**/bin/${build_config}/*.pdb")
    .Include("**/bin/${build_config}/*.xml")
    .Exclude("**/bin/${build_config}/*.Tests.*")
    .Exclude("**/bin/${build_config}/nunit.framework.*")
    .Exclude("**/bin/${build_config}/nsubstitute.*")
    .ForEach def(file):
      File.Copy(file.FullName, "${build_folder}/${file.Name}", true)

target publish_nuget:
   File.Copy("README.md", "Resources\\README.txt", true)
   File.Copy("Release-notes.md", "Resources\\Release-notes.txt", true)

   exec("nuget" , "pack nextra\\nextra.csproj -prop configuration=release")
   exec("nuget" , "pack nextra.web\\nextra.web.csproj -prop configuration=release")
   exec("nuget" , "pack nextra.mvc\\nextra.mvc.csproj -prop configuration=release")
   exec("nuget" , "pack nextra.wpf\\nextra.wpf.csproj -prop configuration=release")
   exec("nuget" , "pack nextra.webforms\\nextra.webforms.csproj -prop configuration=release")
   exec("nuget" , "pack nextra.winforms\\nextra.winforms.csproj -prop configuration=release")

   exec("nuget push nextra.${build_version}.nupkg")
   exec("nuget push nextra.web.${build_version}.nupkg")
   exec("nuget push nextra.mvc.${build_version}.nupkg")
   exec("nuget push nextra.wpf.${build_version}.nupkg")
   exec("nuget push nextra.webforms.${build_version}.nupkg")
   exec("nuget push nextra.winforms.${build_version}.nupkg")

   exec("del *.nupkg")
   exec("del Resources\\README.txt")
   exec("del Resources\\Release-notes.txt")

target publish_github:
   exec("git add .")
   exec("git commit . -m \"Publishing new version\"")
   exec("git tag ${build_version}")
   exec("git push origin master")
   exec("git push origin ${build_version}")

import System.IO
import System.Reflection

solution_file = "NExtra.sln"
build_folder  = "_tmpbuild_/"
build_version  = env('build.version')
build_config  = env('build.config')

test_assemblies = (
   "NExtra.Tests/bin/${build_config}/NExtra.Tests.dll",
   "NExtra.Web.Tests/bin/${build_config}/NExtra.Web.Tests.dll",
   "NExtra.Mvc.Tests/bin/${build_config}/NExtra.Mvc.Tests.dll",
   "NExtra.WPF.Tests/bin/${build_config}/NExtra.WPF.Tests.dll",
   "NExtra.WebForms.Tests/bin/${build_config}/NExtra.WebForms.Tests.dll",
   "NExtra.WinForms.Tests/bin/${build_config}/NExtra.WinForms.Tests.dll",
)

target default:
   pass

target zip, (compile, test, copy):
   zip("${build_folder}", "NExtra.${build_version}.zip")
   rmdir(build_folder)

target deploy, (compile, test, copy):
   with FileList(build_folder):
    .Include("**/**")
    .ForEach def(file):
      file.CopyToDirectory("NExtra.${build_version}")
   rmdir(build_folder)

target publish, (zip, publish_nuget, publish_github):
   pass

target compile:
   msbuild(file: solution_file, configuration: build_config, version: "4")

target test:
   nunit(assemblies: test_assemblies, enableTeamCity: true, toolPath: "resources/phantom/lib/nunit/nunit-console.exe", teamCityArgs: "v4.0 x86 NUnit-2.5.5")
   exec("del TestResult.xml")

target copy:
   rmdir(build_folder)
   mkdir(build_folder)

   File.Copy("README.md", "${build_folder}/README.txt", true)
   File.Copy("Release-notes.md", "${build_folder}/Release-notes.txt", true)

   with FileList(""):
    .Include("**/bin/${build_config}/*.dll")
    .Include("**/bin/${build_config}/*.pdb")
    .Include("**/bin/${build_config}/*.xml")
    .Exclude("**/bin/${build_config}/*.Tests.*")
    .Exclude("**/bin/${build_config}/nunit.framework.*")
    .Exclude("**/bin/${build_config}/nsubstitute.*")
    .ForEach def(file):
      File.Copy(file.FullName, "${build_folder}/${file.Name}", true)

target publish_nuget:
   File.Copy("README.md", "Resources\\README.txt", true)
   File.Copy("Release-notes.md", "Resources\\Release-notes.txt", true)

   exec("nuget" , "pack nextra\\nextra.csproj -prop configuration=release")
   exec("nuget" , "pack nextra.web\\nextra.web.csproj -prop configuration=release")
   exec("nuget" , "pack nextra.mvc\\nextra.mvc.csproj -prop configuration=release")
   exec("nuget" , "pack nextra.wpf\\nextra.wpf.csproj -prop configuration=release")
   exec("nuget" , "pack nextra.webforms\\nextra.webforms.csproj -prop configuration=release")
   exec("nuget" , "pack nextra.winforms\\nextra.winforms.csproj -prop configuration=release")

   exec("nuget push nextra.${build_version}.nupkg")
   exec("nuget push nextra.web.${build_version}.nupkg")
   exec("nuget push nextra.mvc.${build_version}.nupkg")
   exec("nuget push nextra.wpf.${build_version}.nupkg")
   exec("nuget push nextra.webforms.${build_version}.nupkg")
   exec("nuget push nextra.winforms.${build_version}.nupkg")

   exec("del *.nupkg")
   exec("del Resources\\README.txt")
   exec("del Resources\\Release-notes.txt")

target publish_github:
   exec("git add .")
   exec("git commit . -m 'Publishing'")
   exec("git tag ${build_version}")
   exec("git push origin master")
   exec("git push origin ${build_version}")


Fixed instrumented generator logic for while loops

namespace BooJs.Compiler.Steps

import System
import Boo.Lang.Compiler(CompilerErrorFactory)
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.Steps
import Boo.Lang.Compiler.TypeSystem.Internal
import Boo.Lang.PatternMatching

class ProcessGenerators(AbstractTransformerCompilerStep):
"""
    Specialized step to process generators replacing Boo's one. Code is
    transformed to a reentrant state machine, allowing to halt execution
    where a yield is found and resume later on from that same point.
"""
    class TransformGenerator(FastDepthFirstVisitor):
        [getter(States)]
        _states = StatementCollection()

        property State as int = 0

        Current as Block:
            get: return States[State]

        _statemachinelabel = ReferenceExpression('statemachine')
        _gotostatemachine = GotoStatement(Label: _statemachinelabel)

        def constructor():
            CreateState()

        def CreateState():
            block = Block()
            States.Add(block)
            return len(States)-1

        def OnExpressionStatement(node as ExpressionStatement):
            Current.Add(node)

        def OnForStatement(node as ForStatement):
            Current.Add(node)

        def OnYieldStatement(node as YieldStatement):
            if node.GetAncestor(NodeType.IfStatement) or node.GetAncestor(NodeType.UnlessStatement):
                raise CompilerErrorFactory.NotImplemented(node, 'Yield is not currently supported inside if or unless statements')

            # Create a re-entry state
            nextstate = CreateState()

            # Setup jump to next state and return value
            Current.Add([| __state = $(nextstate) |])
            Current.Add([| return $(node.Expression) |])

            # Continue in the re-entry state
            State = nextstate

        def OnReturnStatement(node as ReturnStatement):
            if node.GetAncestor(NodeType.IfStatement) or node.GetAncestor(NodeType.UnlessStatement):
                raise CompilerErrorFactory.NotImplemented(node, 'Return is not currently supported inside if or unless statements')

            # If it has an expression just handle as a yield
            if node.Expression:
                ynode = YieldStatement(node.LexicalInfo, Expression: node.Expression)
                OnYieldStatement(ynode)
            # Otherwise just create a new state where the generator terminates
            else:
                nextstate = CreateState()
                Current.Add([| __state = $(nextstate) |])
                Current.Add(_gotostatemachine)
                State = nextstate

            # At this point we want to always stop the generator
            Current.Add([| raise Boo.STOP |])

        def OnWhileStatement(node as WhileStatement):
            # Loop always starts in a new step
            loopstate = CreateState()

            # Make sure the current state ends up in the loop one
            if State != loopstate - 1:
                Current.Add([| __state = $(loopstate) |])
                Current.Add(_gotostatemachine)

            # Process the loop body
            State = loopstate
            Visit node.Block

            # Go back to the start of the loop to check if we have more iterations left
            Current.Add([| __state = $(loopstate) |])
            Current.Add(_gotostatemachine)

            # Create new step for exiting the loop but don't use it yet
            exitstate = CreateState()

            # Check if we should skip the loop by negating the condition
            State = loopstate
            ifs = [|
                if not $(node.Condition):
                    __state = $(exitstate)
                    $(_gotostatemachine)
            |]
            # Place it as the first statement in the loop state
            Current.Insert(0, ifs)

            # Continue in the exit step previously created
            State = exitstate

        def OnTryStatement(node as TryStatement):
            # A try block always initiates an state
            trystate = CreateState()

            # Make sure the current state ends up in the new one
            Current.Add([| __state = $(trystate) |])
            Current.Add(_gotostatemachine)

            State = trystate

            # Register ensure block
            if node.EnsureBlock:
                Current.Add([| __final.push( $(BlockExpression(Body: node.EnsureBlock)) ) |])

            Visit node.ProtectedBlock

            # Execute ensure block after we reach the end of the protected block
            if node.EnsureBlock:
                Current.Add( [| __final.pop()() |] )

            # Create a new state for exiting the try/except block
            afterstate = CreateState()

            # Skip the exception handler state
            Current.Add( [| __state = $afterstate |] )
            Current.Add(_gotostatemachine)

            if not node.ExceptionHandlers.IsEmpty:
                # Create a new state for the exception handler
                exceptstate = CreateState()

                # Register the states covered by this protected block
                tryblock = States[trystate] as Block
                catchst = [| $exceptstate |]
                for state in range(trystate, exceptstate):
                    catchst = [| __catch[$state] = $catchst |]
                tryblock.Insert(0, catchst)

                # Produce the exception handler
                State = exceptstate
                Visit node.ExceptionHandlers

            # Continue in the after state
            State = afterstate

        def OnUnlessStatement(node as UnlessStatement):
        """ We just include them in the current state, no special processing is performed
            currently. This means that neither yield nor return statements can be used inside.
        """
            super(node)
            Current.Add(node)

        def OnIfStatement(node as IfStatement):
        """ We just include them in the current state, no special processing is performed
            currently. This means that neither yield nor return statements can be used inside.
        """
            super(node)
            Current.Add(node)

    override def Run():
        if len(Errors) > 0:
            return
        Visit CompileUnit

    def HasTryStatement(node as Block) as bool:
        for st in node.Statements:
            match st:
                case TryStatement():
                    return true
                case wst=WhileStatement():
                    return HasTryStatement(wst.Block)
                case ist=IfStatement():
                    return HasTryStatement(ist.TrueBlock) or HasTryStatement(ist.FalseBlock)
                case bst=Block():
                    return HasTryStatement(bst)
                otherwise:
                    continue

        return false

    override def EnterMethod(node as Method):
        # Types should be already resolved so we can just check if it was flagged as a generator
        entity = node.Entity as InternalMethod
        return entity and entity.IsGenerator

    override def LeaveMethod(node as Method):

        has_try = HasTryStatement(node.Body)

        # Transform the method body into a list of states
        transformer = TransformGenerator()
        transformer.OnBlock(node.Body)

        # If the method is a closure we look for the original method node to declare locals in it
        method = node
        if node.IsSynthetic:
            for member in (node.ParentNode as TypeDefinition).Members:
                if member.LexicalInfo.Equals(node.LexicalInfo):
                    method = member
                    break

        # Remove the original method contents
        node.Body.Clear()
        # Create a new local to keep the current step of the generator
        CodeBuilder.DeclareLocal(method, '__state', TypeSystemServices.IntType)
        if has_try:
            CodeBuilder.DeclareLocal(method, '__catch', TypeSystemServices.HashType)
            CodeBuilder.DeclareLocal(method, '__final', TypeSystemServices.ArrayType)
            node.Body.Add( CodeBuilder.CreateAssignment(ReferenceExpression('__catch'), HashLiteralExpression()) )
            node.Body.Add( CodeBuilder.CreateAssignment(ReferenceExpression('__final'), ListLiteralExpression()) )

        # Add value check to first state
        valuecheck = [|
            if __value is not Boo.UNDEF or __error is not Boo.UNDEF:
                raise TypeError('Generator not started yet, unable to process sent value/error')
        |]
        (transformer.States[0] as Block).Insert(0, valuecheck)

        # Wrap the steps into a switch/case construct
        switch = MacroStatement(Name: 'switch')
        switch.Arguments.Add(ReferenceExpression('__state'))
        for idx as int, step as Block in enumerate(transformer.States):
            case = MacroStatement()
            case.Name = 'case'
            case.Arguments.Add(IntegerLiteralExpression(idx))
            case.Body = step
            switch.Body.Add(case)

        statemachine as Statement
        statemachine = [|
            # NOTE: To simplify we will raise the sent error from the top of the generator
            #       function and not from the current step. Unless real world scenarios show
            #       that it's desirable to have it raised from more exact locations, it doesn't
            #       seem to have a great value added in contrast to its cost.
            if __error:
                raise __error

            $switch

            # Set the state to an imposible value
            __state = -1
            raise Boo.STOP
        |]

        # Wrap the state machine to support exception handling
        if has_try:
            statemachine = [|
                try:
                    $statemachine
                except:
                    if __state in __catch:
                        __error = null  # Make sure any reported error is removed
                        __state = __catch[__state]
                        goto statemachine

                    # Position the generator to the terminating state
                    __state = -1

                    # Ensure all finally blocks are executed
                    while __final.length:
                        __final.pop()()

                    # Re-raise the exception
                    raise __e
            |]

        # Make the method return a generator
        generator = [|
            return Boo.generator do(__value, __error):
                :statemachine
                $statemachine
        |]
        node.Body.Add(generator)

namespace BooJs.Compiler.Steps

import System
import Boo.Lang.Compiler(CompilerErrorFactory)
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.Steps
import Boo.Lang.Compiler.TypeSystem.Internal
import Boo.Lang.PatternMatching

class ProcessGenerators(AbstractTransformerCompilerStep):
"""
    Specialized step to process generators replacing Boo's one. Code is
    transformed to a reentrant state machine, allowing to halt execution
    where a yield is found and resume later on from that same point.
"""
    class TransformGenerator(FastDepthFirstVisitor):
        [getter(States)]
        _states = StatementCollection()

        property State as int = 0

        Current as Block:
            get: return States[State]

        _statemachinelabel = ReferenceExpression('statemachine')
        _gotostatemachine = GotoStatement(Label: _statemachinelabel)

        def constructor():
            CreateState()

        def CreateState():
            block = Block()
            States.Add(block)
            return len(States)-1

        def OnExpressionStatement(node as ExpressionStatement):
            Current.Add(node)

        def OnForStatement(node as ForStatement):
            Current.Add(node)

        def OnYieldStatement(node as YieldStatement):
            if node.GetAncestor(NodeType.IfStatement) or node.GetAncestor(NodeType.UnlessStatement):
                raise CompilerErrorFactory.NotImplemented(node, 'Yield is not currently supported inside if or unless statements')

            # Create a re-entry state
            nextstate = CreateState()

            # Setup jump to next state and return value
            Current.Add([| __state = $(nextstate) |])
            Current.Add([| return $(node.Expression) |])

            # Continue in the re-entry state
            State = nextstate

        def OnReturnStatement(node as ReturnStatement):
            if node.GetAncestor(NodeType.IfStatement) or node.GetAncestor(NodeType.UnlessStatement):
                raise CompilerErrorFactory.NotImplemented(node, 'Return is not currently supported inside if or unless statements')

            # If it has an expression just handle as a yield
            if node.Expression:
                ynode = YieldStatement(node.LexicalInfo, Expression: node.Expression)
                OnYieldStatement(ynode)
            # Otherwise just create a new state where the generator terminates
            else:
                nextstate = CreateState()
                Current.Add([| __state = $(nextstate) |])
                Current.Add(_gotostatemachine)
                State = nextstate

            # At this point we want to always stop the generator
            Current.Add([| raise Boo.STOP |])

        def OnWhileStatement(node as WhileStatement):
            # Loop always starts in a new step
            loopstate = CreateState()

            # Make sure the current state ends up in the loop one
            Current.Add([| __state = $(loopstate) |])
            Current.Add(_gotostatemachine)

            State = loopstate

            # Create new step for exiting the loop but don't use it yet
            exitstate = CreateState()

            # Check if we should skip the loop by negating the condition
            ifs = [|
                if not $(node.Condition):
                    __state = $(exitstate)
                    $(_gotostatemachine)
            |]
            Current.Add(ifs)

            # Process the loop body
            Visit node.Block

            # Go back to the start of the loop to check if we have more iterations left
            Current.Add([| __state = $(loopstate) |])
            Current.Add(_gotostatemachine)

            # Continue in the exit step previously created
            State = exitstate

        def OnTryStatement(node as TryStatement):
            # A try block always initiates an state
            trystate = CreateState()

            # Make sure the current state ends up in the new one
            Current.Add([| __state = $(trystate) |])
            Current.Add(_gotostatemachine)

            State = trystate

            # Register ensure block
            if node.EnsureBlock:
                Current.Add([| __final.push( $(BlockExpression(Body: node.EnsureBlock)) ) |])

            Visit node.ProtectedBlock

            # Execute ensure block after we reach the end of the protected block
            if node.EnsureBlock:
                Current.Add( [| __final.pop()() |] )

            # Create a new state for exiting the try/except block
            afterstate = CreateState()

            # Skip the exception handler state
            Current.Add( [| __state = $afterstate |] )
            Current.Add(_gotostatemachine)

            if not node.ExceptionHandlers.IsEmpty:
                # Create a new state for the exception handler
                exceptstate = CreateState()

                # Register the states covered by this protected block
                tryblock = States[trystate] as Block
                catchst = [| $exceptstate |]
                for state in range(trystate, exceptstate):
                    catchst = [| __catch[$state] = $catchst |]
                tryblock.Insert(0, catchst)

                # Produce the exception handler
                State = exceptstate
                Visit node.ExceptionHandlers

            # Continue in the after state
            State = afterstate

        def OnUnlessStatement(node as UnlessStatement):
        """ We just include them in the current state, no special processing is performed
            currently. This means that neither yield nor return statements can be used inside.
        """
            super(node)
            Current.Add(node)

        def OnIfStatement(node as IfStatement):
        """ We just include them in the current state, no special processing is performed
            currently. This means that neither yield nor return statements can be used inside.
        """
            super(node)
            Current.Add(node)

    override def Run():
        if len(Errors) > 0:
            return
        Visit CompileUnit

    def HasTryStatement(node as Block) as bool:
        for st in node.Statements:
            match st:
                case TryStatement():
                    return true
                case wst=WhileStatement():
                    return HasTryStatement(wst.Block)
                case ist=IfStatement():
                    return HasTryStatement(ist.TrueBlock) or HasTryStatement(ist.FalseBlock)
                case bst=Block():
                    return HasTryStatement(bst)
                otherwise:
                    continue

        return false

    override def EnterMethod(node as Method):
        # Types should be already resolved so we can just check if it was flagged as a generator
        entity = node.Entity as InternalMethod
        return entity and entity.IsGenerator

    override def LeaveMethod(node as Method):

        has_try = HasTryStatement(node.Body)

        # Transform the method body into a list of states
        transformer = TransformGenerator()
        transformer.OnBlock(node.Body)

        # If the method is a closure we look for the original method node to declare locals in it
        method = node
        if node.IsSynthetic:
            for member in (node.ParentNode as TypeDefinition).Members:
                if member.LexicalInfo.Equals(node.LexicalInfo):
                    method = member
                    break

        # Remove the original method contents
        node.Body.Clear()
        # Create a new local to keep the current step of the generator
        CodeBuilder.DeclareLocal(method, '__state', TypeSystemServices.IntType)
        if has_try:
            CodeBuilder.DeclareLocal(method, '__catch', TypeSystemServices.HashType)
            CodeBuilder.DeclareLocal(method, '__final', TypeSystemServices.ArrayType)
            node.Body.Add( CodeBuilder.CreateAssignment(ReferenceExpression('__catch'), HashLiteralExpression()) )
            node.Body.Add( CodeBuilder.CreateAssignment(ReferenceExpression('__final'), ListLiteralExpression()) )

        # Add value check to first state
        valuecheck = [|
            if __value is not Boo.UNDEF or __error is not Boo.UNDEF:
                raise TypeError('Generator not started yet, unable to process sent value/error')
        |]
        (transformer.States[0] as Block).Insert(0, valuecheck)

        # Wrap the steps into a switch/case construct
        switch = MacroStatement(Name: 'switch')
        switch.Arguments.Add(ReferenceExpression('__state'))
        for idx as int, step as Block in enumerate(transformer.States):
            case = MacroStatement()
            case.Name = 'case'
            case.Arguments.Add(IntegerLiteralExpression(idx))
            case.Body = step
            switch.Body.Add(case)

        statemachine as Statement
        statemachine = [|
            # NOTE: To simplify we will raise the sent error from the top of the generator
            #       function and not from the current step. Unless real world scenarios show
            #       that it's desirable to have it raised from more exact locations, it doesn't
            #       seem to have a great value added in contrast to its cost.
            if __error:
                raise __error

            $switch

            # Set the state to an imposible value
            __state = -1
            raise Boo.STOP
        |]

        # Wrap the state machine to support exception handling
        if has_try:
            statemachine = [|
                try:
                    $statemachine
                except:
                    if __state in __catch:
                        __error = null  # Make sure any reported error is removed
                        __state = __catch[__state]
                        goto statemachine

                    # Position the generator to the terminating state
                    __state = -1

                    # Ensure all finally blocks are executed
                    while __final.length:
                        __final.pop()()

                    # Re-raise the exception
                    raise __e
            |]

        # Make the method return a generator
        generator = [|
            return Boo.generator do(__value, __error):
                :statemachine
                $statemachine
        |]
        node.Body.Add(generator)


Fixing issue #116

Fixing issue #116

#region license
// Copyright (c) 2003, 2004, 2005 Rodrigo B. de Oliveira (rbo@acm.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//     * Neither the name of Rodrigo B. de Oliveira nor the names of its
//     contributors may be used to endorse or promote products derived from this
//     software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion

namespace Boo.Lang.Extensions

import Boo.Lang.Environments
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.TypeSystem
import Boo.Lang.Compiler.TypeSystem.Internal
import Boo.Lang.Compiler.TypeSystem.Reflection
import Boo.Lang.Compiler.TypeSystem.Services

class MacroMacro(LexicalInfoPreservingGeneratorMacro):

	private static final Usage = "Usage: `macro [<parent.>+]<name>[(arg0,...)]`"

	_macro as MacroStatement
	_name as string

	_apb as ArgumentsPatternBuilder
	_usingPatternMatching = false

	ArgumentsPattern:
		get:
			return _apb.Pattern if _apb

	ArgumentsPrologue:
		get:
			return _apb.Prologue if _apb

	override protected def ExpandGeneratorImpl(macro as MacroStatement):
		raise Usage if len(macro.Arguments) != 1

		_macro = macro

		arg = macro.Arguments[0]
		mie = arg as MethodInvocationExpression
		if mie:
			_apb = ArgumentsPatternBuilder(mie.Arguments)
			arg = mie.Target

		if arg.NodeType == NodeType.ReferenceExpression:
			_name = cast(ReferenceExpression, arg).Name
			yield CreateMacroType()
			yield [| import Boo.Lang.PatternMatching |] if _usingPatternMatching
		elif arg.NodeType == NodeType.MemberReferenceExpression:
			if macro.GetAncestor[of MacroStatement]() is not null:
				raise "Nested macro extension cannot be itself a nested macro"
			_name = cast(MemberReferenceExpression, arg).Name
			CreateMacroExtensionType(cast(MemberReferenceExpression, arg))
		else:
			raise Usage

	private static def BuildMacroTypeName(name as string) as string:
		return char.ToUpper(name[0]) + name[1:] + "Macro"

	private static def BuildMacroTypeName(name as Expression) as string:
		return BuildMacroTypeName(name, false)

	private static def BuildMacroTypeName(name as Expression, external as bool) as string:
		sb = System.Text.StringBuilder()
		for part in UnpackReferences(name):
			sb.Append(char('.')) if sb.Length and not external
			sb.Append(char('$')) if external
			sb.Append(BuildMacroTypeName(part))
		return sb.ToString()

	private static def UnpackReferences(refs as Expression) as string*:
		if refs.NodeType == NodeType.ReferenceExpression:
			re = cast(ReferenceExpression, refs)
			raise "Extending macro `macro` is not supported" if re.Name == "macro"
			yield re.Name
		elif refs.NodeType == NodeType.MemberReferenceExpression:
			mre = cast(MemberReferenceExpression, refs)
			for name in UnpackReferences(mre.Target):
				yield name
			yield mre.Name
		else:
			raise "Invalid node type: ${refs.NodeType}"

	private static def GetParentMacroNames(macro as MacroStatement) as string*:
		for parent in macro.GetAncestors[of MacroStatement]():
			continue if parent.Name != 'macro'

			arg = parent.Arguments[0]
			if arg.NodeType == NodeType.MethodInvocationExpression:
				arg = cast(MethodInvocationExpression, arg).Target

			if arg.NodeType == NodeType.ReferenceExpression:
				yield cast(ReferenceExpression, arg).Name

			elif arg.NodeType == NodeType.MemberReferenceExpression:
				for name in UnpackReferences(cast(MemberReferenceExpression, arg)):
					yield name

	private def CreateMacroExtensionType(mre as MemberReferenceExpression):
		#resolve the macro type we want to expand
		parentTypeName = BuildMacroTypeName(mre.Target)
		parent = NameResolutionService.ResolveQualifiedName(parentTypeName) as IType
		if parent is null: #oh, this is a external extension maybe?
			parentTypeName = BuildMacroTypeName(mre.Target, true)
			parent = NameResolutionService.ResolveQualifiedName(parentTypeName) as IType
		if parent is null:
			raise "No macro `${mre.Target.ToString()}` has been found to extend"

		elif parent isa InternalClass:
			#create macro type and add it as usual into its parent macro type
			type = CreateMacroType(UnpackReferences(mre.Target))
			cast(InternalClass, parent).TypeDefinition.Members.Add(type)

		elif parent isa ExternalType:
			#create macro type with parent(s)-based prefix then add an extension method for redirection
			type = CreateMacroType(BuildMacroTypeName(mre, true), UnpackReferences(mre.Target))
			module = _macro.GetAncestor[of Module]()
			module.Members.Add(type)
			method = CreateMacroExtensionProxy(parent, type.Name)
			module.Members.Add(method)

		else:
			raise "Cannot bind a nested macro extension to entity: ${parent}"

	private def CreateMacroExtensionProxy(parent as IType, extension as string):
		return [|
			[Boo.Lang.ExtensionAttribute]
			[System.Runtime.CompilerServices.CompilerGeneratedAttribute]
			static def $(_name)(parent as $(parent.FullName), context as Boo.Lang.Compiler.CompilerContext) as $(extension):
				return $(ReferenceExpression(extension))(context)
		|]

	private def CreateMacroType() as ClassDefinition:
		return CreateMacroType(BuildMacroTypeName(_name), GetParentMacroNames(_macro))

	private def CreateMacroType(parents as string*) as ClassDefinition:
		return CreateMacroType(BuildMacroTypeName(_name), parents)

	private def CreateMacroType(typeName as string, parents as string*) as ClassDefinition:
		if YieldFinder(_macro).Found:
			macroType = CreateGeneratorMacroType(typeName)
		else:
			macroType = CreateOldStyleMacroType(typeName)

		sme = SubMacroExtractor()
		for member in macroType.Members.ToArray():
			method = member as Method
			continue if method is null
			method.Accept(sme)

		#add parent macro(s) accessor(s)
		for parent in parents:
			if not macroType.Members[parent]:
				for accessor in CreateParentMacroAccessor(parent):
					macroType.Members.Add(accessor)
		return macroType

	#BOO-1077 style
	private def CreateGeneratorMacroType(typeName as string) as ClassDefinition:
		compatErrorMsg = "Boo installed version is older than the new macro syntax '${_name}' is using. Read BOO-1077 for more info."
		arg = ReferenceExpression(_name)
		return [|
				public final class $(typeName) (Boo.Lang.Compiler.LexicalInfoPreservingGeneratorMacro):
					[System.Runtime.CompilerServices.CompilerGeneratedAttribute]
					private __macro as Boo.Lang.Compiler.Ast.MacroStatement
					public def constructor():
						super()
					public def constructor(context as Boo.Lang.Compiler.CompilerContext):
						raise System.ArgumentNullException("context") if not context
						super(context)
					override protected def ExpandGeneratorImpl($_name as Boo.Lang.Compiler.Ast.MacroStatement) as Boo.Lang.Compiler.Ast.Node*:
						raise System.ArgumentNullException($_name) if not $arg
						self.__macro = $arg
						$(ExpandBody())

					[System.Runtime.CompilerServices.CompilerGeneratedAttribute]
					override protected def ExpandImpl($_name as Boo.Lang.Compiler.Ast.MacroStatement) as Boo.Lang.Compiler.Ast.Statement:
						raise System.NotImplementedException($compatErrorMsg)
			|]

	private def CreateOldStyleMacroType(typeName as string) as ClassDefinition:
		arg = ReferenceExpression(_name)
		return [|
				public final class $(typeName) (Boo.Lang.Compiler.LexicalInfoPreservingMacro):
					[System.Runtime.CompilerServices.CompilerGeneratedAttribute]
					private __macro as Boo.Lang.Compiler.Ast.MacroStatement
					public def constructor():
						super()
					public def constructor(context as Boo.Lang.Compiler.CompilerContext):
						raise System.ArgumentNullException("context") if not context
						super(context)
					override protected def ExpandImpl($_name as Boo.Lang.Compiler.Ast.MacroStatement) as Boo.Lang.Compiler.Ast.Statement:
						raise System.ArgumentNullException($_name) if not $arg
						self.__macro = $arg
						$(ExpandBody())
			|]

	private def ExpandBody():
		body = _macro.Body

		if ArgumentsPattern:
			if 0 == ArgumentsPattern.Count and not ArgumentsPrologue:
				body = [|
					if __macro.Arguments.Count == 0:
						$body
					else:
						$(CreateRaiseMacroArgumentsMatchError())
				|].ToBlock()
			elif ArgumentsPattern.Count > 0:
				case = CaseStatement()
				case.Pattern = QuasiquoteExpression(pattern = MacroStatement(_name))
				pattern.Arguments = ArgumentsPattern
				if ArgumentsPrologue:
					case.Body = [|
						$ArgumentsPrologue
						$body
					|].ToBlock()
				else:
					case.Body = body
				otherwise = OtherwiseStatement() #TODO: macro overload without arguments def
				otherwise.Body = CreateRaiseMacroArgumentsMatchError().ToBlock()
				body = [|
					$case
					$otherwise
				|]

		if ContainsCase(body):
			return ExpandWithPatternMatching(_name, body)

		if ArgumentsPrologue:
			return [|
				$ArgumentsPrologue
				$body
			|].ToBlock()
		else:
			return body

	def CreateRaiseMacroArgumentsMatchError() as RaiseStatement:
		errorMsg = "`${$(_name)}` macro invocation argument(s) did not match definition: `${$(_macro.Arguments[0].ToString())}`"
		return [| raise $errorMsg |]

	#region PatternMatching
	internal class CustomBlockStatement(CustomStatement):
		public Body as Block

	internal class CaseStatement(CustomBlockStatement):
		public Pattern as Expression

	internal class OtherwiseStatement(CustomBlockStatement):
		pass

	class CaseMacro(LexicalInfoPreservingGeneratorMacro):
		override protected def ExpandGeneratorImpl(case as MacroStatement):
			if len(case.Arguments) != 1:
				raise "Usage: case <pattern>"
			pattern, = case.Arguments
			yield CaseStatement(Pattern: pattern, Body: case.Body)

	class OtherwiseMacro(LexicalInfoPreservingGeneratorMacro):
		override protected def ExpandGeneratorImpl(case as MacroStatement):
			if len(case.Arguments) != 0:
				raise "Usage: otherwise: <block>"
			yield OtherwiseStatement(Body: case.Body)

	private static def ContainsCase(body as Block):
		for stmt in body.Statements:
			return true if stmt isa CaseStatement

	private def ExpandWithPatternMatching(name as string, body as Block):

		_usingPatternMatching = true

		matchBlock = [|
			match $(ReferenceExpression(name)):
				pass
		|]
		statementsEnumerator = body.Statements.GetEnumerator()
		while statementsEnumerator.MoveNext():
			stmt as Statement = statementsEnumerator.Current
			if stmt isa CaseStatement:
				case as CaseStatement = stmt
				caseBlock = [|
					case $(case.Pattern):
						$(case.Body)
				|]
				matchBlock.Body.Add(caseBlock)
			elif stmt isa OtherwiseStatement:
				otherwise as OtherwiseStatement = stmt
				otherwiseBlock = [|
					otherwise:
						$(otherwise.Body)
				|]
				matchBlock.Body.Add(otherwiseBlock)

				// otherwise marks the end of the match block
				resultingBlock = Block()
				resultingBlock.Add(matchBlock)
				for remaining as Statement in statementsEnumerator:
					resultingBlock.Add(remaining)
				return resultingBlock
			else:
				// statements after a sequence of 'case <pattern>' macros
				// also mark the end of a match block
				resultingBlock = Block()
				resultingBlock.Add(matchBlock)
				resultingBlock.Add(stmt)
				for remaining as Statement in statementsEnumerator:
					resultingBlock.Add(remaining)
				return resultingBlock
		return matchBlock
	#endregion

	private static def CreateParentMacroAccessor(name as string):
		cacheField = [|
			[System.Runtime.CompilerServices.CompilerGeneratedAttribute]
			private $("$" + name) as Boo.Lang.Compiler.Ast.MacroStatement
		|]
		yield cacheField

		cacheFieldRef = ReferenceExpression(cacheField.Name)
		prop = [|
			[System.Runtime.CompilerServices.CompilerGeneratedAttribute]
			private $name:
				get:
					$cacheFieldRef = __macro.GetParentMacroByName($name) unless $cacheFieldRef
					return $cacheFieldRef
		|]
		prop.IsSynthetic = true #avoid BCW0014 if not used
		yield prop

	private final class YieldFinder(DepthFirstVisitor):
		private _found = false

		def constructor(macro as MacroStatement):
			macro.Body.Accept(self)

		Found:
			get: return _found

		override def OnMethod(node as Method):
			pass

		override def OnCustomStatement(node as CustomStatement):
			customBlock = node as CustomBlockStatement
			if customBlock is null:
				super(node)
			else:
				customBlock.Body.Accept(self)

		override def OnYieldStatement(node as YieldStatement):
			_found = true

		override def EnterMacroStatement(node as MacroStatement) as bool:
			return false

	private final class ArgumentsPatternBuilder():
		static final MacroField = ReferenceExpression("__macro")

		_input as ExpressionCollection
		_pattern as ExpressionCollection
		_prologue as Block
		_tss = EnvironmentProvision[of TypeSystemServices]()
		_nrs = EnvironmentProvision[of NameResolutionService]()
		_arg as ReferenceExpression
		_argIndex = 0
		_enumerable = false

		def constructor(input as ExpressionCollection):
			_input = input

		Pattern:
			get:
				Build() if not _pattern
				return _pattern

		Prologue:
			get:
				Build() if not _pattern
				return _prologue

		def Build():
			_pattern = ExpressionCollection()
			for arg in _input:
				_enumerable = false
				Append(arg) #FIXME: run-time (duck) dispatch here makes mono cry
				++_argIndex

		private TypeSystemServices:
			get: return _tss.Instance

		private NameResolutionService:
			get: return _nrs.Instance

		private IsBodyArgument:
			get: return _arg.Name == "body"

		private IsLastArgument:
			get: return _argIndex == _input.Count-1

		private def Append(e as Expression):
			if e.NodeType == NodeType.ReferenceExpression:
				_arg = cast(ReferenceExpression, e)
				Append(null as IType)
			elif e.NodeType == NodeType.TryCastExpression:
				Append(e as TryCastExpression)
			else:
				raise "Invalid macro argument declaration: `${e}`"

		private def Append(e as TryCastExpression):
			re = e.Target as ReferenceExpression
			raise "Invalid macro argument name: `${e.Target}`" unless re

			NameResolutionService.ResolveTypeReference(e.Type)
			type = TypeSystemServices.GetType(e.Type)
			_arg = cast(ReferenceExpression, re)
			Append(type)

		private def Append(type as IType):
			if IsBodyArgument and not IsLastArgument:
				raise "`${_arg.Name}` argument must be the last argument"

			if type is null:
				AppendDefaultNode()
			elif IsAstNode(type):
				AppendNode(type)
			elif TypeSystemServices.IsLiteralPrimitive(type):
				AppendPrimitive(type)
			elif not _enumerable and (etype = TypeSystemServices.GetEnumeratorItemType(type)) is not null:
				if not IsLastArgument:
					raise "Enumerable or array type argument `${_arg.Name}` must be the last argument"
				if not IsBodyArgument and _pattern.Count > 0:
					_pattern.Add(SpliceExpression([| *_ |]))
				_enumerable = true
				Append(etype)
			else:
				raise "Unsupported type `${type.FullName}` for argument `${_arg.Name}`, a macro argument type must be a literal-able primitive or an AST node"

		private def IsAstNode(type as IType):
			return type.IsSubclassOf(TypeSystemServices.Map(Boo.Lang.Compiler.Ast.Node))

		private def AppendDefaultNode():
			if IsBodyArgument:
				_prologue = Block()
				_prologue.Add([| $_arg = $(MacroField).Body.Statements |])
			else:
				_pattern.Add(SpliceExpression([| $_arg = Boo.Lang.Compiler.Ast.Expression() |]))

		private def AppendNode(type as IType):
			AssertEnumerable(type.FullName) if IsBodyArgument
			if not _enumerable:
				_pattern.Add(SpliceExpression([| $_arg = $(ReferenceExpression(type.FullName))() |]))
			else:
				_prologue = GetPrologue(type)

		private def AppendPrimitive(type as IType):
			AssertEnumerable(type.FullName) if IsBodyArgument

			if type == TypeSystemServices.StringType:
				AppendString()
			elif type == TypeSystemServices.BoolType:
				AppendBool()
			elif type == TypeSystemServices.LongType or type == TypeSystemServices.ULongType:
				AppendLong()
			elif TypeSystemServices.IsIntegerNumber(type):
				AppendInt()
			elif type == TypeSystemServices.SingleType:
				AppendSingle()
			elif type == TypeSystemServices.DoubleType:
				AppendDouble()
			elif type == TypeSystemServices.RegexType:
				AppendRegex()
			elif type == TypeSystemServices.CharType:
				AppendChar()
			elif type == TypeSystemServices.TimeSpanType:
				AppendTimeSpan()
			else:
				raise "Unknown primitive `${type}`"

		private def AppendString():
			if not _enumerable:
				_pattern.Add(SpliceExpression([| Boo.Lang.Compiler.Ast.StringLiteralExpression(Value: $_arg) |]))
			else:
				_prologue = GetPrologue[of Boo.Lang.Compiler.Ast.StringLiteralExpression, string]()

		private def AppendBool():
			if not _enumerable:
				_pattern.Add(SpliceExpression([| Boo.Lang.Compiler.Ast.BoolLiteralExpression(Value: $_arg) |]))
			else:
				_prologue = GetPrologue[of Boo.Lang.Compiler.Ast.BoolLiteralExpression, bool]()

		private def AppendLong():
			if not _enumerable:
				_pattern.Add(SpliceExpression([| Boo.Lang.Compiler.Ast.IntegerLiteralExpression(Value: $_arg, IsLong: true) |]))
			else:
				_prologue = GetPrologue[of Boo.Lang.Compiler.Ast.IntegerLiteralExpression, long]()

		private def AppendInt():
			if not _enumerable:
				_pattern.Add(SpliceExpression([| Boo.Lang.Compiler.Ast.IntegerLiteralExpression(Value: $_arg, IsLong: false) |]))
			else:
				_prologue = GetPrologue[of Boo.Lang.Compiler.Ast.IntegerLiteralExpression, int]()

		private def AppendSingle():
			if not _enumerable:
				_pattern.Add(SpliceExpression([| Boo.Lang.Compiler.Ast.DoubleLiteralExpression(Value: $_arg, IsSingle: true) |]))
			else:
				_prologue = GetPrologue[of Boo.Lang.Compiler.Ast.DoubleLiteralExpression, single]()

		private def AppendDouble():
			if not _enumerable:
				_pattern.Add(SpliceExpression([| Boo.Lang.Compiler.Ast.DoubleLiteralExpression(Value: $_arg, IsSingle: false) |]))
			else:
				_prologue = GetPrologue[of Boo.Lang.Compiler.Ast.DoubleLiteralExpression, double]()

		private def AppendRegex():
			if not _enumerable:
				_pattern.Add(SpliceExpression([| Boo.Lang.Compiler.Ast.RELiteralExpression(Regex: $_arg) |]))
			else:
				_prologue = GetPrologue[of Boo.Lang.Compiler.Ast.RELiteralExpression, regex]()

		private def AppendChar():
			if not _enumerable:
				_pattern.Add(SpliceExpression([| Boo.Lang.Compiler.Ast.CharLiteralExpression(Value: $_arg) |]))
			else:
				_prologue = GetPrologue[of Boo.Lang.Compiler.Ast.CharLiteralExpression, char]()

		private def AppendTimeSpan():
			if not _enumerable:
				_pattern.Add(SpliceExpression([| Boo.Lang.Compiler.Ast.TimeSpanLiteralExpression(Value: $_arg) |]))
			else:
				_prologue = GetPrologue[of Boo.Lang.Compiler.Ast.TimeSpanLiteralExpression, timespan]()

		private def GetPrologueCollection(collectionType as string):
			typeRef = SimpleTypeReference(collectionType)
			if IsBodyArgument:
				return [| $(MacroField).Body.Statements.Cast[of $(typeRef)]() |]
			else:
				return [| $(MacroField).Arguments.Cast[of $(typeRef)]($_argIndex) |]

		private def GetPrologue(type as IType):
			p = Block()
			p.Add([| $_arg = $(GetPrologueCollection(type.FullName)) |])
			return p

		private def GetPrologue[of TNode, TValue]():
			temp = CreateTemp()
			p = Block()
			p.Add([| $temp = $(GetPrologueCollection(typeof(TNode).FullName)) |])
			p.Add([| $_arg = Boo.Lang.Compiler.Ast.AstUtil.GetValues[of $TNode, $TValue]($temp) |])
			return p

		private def CreateTemp():
			return ReferenceExpression(CompilerContext.Current.GetUniqueName())

		private def AssertEnumerable(typeName as string):
			if not _enumerable:
				typeName = typeof(Boo.Lang.Compiler.Ast.Statement).FullName unless typeName
				raise "`${_arg.Name}` argument must be of enumerable type. Did you mean `${_arg.Name} as ${typeName}*`?"

	class SubMacroExtractor(DepthFirstTransformer):

		override def OnTypeMemberStatement(node as TypeMemberStatement):
			tm = node.TypeMember
			cd = node.GetAncestor[of ClassDefinition]()
			RemoveCurrentNode()
			cd.Members.Add(tm)

#region license
// Copyright (c) 2003, 2004, 2005 Rodrigo B. de Oliveira (rbo@acm.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//     * Neither the name of Rodrigo B. de Oliveira nor the names of its
//     contributors may be used to endorse or promote products derived from this
//     software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion

namespace Boo.Lang.Extensions

import Boo.Lang.Environments
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.TypeSystem
import Boo.Lang.Compiler.TypeSystem.Internal
import Boo.Lang.Compiler.TypeSystem.Reflection
import Boo.Lang.Compiler.TypeSystem.Services

class MacroMacro(LexicalInfoPreservingGeneratorMacro):

	private static final Usage = "Usage: `macro [<parent.>+]<name>[(arg0,...)]`"

	_macro as MacroStatement
	_name as string

	_apb as ArgumentsPatternBuilder
	_usingPatternMatching = false

	ArgumentsPattern:
		get:
			return _apb.Pattern if _apb

	ArgumentsPrologue:
		get:
			return _apb.Prologue if _apb

	override protected def ExpandGeneratorImpl(macro as MacroStatement):
		raise Usage if len(macro.Arguments) != 1

		_macro = macro

		arg = macro.Arguments[0]
		mie = arg as MethodInvocationExpression
		if mie:
			_apb = ArgumentsPatternBuilder(mie.Arguments)
			arg = mie.Target

		if arg.NodeType == NodeType.ReferenceExpression:
			_name = cast(ReferenceExpression, arg).Name
			yield CreateMacroType()
			yield [| import Boo.Lang.PatternMatching |] if _usingPatternMatching
		elif arg.NodeType == NodeType.MemberReferenceExpression:
			if macro.GetAncestor[of MacroStatement]() is not null:
				raise "Nested macro extension cannot be itself a nested macro"
			_name = cast(MemberReferenceExpression, arg).Name
			CreateMacroExtensionType(cast(MemberReferenceExpression, arg))
		else:
			raise Usage

	private static def BuildMacroTypeName(name as string) as string:
		return char.ToUpper(name[0]) + name[1:] + "Macro"

	private static def BuildMacroTypeName(name as Expression) as string:
		return BuildMacroTypeName(name, false)

	private static def BuildMacroTypeName(name as Expression, external as bool) as string:
		sb = System.Text.StringBuilder()
		for part in UnpackReferences(name):
			sb.Append(char('.')) if sb.Length and not external
			sb.Append(char('$')) if external
			sb.Append(BuildMacroTypeName(part))
		return sb.ToString()

	private static def UnpackReferences(refs as Expression) as string*:
		if refs.NodeType == NodeType.ReferenceExpression:
			re = cast(ReferenceExpression, refs)
			raise "Extending macro `macro` is not supported" if re.Name == "macro"
			yield re.Name
		elif refs.NodeType == NodeType.MemberReferenceExpression:
			mre = cast(MemberReferenceExpression, refs)
			for name in UnpackReferences(mre.Target):
				yield name
			yield mre.Name
		else:
			raise "Invalid node type: ${refs.NodeType}"

	private static def GetParentMacroNames(macro as MacroStatement) as string*:
		for parent in macro.GetAncestors[of MacroStatement]():
			continue if parent.Name != 'macro'

			arg = parent.Arguments[0]
			if arg.NodeType == NodeType.MethodInvocationExpression:
				arg = cast(MethodInvocationExpression, arg).Target

			if arg.NodeType == NodeType.ReferenceExpression:
				yield cast(ReferenceExpression, arg).Name

			elif arg.NodeType == NodeType.MemberReferenceExpression:
				for name in UnpackReferences(cast(MemberReferenceExpression, arg)):
					yield name

	private def CreateMacroExtensionType(mre as MemberReferenceExpression):
		#resolve the macro type we want to expand
		parentTypeName = BuildMacroTypeName(mre.Target)
		parent = NameResolutionService.ResolveQualifiedName(parentTypeName) as IType
		if parent is null: #oh, this is a external extension maybe?
			parentTypeName = BuildMacroTypeName(mre.Target, true)
			parent = NameResolutionService.ResolveQualifiedName(parentTypeName) as IType
		if parent is null:
			raise "No macro `${mre.Target.ToString()}` has been found to extend"

		elif parent isa InternalClass:
			#create macro type and add it as usual into its parent macro type
			type = CreateMacroType(UnpackReferences(mre.Target))
			cast(InternalClass, parent).TypeDefinition.Members.Add(type)

		elif parent isa ExternalType:
			#create macro type with parent(s)-based prefix then add an extension method for redirection
			type = CreateMacroType(BuildMacroTypeName(mre, true), UnpackReferences(mre.Target))
			module = _macro.GetAncestor[of Module]()
			module.Members.Add(type)
			method = CreateMacroExtensionProxy(parent, type.Name)
			module.Members.Add(method)

		else:
			raise "Cannot bind a nested macro extension to entity: ${parent}"

	private def CreateMacroExtensionProxy(parent as IType, extension as string):
		return [|
			[Boo.Lang.ExtensionAttribute]
			[System.Runtime.CompilerServices.CompilerGeneratedAttribute]
			static def $(_name)(parent as $(parent.FullName), context as Boo.Lang.Compiler.CompilerContext) as $(extension):
				return $(ReferenceExpression(extension))(context)
		|]

	private def CreateMacroType() as ClassDefinition:
		return CreateMacroType(BuildMacroTypeName(_name), GetParentMacroNames(_macro))

	private def CreateMacroType(parents as string*) as ClassDefinition:
		return CreateMacroType(BuildMacroTypeName(_name), parents)

	private def CreateMacroType(typeName as string, parents as string*) as ClassDefinition:
		if YieldFinder(_macro).Found:
			macroType = CreateGeneratorMacroType(typeName)
		else:
			macroType = CreateOldStyleMacroType(typeName)

		#add parent macro(s) accessor(s)
		for parent in parents:
			if not macroType.Members[parent]:
				for accessor in CreateParentMacroAccessor(parent):
					macroType.Members.Add(accessor)
		return macroType

	#BOO-1077 style
	private def CreateGeneratorMacroType(typeName as string) as ClassDefinition:
		compatErrorMsg = "Boo installed version is older than the new macro syntax '${_name}' is using. Read BOO-1077 for more info."
		arg = ReferenceExpression(_name)
		return [|
				public final class $(typeName) (Boo.Lang.Compiler.LexicalInfoPreservingGeneratorMacro):
					[System.Runtime.CompilerServices.CompilerGeneratedAttribute]
					private __macro as Boo.Lang.Compiler.Ast.MacroStatement
					public def constructor():
						super()
					public def constructor(context as Boo.Lang.Compiler.CompilerContext):
						raise System.ArgumentNullException("context") if not context
						super(context)
					override protected def ExpandGeneratorImpl($_name as Boo.Lang.Compiler.Ast.MacroStatement) as Boo.Lang.Compiler.Ast.Node*:
						raise System.ArgumentNullException($_name) if not $arg
						self.__macro = $arg
						$(ExpandBody())

					[System.Runtime.CompilerServices.CompilerGeneratedAttribute]
					override protected def ExpandImpl($_name as Boo.Lang.Compiler.Ast.MacroStatement) as Boo.Lang.Compiler.Ast.Statement:
						raise System.NotImplementedException($compatErrorMsg)
			|]

	private def CreateOldStyleMacroType(typeName as string) as ClassDefinition:
		arg = ReferenceExpression(_name)
		return [|
				public final class $(typeName) (Boo.Lang.Compiler.LexicalInfoPreservingMacro):
					[System.Runtime.CompilerServices.CompilerGeneratedAttribute]
					private __macro as Boo.Lang.Compiler.Ast.MacroStatement
					public def constructor():
						super()
					public def constructor(context as Boo.Lang.Compiler.CompilerContext):
						raise System.ArgumentNullException("context") if not context
						super(context)
					override protected def ExpandImpl($_name as Boo.Lang.Compiler.Ast.MacroStatement) as Boo.Lang.Compiler.Ast.Statement:
						raise System.ArgumentNullException($_name) if not $arg
						self.__macro = $arg
						$(ExpandBody())
			|]

	private def ExpandBody():
		body = _macro.Body

		if ArgumentsPattern:
			if 0 == ArgumentsPattern.Count and not ArgumentsPrologue:
				body = [|
					if __macro.Arguments.Count == 0:
						$body
					else:
						$(CreateRaiseMacroArgumentsMatchError())
				|].ToBlock()
			elif ArgumentsPattern.Count > 0:
				case = CaseStatement()
				case.Pattern = QuasiquoteExpression(pattern = MacroStatement(_name))
				pattern.Arguments = ArgumentsPattern
				if ArgumentsPrologue:
					case.Body = [|
						$ArgumentsPrologue
						$body
					|].ToBlock()
				else:
					case.Body = body
				otherwise = OtherwiseStatement() #TODO: macro overload without arguments def
				otherwise.Body = CreateRaiseMacroArgumentsMatchError().ToBlock()
				body = [|
					$case
					$otherwise
				|]

		if ContainsCase(body):
			return ExpandWithPatternMatching(_name, body)

		if ArgumentsPrologue:
			return [|
				$ArgumentsPrologue
				$body
			|].ToBlock()
		else:
			return body

	def CreateRaiseMacroArgumentsMatchError() as RaiseStatement:
		errorMsg = "`${$(_name)}` macro invocation argument(s) did not match definition: `${$(_macro.Arguments[0].ToString())}`"
		return [| raise $errorMsg |]

	#region PatternMatching
	internal class CustomBlockStatement(CustomStatement):
		public Body as Block

	internal class CaseStatement(CustomBlockStatement):
		public Pattern as Expression

	internal class OtherwiseStatement(CustomBlockStatement):
		pass

	class CaseMacro(LexicalInfoPreservingGeneratorMacro):
		override protected def ExpandGeneratorImpl(case as MacroStatement):
			if len(case.Arguments) != 1:
				raise "Usage: case <pattern>"
			pattern, = case.Arguments
			yield CaseStatement(Pattern: pattern, Body: case.Body)

	class OtherwiseMacro(LexicalInfoPreservingGeneratorMacro):
		override protected def ExpandGeneratorImpl(case as MacroStatement):
			if len(case.Arguments) != 0:
				raise "Usage: otherwise: <block>"
			yield OtherwiseStatement(Body: case.Body)

	private static def ContainsCase(body as Block):
		for stmt in body.Statements:
			return true if stmt isa CaseStatement

	private def ExpandWithPatternMatching(name as string, body as Block):

		_usingPatternMatching = true

		matchBlock = [|
			match $(ReferenceExpression(name)):
				pass
		|]
		statementsEnumerator = body.Statements.GetEnumerator()
		while statementsEnumerator.MoveNext():
			stmt as Statement = statementsEnumerator.Current
			if stmt isa CaseStatement:
				case as CaseStatement = stmt
				caseBlock = [|
					case $(case.Pattern):
						$(case.Body)
				|]
				matchBlock.Body.Add(caseBlock)
			elif stmt isa OtherwiseStatement:
				otherwise as OtherwiseStatement = stmt
				otherwiseBlock = [|
					otherwise:
						$(otherwise.Body)
				|]
				matchBlock.Body.Add(otherwiseBlock)

				// otherwise marks the end of the match block
				resultingBlock = Block()
				resultingBlock.Add(matchBlock)
				for remaining as Statement in statementsEnumerator:
					resultingBlock.Add(remaining)
				return resultingBlock
			else:
				// statements after a sequence of 'case <pattern>' macros
				// also mark the end of a match block
				resultingBlock = Block()
				resultingBlock.Add(matchBlock)
				resultingBlock.Add(stmt)
				for remaining as Statement in statementsEnumerator:
					resultingBlock.Add(remaining)
				return resultingBlock
		return matchBlock
	#endregion

	private static def CreateParentMacroAccessor(name as string):
		cacheField = [|
			[System.Runtime.CompilerServices.CompilerGeneratedAttribute]
			private $("$" + name) as Boo.Lang.Compiler.Ast.MacroStatement
		|]
		yield cacheField

		cacheFieldRef = ReferenceExpression(cacheField.Name)
		prop = [|
			[System.Runtime.CompilerServices.CompilerGeneratedAttribute]
			private $name:
				get:
					$cacheFieldRef = __macro.GetParentMacroByName($name) unless $cacheFieldRef
					return $cacheFieldRef
		|]
		prop.IsSynthetic = true #avoid BCW0014 if not used
		yield prop

	private final class YieldFinder(DepthFirstVisitor):
		private _found = false

		def constructor(macro as MacroStatement):
			macro.Body.Accept(self)

		Found:
			get: return _found

		override def OnMethod(node as Method):
			pass

		override def OnCustomStatement(node as CustomStatement):
			customBlock = node as CustomBlockStatement
			if customBlock is null:
				super(node)
			else:
				customBlock.Body.Accept(self)

		override def OnYieldStatement(node as YieldStatement):
			_found = true

		override def EnterMacroStatement(node as MacroStatement) as bool:
			return false

	private final class ArgumentsPatternBuilder():
		static final MacroField = ReferenceExpression("__macro")

		_input as ExpressionCollection
		_pattern as ExpressionCollection
		_prologue as Block
		_tss = EnvironmentProvision[of TypeSystemServices]()
		_nrs = EnvironmentProvision[of NameResolutionService]()
		_arg as ReferenceExpression
		_argIndex = 0
		_enumerable = false

		def constructor(input as ExpressionCollection):
			_input = input

		Pattern:
			get:
				Build() if not _pattern
				return _pattern

		Prologue:
			get:
				Build() if not _pattern
				return _prologue

		def Build():
			_pattern = ExpressionCollection()
			for arg in _input:
				_enumerable = false
				Append(arg) #FIXME: run-time (duck) dispatch here makes mono cry
				++_argIndex

		private TypeSystemServices:
			get: return _tss.Instance

		private NameResolutionService:
			get: return _nrs.Instance

		private IsBodyArgument:
			get: return _arg.Name == "body"

		private IsLastArgument:
			get: return _argIndex == _input.Count-1

		private def Append(e as Expression):
			if e.NodeType == NodeType.ReferenceExpression:
				_arg = cast(ReferenceExpression, e)
				Append(null as IType)
			elif e.NodeType == NodeType.TryCastExpression:
				Append(e as TryCastExpression)
			else:
				raise "Invalid macro argument declaration: `${e}`"

		private def Append(e as TryCastExpression):
			re = e.Target as ReferenceExpression
			raise "Invalid macro argument name: `${e.Target}`" unless re

			NameResolutionService.ResolveTypeReference(e.Type)
			type = TypeSystemServices.GetType(e.Type)
			_arg = cast(ReferenceExpression, re)
			Append(type)

		private def Append(type as IType):
			if IsBodyArgument and not IsLastArgument:
				raise "`${_arg.Name}` argument must be the last argument"

			if type is null:
				AppendDefaultNode()
			elif IsAstNode(type):
				AppendNode(type)
			elif TypeSystemServices.IsLiteralPrimitive(type):
				AppendPrimitive(type)
			elif not _enumerable and (etype = TypeSystemServices.GetEnumeratorItemType(type)) is not null:
				if not IsLastArgument:
					raise "Enumerable or array type argument `${_arg.Name}` must be the last argument"
				if not IsBodyArgument and _pattern.Count > 0:
					_pattern.Add(SpliceExpression([| *_ |]))
				_enumerable = true
				Append(etype)
			else:
				raise "Unsupported type `${type.FullName}` for argument `${_arg.Name}`, a macro argument type must be a literal-able primitive or an AST node"

		private def IsAstNode(type as IType):
			return type.IsSubclassOf(TypeSystemServices.Map(Boo.Lang.Compiler.Ast.Node))

		private def AppendDefaultNode():
			if IsBodyArgument:
				_prologue = Block()
				_prologue.Add([| $_arg = $(MacroField).Body.Statements |])
			else:
				_pattern.Add(SpliceExpression([| $_arg = Boo.Lang.Compiler.Ast.Expression() |]))

		private def AppendNode(type as IType):
			AssertEnumerable(type.FullName) if IsBodyArgument
			if not _enumerable:
				_pattern.Add(SpliceExpression([| $_arg = $(ReferenceExpression(type.FullName))() |]))
			else:
				_prologue = GetPrologue(type)

		private def AppendPrimitive(type as IType):
			AssertEnumerable(type.FullName) if IsBodyArgument

			if type == TypeSystemServices.StringType:
				AppendString()
			elif type == TypeSystemServices.BoolType:
				AppendBool()
			elif type == TypeSystemServices.LongType or type == TypeSystemServices.ULongType:
				AppendLong()
			elif TypeSystemServices.IsIntegerNumber(type):
				AppendInt()
			elif type == TypeSystemServices.SingleType:
				AppendSingle()
			elif type == TypeSystemServices.DoubleType:
				AppendDouble()
			elif type == TypeSystemServices.RegexType:
				AppendRegex()
			elif type == TypeSystemServices.CharType:
				AppendChar()
			elif type == TypeSystemServices.TimeSpanType:
				AppendTimeSpan()
			else:
				raise "Unknown primitive `${type}`"

		private def AppendString():
			if not _enumerable:
				_pattern.Add(SpliceExpression([| Boo.Lang.Compiler.Ast.StringLiteralExpression(Value: $_arg) |]))
			else:
				_prologue = GetPrologue[of Boo.Lang.Compiler.Ast.StringLiteralExpression, string]()

		private def AppendBool():
			if not _enumerable:
				_pattern.Add(SpliceExpression([| Boo.Lang.Compiler.Ast.BoolLiteralExpression(Value: $_arg) |]))
			else:
				_prologue = GetPrologue[of Boo.Lang.Compiler.Ast.BoolLiteralExpression, bool]()

		private def AppendLong():
			if not _enumerable:
				_pattern.Add(SpliceExpression([| Boo.Lang.Compiler.Ast.IntegerLiteralExpression(Value: $_arg, IsLong: true) |]))
			else:
				_prologue = GetPrologue[of Boo.Lang.Compiler.Ast.IntegerLiteralExpression, long]()

		private def AppendInt():
			if not _enumerable:
				_pattern.Add(SpliceExpression([| Boo.Lang.Compiler.Ast.IntegerLiteralExpression(Value: $_arg, IsLong: false) |]))
			else:
				_prologue = GetPrologue[of Boo.Lang.Compiler.Ast.IntegerLiteralExpression, int]()

		private def AppendSingle():
			if not _enumerable:
				_pattern.Add(SpliceExpression([| Boo.Lang.Compiler.Ast.DoubleLiteralExpression(Value: $_arg, IsSingle: true) |]))
			else:
				_prologue = GetPrologue[of Boo.Lang.Compiler.Ast.DoubleLiteralExpression, single]()

		private def AppendDouble():
			if not _enumerable:
				_pattern.Add(SpliceExpression([| Boo.Lang.Compiler.Ast.DoubleLiteralExpression(Value: $_arg, IsSingle: false) |]))
			else:
				_prologue = GetPrologue[of Boo.Lang.Compiler.Ast.DoubleLiteralExpression, double]()

		private def AppendRegex():
			if not _enumerable:
				_pattern.Add(SpliceExpression([| Boo.Lang.Compiler.Ast.RELiteralExpression(Regex: $_arg) |]))
			else:
				_prologue = GetPrologue[of Boo.Lang.Compiler.Ast.RELiteralExpression, regex]()

		private def AppendChar():
			if not _enumerable:
				_pattern.Add(SpliceExpression([| Boo.Lang.Compiler.Ast.CharLiteralExpression(Value: $_arg) |]))
			else:
				_prologue = GetPrologue[of Boo.Lang.Compiler.Ast.CharLiteralExpression, char]()

		private def AppendTimeSpan():
			if not _enumerable:
				_pattern.Add(SpliceExpression([| Boo.Lang.Compiler.Ast.TimeSpanLiteralExpression(Value: $_arg) |]))
			else:
				_prologue = GetPrologue[of Boo.Lang.Compiler.Ast.TimeSpanLiteralExpression, timespan]()

		private def GetPrologueCollection(collectionType as string):
			typeRef = SimpleTypeReference(collectionType)
			if IsBodyArgument:
				return [| $(MacroField).Body.Statements.Cast[of $(typeRef)]() |]
			else:
				return [| $(MacroField).Arguments.Cast[of $(typeRef)]($_argIndex) |]

		private def GetPrologue(type as IType):
			p = Block()
			p.Add([| $_arg = $(GetPrologueCollection(type.FullName)) |])
			return p

		private def GetPrologue[of TNode, TValue]():
			temp = CreateTemp()
			p = Block()
			p.Add([| $temp = $(GetPrologueCollection(typeof(TNode).FullName)) |])
			p.Add([| $_arg = Boo.Lang.Compiler.Ast.AstUtil.GetValues[of $TNode, $TValue]($temp) |])
			return p

		private def CreateTemp():
			return ReferenceExpression(CompilerContext.Current.GetUniqueName())

		private def AssertEnumerable(typeName as string):
			if not _enumerable:
				typeName = typeof(Boo.Lang.Compiler.Ast.Statement).FullName unless typeName
				raise "`${_arg.Name}` argument must be of enumerable type. Did you mean `${_arg.Name} as ${typeName}*`?"


Avoid throwing basic exception in macro-generated code.

git-svn-id: fcce7d6ce0ac15bf24f68871b5145d46fc068279@3061 2c1201b4-01cd-e047-a400-b836ae1fbc61

#region license
// Copyright (c) 2003, 2004, 2005 Rodrigo B. de Oliveira (rbo@acm.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//     * Neither the name of Rodrigo B. de Oliveira nor the names of its
//     contributors may be used to endorse or promote products derived from this
//     software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion

namespace Boo.Lang.Extensions

import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast

class MacroMacro(AbstractAstMacro):

	override def Expand(macro as MacroStatement) as Statement:
		if len(macro.Arguments) != 1 or macro.Arguments[0].NodeType != NodeType.ReferenceExpression:
			raise System.ArgumentException("Usage: macro <reference>", "reference")
		klass = CreateMacroType(macro)
		klass.LexicalInfo = macro.LexicalInfo
		#TODO: create macro as a nested class of the current type
		#      if not at module-level ? (=> macro namespaces)
		EnclosingModule(macro).Members.Add(klass)
		return null

	private def PascalCase(name as string) as string:
		return char.ToUpper(name[0]) + name[1:]

	private def CreateMacroType(macro as MacroStatement) as ClassDefinition:
		name = (macro.Arguments[0] as ReferenceExpression).Name
		newStyle = YieldFinder(macro).Found
		return CreateNewStyleMacroType(name, macro) if newStyle
		return CreateOldStyleMacroType(name, macro)

	#BOO-1077 style
	private def CreateNewStyleMacroType(name as string, macro as MacroStatement) as ClassDefinition:
		return [|
				class $(PascalCase(name) + "Macro") (Boo.Lang.Compiler.LexicalInfoPreservingGeneratorMacro):
					override protected def ExpandGeneratorImpl($name as Boo.Lang.Compiler.Ast.MacroStatement) as Boo.Lang.Compiler.Ast.Node*:
						$(macro.Block)
					override protected def ExpandImpl($name as Boo.Lang.Compiler.Ast.MacroStatement) as Boo.Lang.Compiler.Ast.Statement:
						raise System.NotImplementedException("Boo installed version is older than the new macro syntax '${$(name)}' uses. Read BOO-1077 for more info.")
			|]

	private def CreateOldStyleMacroType(name as string, macro as MacroStatement) as ClassDefinition:
		return [|
				class $(PascalCase(name) + "Macro") (Boo.Lang.Compiler.LexicalInfoPreservingMacro):
					override protected def ExpandImpl($name as Boo.Lang.Compiler.Ast.MacroStatement) as Boo.Lang.Compiler.Ast.Statement:
						$(macro.Block)
			|]

	private def EnclosingModule(macro as Node) as Module:
		return macro.GetAncestor(NodeType.Module)

	private class YieldFinder(DepthFirstVisitor):
		Found as bool:
			get:
				return _found
		_found = false

		def constructor(macro as MacroStatement):
			super.OnMacroStatement(macro)

		override def OnYieldStatement(node as YieldStatement):
			_found = true if _macroDepth == 1

		override def EnterMacroStatement(node as MacroStatement) as bool:
			_macroDepth++
			return true

		override def LeaveMacroStatement(node as MacroStatement):
			_macroDepth--

		_macroDepth = 0

#region license
// Copyright (c) 2003, 2004, 2005 Rodrigo B. de Oliveira (rbo@acm.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//     * Neither the name of Rodrigo B. de Oliveira nor the names of its
//     contributors may be used to endorse or promote products derived from this
//     software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion

namespace Boo.Lang.Extensions

import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast

class MacroMacro(AbstractAstMacro):

	override def Expand(macro as MacroStatement) as Statement:
		if len(macro.Arguments) != 1 or macro.Arguments[0].NodeType != NodeType.ReferenceExpression:
			raise "macro <reference>"
		klass = CreateMacroType(macro)
		klass.LexicalInfo = macro.LexicalInfo
		#TODO: create macro as a nested class of the current type
		#      if not at module-level ? (=> macro namespaces)
		EnclosingModule(macro).Members.Add(klass)
		return null

	private def PascalCase(name as string) as string:
		return char.ToUpper(name[0]) + name[1:]

	private def CreateMacroType(macro as MacroStatement) as ClassDefinition:
		name = (macro.Arguments[0] as ReferenceExpression).Name
		newStyle = YieldFinder(macro).Found
		return CreateNewStyleMacroType(name, macro) if newStyle
		return CreateOldStyleMacroType(name, macro)

	#BOO-1077 style
	private def CreateNewStyleMacroType(name as string, macro as MacroStatement) as ClassDefinition:
		return [|
				class $(PascalCase(name) + "Macro") (Boo.Lang.Compiler.LexicalInfoPreservingGeneratorMacro):
					override protected def ExpandGeneratorImpl($name as Boo.Lang.Compiler.Ast.MacroStatement) as Boo.Lang.Compiler.Ast.Node*:
						$(macro.Block)
					override protected def ExpandImpl($name as Boo.Lang.Compiler.Ast.MacroStatement) as Boo.Lang.Compiler.Ast.Statement:
						raise "Boo installed version is older than the new macro syntax '${$(name)}' uses. Read BOO-1077 for more info."
			|]

	private def CreateOldStyleMacroType(name as string, macro as MacroStatement) as ClassDefinition:
		return [|
				class $(PascalCase(name) + "Macro") (Boo.Lang.Compiler.LexicalInfoPreservingMacro):
					override protected def ExpandImpl($name as Boo.Lang.Compiler.Ast.MacroStatement) as Boo.Lang.Compiler.Ast.Statement:
						$(macro.Block)
			|]

	private def EnclosingModule(macro as Node) as Module:
		return macro.GetAncestor(NodeType.Module)

	private class YieldFinder(DepthFirstVisitor):
		Found as bool:
			get:
				return _found
		_found = false

		def constructor(macro as MacroStatement):
			super.OnMacroStatement(macro)

		override def OnYieldStatement(node as YieldStatement):
			_found = true if _macroDepth == 1

		override def EnterMacroStatement(node as MacroStatement) as bool:
			_macroDepth++
			return true

		override def LeaveMacroStatement(node as MacroStatement):
			_macroDepth--

		_macroDepth = 0