Use unique name provider to generate namespace aliases

namespace BooJs.Compiler.Steps

from Boo.Lang.Compiler.Ast import *
from Boo.Lang.Compiler.Steps import AbstractTransformerCompilerStep
from Boo.Lang.Compiler.TypeSystem import EntityType
from Boo.Lang.Compiler.TypeSystem.Reflection import ExternalType

from BooJs.Compiler.Utils import *

class ProcessImports(AbstractTransformerCompilerStep):
"""
    Process imports

    Wraps the module in an AMD style loader, placing any referenced namespaces as imports
"""
    _mappings = {}
    _asmrefs = {}

    protected def FindNamespace(fqn as string) as string:
    """ Given a fully qualified name it will check backwards until it
        finds the containing namespace
    """
        parts = fqn.Split(char('.'))
        while len(parts):
            type = NameResolutionService.ResolveQualifiedName(join(parts, '.'))
            if type.EntityType in (EntityType.Namespace,):
                return type.FullName
            parts = parts[:-1]
        return null

    protected def MapNamespace(ns as string, alias as string, asmref as ReferenceExpression):
        actualns = FindNamespace(ns)
        if actualns != ns:
            alias = null

        # Namespace already mapped, nothing to do
        if actualns in _mappings:
            return

        # Generate an alias name if none given
        if not alias:
            alias = Context.GetUniqueName('NS')

        # Map the namespace
        _mappings[actualns] = alias
        if asmref:
            _asmrefs[actualns] = asmref.Name

    def OnModule(node as Module):
        # Map module namespace to the exports special symbol
        ns = (node.Namespace.Name if node.Namespace else '')
        _mappings[ns] = 'exports'

        # Makes sure we visit the imports first
        Visit node.Imports
        Visit node.Members
        Visit node.Globals

        # Annotate the module with the reported namespace mappings
        # TODO: Avoid the annotations by converting this step into a simple visitor
        #       used from the printer
        node.Annotate('nsmapping', _mappings.Clone())
        node.Annotate('nsasmrefs', _asmrefs.Clone())

        # Reset for next module
        _mappings.Clear()
        _asmrefs.Clear()

    def OnImport(node as Import):
        # Detect imports of the own module namespace
        module = node.GetAncestor[of Module]()
        if module and module.Namespace and module.Namespace.Name == node.Namespace:
            return

        if mie = node.Expression as MethodInvocationExpression:
            for arg as ReferenceExpression in mie.Arguments:
                fqn = node.Namespace + '.' + arg.Name
                continue if isExtern(fqn)

                if node.Alias:
                    MapNamespace(node.Namespace + '.' + arg.Name, null, node.AssemblyReference)
                else:
                    MapNamespace(node.Namespace + '.' + arg.Name, arg.Name, node.AssemblyReference)

        elif not isExtern(node.Namespace):
            if node.Alias:
                MapNamespace(node.Namespace, node.Alias.Name, node.AssemblyReference)
            else:
                MapNamespace(node.Namespace, null, node.AssemblyReference)

    def OnReferenceExpression(node as ReferenceExpression):
        # Only process external types
        # NOTE: Shouldn't we check here against Entity?
        if etype = node.ExpressionType as ExternalType:
            name = etype.FullName

        # Handle aliases with multiple references: import Foo(Bar, Baz) as MyFoo
        elif esimple = node.Entity as Boo.Lang.Compiler.TypeSystem.Core.SimpleNamespace:
            if esimple.FullName:
                name = esimple.FullName
            else:
                for member in esimple.GetMembers():
                    if member isa ExternalType:
                        name = (member as ExternalType).ActualType.Namespace
                        break;

        if not name:
            return

        # Find a mapping containing the full name of the entity
        parts = name.Split(char('.'))
        rhs = []
        while len(parts):
            name = join(parts, '.')
            if name in _mappings:
                rhs.Insert(0, _mappings[name])
                node.Name = join(rhs, '.')
                break

            # Skip module classes
            if not IsModule(name):
                rhs.Add(parts[-1])

            parts = parts[:-1]

    protected def IsModule(fqn as string) as bool:
    """ Checks if the given fully qualified name actually resolves into a module
    """
        try:
            type = NameResolutionService.ResolveQualifiedName(fqn) as ExternalType
            return type and type.IsClass and type.IsFinal and type.Name.EndsWith('Module')
        except:
            return false

namespace BooJs.Compiler.Steps

from Boo.Lang.Compiler.Ast import *
from Boo.Lang.Compiler.Steps import AbstractTransformerCompilerStep
from Boo.Lang.Compiler.TypeSystem import EntityType
from Boo.Lang.Compiler.TypeSystem.Reflection import ExternalType

from BooJs.Compiler.Utils import *

class ProcessImports(AbstractTransformerCompilerStep):
"""
    Process imports

    Wraps the module in an AMD style loader, placing any referenced namespaces as imports
"""
    _mappings = {}
    _asmrefs = {}
    _nsidx = 0

    protected def FindNamespace(fqn as string) as string:
    """ Given a fully qualified name it will check backwards until it
        finds the containing namespace
    """
        parts = fqn.Split(char('.'))
        while len(parts):
            type = NameResolutionService.ResolveQualifiedName(join(parts, '.'))
            if type.EntityType in (EntityType.Namespace,):
                return type.FullName
            parts = parts[:-1]
        return null

    protected def MapNamespace(ns as string, alias as string, asmref as ReferenceExpression):
        actualns = FindNamespace(ns)
        if actualns != ns:
            alias = null

        # Namespace already mapped, nothing to do
        if actualns in _mappings:
            return

        # Generate an alias name if none given
        if not alias:
            alias = 'NS' + _nsidx++

        # Map the namespace
        _mappings[actualns] = alias
        if asmref:
            _asmrefs[actualns] = asmref.Name

    def OnModule(node as Module):
        # Map module namespace to the exports special symbol
        ns = (node.Namespace.Name if node.Namespace else '')
        _mappings[ns] = 'exports'

        # Makes sure we visit the imports first
        Visit node.Imports
        Visit node.Members
        Visit node.Globals

        # Annotate the module with the reported namespace mappings
        # TODO: Avoid the annotations by converting this step into a simple visitor
        #       used from the printer
        node.Annotate('nsmapping', _mappings.Clone())
        node.Annotate('nsasmrefs', _asmrefs.Clone())

        # Reset for next module
        _mappings.Clear()
        _asmrefs.Clear()
        _nsidx = 0

    def OnImport(node as Import):
        # Detect imports of the own module namespace
        module = node.GetAncestor[of Module]()
        if module and module.Namespace and module.Namespace.Name == node.Namespace:
            return

        if mie = node.Expression as MethodInvocationExpression:
            for arg as ReferenceExpression in mie.Arguments:
                fqn = node.Namespace + '.' + arg.Name
                continue if isExtern(fqn)

                if node.Alias:
                    MapNamespace(node.Namespace + '.' + arg.Name, null, node.AssemblyReference)
                else:
                    MapNamespace(node.Namespace + '.' + arg.Name, arg.Name, node.AssemblyReference)

        elif not isExtern(node.Namespace):
            if node.Alias:
                MapNamespace(node.Namespace, node.Alias.Name, node.AssemblyReference)
            else:
                MapNamespace(node.Namespace, null, node.AssemblyReference)

    def OnReferenceExpression(node as ReferenceExpression):
        # Only process external types
        # NOTE: Shouldn't we check here against Entity?
        if etype = node.ExpressionType as ExternalType:
            name = etype.FullName

        # Handle aliases with multiple references: import Foo(Bar, Baz) as MyFoo
        elif esimple = node.Entity as Boo.Lang.Compiler.TypeSystem.Core.SimpleNamespace:
            if esimple.FullName:
                name = esimple.FullName
            else:
                for member in esimple.GetMembers():
                    if member isa ExternalType:
                        name = (member as ExternalType).ActualType.Namespace
                        break;

        if not name:
            return

        # Find a mapping containing the full name of the entity
        parts = name.Split(char('.'))
        rhs = []
        while len(parts):
            name = join(parts, '.')
            if name in _mappings:
                rhs.Insert(0, _mappings[name])
                node.Name = join(rhs, '.')
                break

            # Skip module classes
            if not IsModule(name):
                rhs.Add(parts[-1])

            parts = parts[:-1]

    protected def IsModule(fqn as string) as bool:
    """ Checks if the given fully qualified name actually resolves into a module
    """
        try:
            type = NameResolutionService.ResolveQualifiedName(fqn) as ExternalType
            return type and type.IsClass and type.IsFinal and type.Name.EndsWith('Module')
        except:
            return false


Removed left-over comment

Removed left-over comment

#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.PatternMatching.Impl

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

class PatternExpander:

	private static final Dummy = '_'

	def Expand(matchValue as Expression, pattern as Expression) as Expression:
		if mie = pattern as MethodInvocationExpression:
			if mie.Target.ToString() == 'array':
				lst = ArrayLiteralExpression()
				for arg in mie.Arguments:
					lst.Items.Add(arg)

				temp = NewTemp(matchValue)
				condition = [| $temp = $matchValue as (object) |]
				return [| $condition and $(ExpandFixedSizePattern(temp, lst)) |]

			return ExpandObjectPattern(matchValue, pattern)

		if pattern isa MemberReferenceExpression:
			return ExpandValuePattern(matchValue, pattern)

		if pattern isa ReferenceExpression:
			return ExpandBindPattern(matchValue, pattern)

		if pattern isa QuasiquoteExpression:
			return ExpandQuasiquotePattern(matchValue, pattern)

		if pattern isa RELiteralExpression:
			return ExpandRegexPattern(matchValue, pattern)

		binary = pattern as BinaryExpression
		if binary is not null:
			if BinaryOperatorType.Assign == binary.Operator:
				return ExpandCapturePattern(matchValue, pattern)

			if BinaryOperatorType.BitwiseOr == binary.Operator:
				return ExpandEitherPattern(matchValue, pattern)

			if BinaryOperatorType.BitwiseAnd == binary.Operator:
				return ExpandBothPattern(matchValue, pattern)

			if BinaryOperatorType.And == binary.Operator:
				return ExpandConstrainedAndPattern(matchValue, pattern)

			if BinaryOperatorType.Or == binary.Operator:
				return ExpandConstrainedOrPattern(matchValue, pattern)

		if pattern isa ArrayLiteralExpression:
			return ExpandFixedSizePattern(matchValue, pattern)

		return ExpandValuePattern(matchValue, pattern)

	def ExpandBothPattern(matchValue as Expression, node as BinaryExpression) as Expression:
		l = Expand(matchValue, node.Left)
		r = Expand(matchValue, node.Right)
		return [| $l and $r |]

	def ExpandConstrainedAndPattern(matchValue as Expression, node as BinaryExpression) as Expression:
		l = Expand(matchValue, node.Left)
		return [| $l and $(node.Right) |]

	def ExpandConstrainedOrPattern(matchValue as Expression, node as BinaryExpression) as Expression:
		l = Expand(matchValue, node.Left)
		return [| $l or $(node.Right) |]

	def ExpandRegexPattern(matchValue as Expression, node as RELiteralExpression):
		tempBinding = NewTemp(node)
		return ExpandRegexPatternWithBinding(matchValue, node, tempBinding)

	def ExpandRegexPatternWithBinding(matchValue as Expression, pattern as RELiteralExpression, binding as ReferenceExpression):

		groupNames = array(groupName for groupName in pattern.Regex.GetGroupNames() if not IsInteger(groupName))

		expansion = [| @($binding = $pattern.Match($matchValue)) |]
		if len(groupNames) == 0:
			expansion.Arguments.Add([| $binding.Success |])
		else:
			bindingAction = [| @() |]
			for groupName in groupNames:
				groupBinding = ReferenceExpression(LexicalInfo: pattern.LexicalInfo, Name: groupName)
				bindingAction.Arguments.Add([| $groupBinding = $binding.Groups[$groupName].Captures |])
			bindingAction.Arguments.Add([| true |])

			expansion.Arguments.Add([| ($bindingAction if $binding.Success else false) |])

		return expansion

	def IsInteger(s as string):
		_ as int
		return int.TryParse(s, _)

	def ExpandEitherPattern(matchValue as Expression, node as BinaryExpression) as Expression:
		l = Expand(matchValue, node.Left)
		r = Expand(matchValue, node.Right)
		return [| $l or $r |]

	def ExpandBindPattern(matchValue as Expression, node as ReferenceExpression):
		return [| @($node = $matchValue, true) |]

	def ExpandValuePattern(matchValue as Expression, node as Expression):
		return [| $matchValue == $node |]

	def ExpandCapturePattern(matchValue as Expression, node as BinaryExpression):
		name = node.Left
		pattern = node.Right
		if pattern isa RELiteralExpression:
			return ExpandRegexPatternWithBinding(matchValue, pattern, name)

		return ExpandObjectPattern(matchValue, name, pattern)

	def ExpandObjectPattern(matchValue as Expression, node as MethodInvocationExpression) as Expression:
		if len(node.NamedArguments) == 0 and len(node.Arguments) == 0:
			return [| $matchValue isa $(TypeRefFrom(node)) |]
		return ExpandObjectPattern(matchValue, NewTemp(node), node)

	def ExpandObjectPattern(matchValue as Expression, temp as ReferenceExpression, node as MethodInvocationExpression) as Expression:

		condition = [| ($matchValue isa $(TypeRefFrom(node))) and @($temp = cast($(TypeRefFrom(node)), $matchValue), true) |]
		condition.LexicalInfo = node.LexicalInfo

		for member in node.Arguments:
			assert member isa ReferenceExpression, "Invalid argument '${member}' in pattern '${node}'."
			memberRef = MemberReferenceExpression(member.LexicalInfo, temp.CloneNode(), member.ToString())
			condition = [| $condition and @($member = $memberRef, true) |]

		for member in node.NamedArguments:
			namedArgCondition = ExpandMemberPattern(temp.CloneNode(), member)
			condition = [| $condition and $namedArgCondition |]

		return condition

	class QuasiquotePatternBuilder(DepthFirstVisitor):

		static final Ast = [| Boo.Lang.Compiler.Ast |]

		_parent as PatternExpander
		_pattern as Expression

		def constructor(parent as PatternExpander):
			_parent = parent

		def Build(node as QuasiquoteExpression):
			return Expand(node.Node)

		def Expand(node as Node):
			node.Accept(self)
			expansion = _pattern
			_pattern = null
			assert expansion is not null, "Unsupported pattern '${node}'"
			return expansion

		def Push(srcNode as Node, e as Expression):
			assert _pattern is null
			e.LexicalInfo = srcNode.LexicalInfo
			_pattern = e

		override def OnSpliceExpression(node as SpliceExpression):
			_pattern = node.Expression

		override def OnSpliceTypeReference(node as SpliceTypeReference):
			_pattern = node.Expression

		def ExpandFixedSize(items):
			a = [| (,) |]
			for item in items:
				a.Items.Add(Expand(item))
			return a

		override def OnOmittedExpression(node as OmittedExpression):
			_pattern = [| $Ast.OmittedExpression.Default |]

		override def OnSlice(node as Slice):
			ctor = [| $Ast.Slice() |]
			ExpandProperty ctor, "Begin", node.Begin
			ExpandProperty ctor, "End", node.End
			ExpandProperty ctor, "Step", node.Step
			Push node, ctor

		def ExpandProperty(ctor as MethodInvocationExpression, name as string, value as Expression):
			if value is null: return
			ctor.NamedArguments.Add(ExpressionPair(First: ReferenceExpression(name), Second: Expand(value)))

		override def OnMacroStatement(node as MacroStatement):
			if len(node.Arguments) > 0:
				Push node, [| $Ast.MacroStatement(Name: $(node.Name), Arguments: $(ExpandFixedSize(node.Arguments))) |]
			else:
				Push node, [| $Ast.MacroStatement(Name: $(node.Name)) |]

		override def OnSlicingExpression(node as SlicingExpression):
			Push node, [| $Ast.SlicingExpression(Target: $(Expand(node.Target)), Indices: $(ExpandFixedSize(node.Indices))) |]

		override def OnTryCastExpression(node as TryCastExpression):
			Push node, [| $Ast.TryCastExpression(Target: $(Expand(node.Target)), Type: $(Expand(node.Type))) |]

		override def OnCastExpression(node as CastExpression):
			Push node, [| $Ast.CastExpression(Target: $(Expand(node.Target)), Type: $(Expand(node.Type))) |]

		override def OnMethodInvocationExpression(node as MethodInvocationExpression):
			if len(node.Arguments) > 0:
				pattern = [| $Ast.MethodInvocationExpression(Target: $(Expand(node.Target)), Arguments: $(ExpandFixedSize(node.Arguments))) |]
			else:
				pattern = [| $Ast.MethodInvocationExpression(Target: $(Expand(node.Target))) |]
			Push node, pattern

		override def OnTypeofExpression(node as TypeofExpression):
			Push node, [| $Ast.TypeofExpression(Type: $(Expand(node.Type))) |]

		override def OnListLiteralExpression(node as ListLiteralExpression):
			Push node, [| $Ast.ListLiteralExpression(Items: $(ExpandFixedSize(node.Items))) |]

		override def OnBoolLiteralExpression(node as BoolLiteralExpression):
			Push node, [| $Ast.BoolLiteralExpression(Value: $node) |]

		override def OnIntegerLiteralExpression(node as IntegerLiteralExpression):
			Push node, [| $Ast.IntegerLiteralExpression(Value: $node) |]

		override def OnStringLiteralExpression(node as StringLiteralExpression):
			Push node, [| $Ast.StringLiteralExpression(Value: $node) |]

		override def OnNullLiteralExpression(node as NullLiteralExpression):
			Push node, [| $Ast.NullLiteralExpression() |]

		override def OnUnaryExpression(node as UnaryExpression):
			Push node, [| $Ast.UnaryExpression(Operator: $Ast.UnaryOperatorType.$(node.Operator.ToString()), Operand: $(Expand(node.Operand))) |]

		override def OnBinaryExpression(node as BinaryExpression):
			Push node, [| $Ast.BinaryExpression(Operator: $Ast.BinaryOperatorType.$(node.Operator.ToString()), Left: $(Expand(node.Left)), Right: $(Expand(node.Right))) |]

		override def OnReferenceExpression(node as ReferenceExpression):
			Push node, [| $Ast.ReferenceExpression(Name: $(node.Name)) |]

		override def OnSuperLiteralExpression(node as SuperLiteralExpression):
			Push node, [| $Ast.SuperLiteralExpression() |]

		override def OnSelfLiteralExpression(node as SelfLiteralExpression):
			Push node, [| $Ast.SelfLiteralExpression() |]

		override def OnMemberReferenceExpression(node as MemberReferenceExpression):
			Push node, [| $Ast.MemberReferenceExpression(Target: $(Expand(node.Target)), Name: $(node.Name)) |]

		override def OnGenericReferenceExpression(node as GenericReferenceExpression):
			Push node, [| $Ast.GenericReferenceExpression(Target: $(Expand(node.Target)), GenericArguments: $(ExpandFixedSize(node.GenericArguments))) |]

		override def OnSimpleTypeReference(node as SimpleTypeReference):
			Push node, [| $Ast.SimpleTypeReference(Name: $(node.Name)) |]

		override def OnGenericTypeReference(node as GenericTypeReference):
			Push node, [| $Ast.GenericTypeReference(Name: $(node.Name), GenericArguments: $(ExpandFixedSize(node.GenericArguments))) |]

	def ObjectPatternFor(node as QuasiquoteExpression):
		return QuasiquotePatternBuilder(self).Build(node)

	def ExpandQuasiquotePattern(matchValue as Expression, node as QuasiquoteExpression) as Expression:
		return ExpandObjectPattern(matchValue, ObjectPatternFor(node))

	def ExpandMemberPattern(matchValue as Expression, member as ExpressionPair):
		memberRef = ExpandMemberReference(member.First, matchValue)
		return Expand(memberRef, member.Second)

	def ExpandMemberReference(member as Expression, target as Expression) as Expression:
		memberRef = member as MemberReferenceExpression
		if memberRef is not null:
			return MemberReferenceExpression(member.LexicalInfo, ExpandMemberReference(memberRef.Target, target), memberRef.Name)
		return MemberReferenceExpression(member.LexicalInfo, target, member.ToString())

	def ExpandFixedSizePattern(matchValue as Expression, pattern as ArrayLiteralExpression):
		patternLen = len(pattern.Items)
		condition = [| $(patternLen) == len($matchValue) |]

		if patternLen == 0: return condition

		if IsCatchAllPattern(last = pattern.Items[patternLen-1]):
			pattern.Items.Remove(last)
			condition = [| $(patternLen-1) <= len($matchValue) |]

		i = 0
		for item in pattern.Items:
			itemValue = [| $matchValue[$i] |]
			itemPattern = Expand(itemValue, item)
			condition = [| $condition and $itemPattern |]
			++i
		return condition

	def TypeRefFrom(node as MethodInvocationExpression):
		return node.Target

	def IsCatchAllPattern(pattern as Expression) as bool:
		last = pattern as UnaryExpression
		if AstUtil.IsExplodeExpression(last):
			last_re = last.Operand as ReferenceExpression
			return last_re and last_re.Name == Dummy
		return false

internal def NewTemp(e as Expression):
	return ReferenceExpression(
			LexicalInfo: e.LexicalInfo,
			Name: CompilerContext.Current.GetUniqueName("match"))

#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.PatternMatching.Impl

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

class PatternExpander:

	private static final Dummy = '_'

	def Expand(matchValue as Expression, pattern as Expression) as Expression:
		if mie = pattern as MethodInvocationExpression:
			if mie.Target.ToString() == 'array':
				lst = ArrayLiteralExpression()
				for arg in mie.Arguments:
					lst.Items.Add(arg)

				temp = NewTemp(matchValue)
				condition = [| $temp = $matchValue as (object) |] #System.Collections.IList |]
				return [| $condition and $(ExpandFixedSizePattern(temp, lst)) |]

			return ExpandObjectPattern(matchValue, pattern)

		if pattern isa MemberReferenceExpression:
			return ExpandValuePattern(matchValue, pattern)

		if pattern isa ReferenceExpression:
			return ExpandBindPattern(matchValue, pattern)

		if pattern isa QuasiquoteExpression:
			return ExpandQuasiquotePattern(matchValue, pattern)

		if pattern isa RELiteralExpression:
			return ExpandRegexPattern(matchValue, pattern)

		binary = pattern as BinaryExpression
		if binary is not null:
			if BinaryOperatorType.Assign == binary.Operator:
				return ExpandCapturePattern(matchValue, pattern)

			if BinaryOperatorType.BitwiseOr == binary.Operator:
				return ExpandEitherPattern(matchValue, pattern)

			if BinaryOperatorType.BitwiseAnd == binary.Operator:
				return ExpandBothPattern(matchValue, pattern)

			if BinaryOperatorType.And == binary.Operator:
				return ExpandConstrainedAndPattern(matchValue, pattern)

			if BinaryOperatorType.Or == binary.Operator:
				return ExpandConstrainedOrPattern(matchValue, pattern)

		if pattern isa ArrayLiteralExpression:
			return ExpandFixedSizePattern(matchValue, pattern)

		return ExpandValuePattern(matchValue, pattern)

	def ExpandBothPattern(matchValue as Expression, node as BinaryExpression) as Expression:
		l = Expand(matchValue, node.Left)
		r = Expand(matchValue, node.Right)
		return [| $l and $r |]

	def ExpandConstrainedAndPattern(matchValue as Expression, node as BinaryExpression) as Expression:
		l = Expand(matchValue, node.Left)
		return [| $l and $(node.Right) |]

	def ExpandConstrainedOrPattern(matchValue as Expression, node as BinaryExpression) as Expression:
		l = Expand(matchValue, node.Left)
		return [| $l or $(node.Right) |]

	def ExpandRegexPattern(matchValue as Expression, node as RELiteralExpression):
		tempBinding = NewTemp(node)
		return ExpandRegexPatternWithBinding(matchValue, node, tempBinding)

	def ExpandRegexPatternWithBinding(matchValue as Expression, pattern as RELiteralExpression, binding as ReferenceExpression):

		groupNames = array(groupName for groupName in pattern.Regex.GetGroupNames() if not IsInteger(groupName))

		expansion = [| @($binding = $pattern.Match($matchValue)) |]
		if len(groupNames) == 0:
			expansion.Arguments.Add([| $binding.Success |])
		else:
			bindingAction = [| @() |]
			for groupName in groupNames:
				groupBinding = ReferenceExpression(LexicalInfo: pattern.LexicalInfo, Name: groupName)
				bindingAction.Arguments.Add([| $groupBinding = $binding.Groups[$groupName].Captures |])
			bindingAction.Arguments.Add([| true |])

			expansion.Arguments.Add([| ($bindingAction if $binding.Success else false) |])

		return expansion

	def IsInteger(s as string):
		_ as int
		return int.TryParse(s, _)

	def ExpandEitherPattern(matchValue as Expression, node as BinaryExpression) as Expression:
		l = Expand(matchValue, node.Left)
		r = Expand(matchValue, node.Right)
		return [| $l or $r |]

	def ExpandBindPattern(matchValue as Expression, node as ReferenceExpression):
		return [| @($node = $matchValue, true) |]

	def ExpandValuePattern(matchValue as Expression, node as Expression):
		return [| $matchValue == $node |]

	def ExpandCapturePattern(matchValue as Expression, node as BinaryExpression):
		name = node.Left
		pattern = node.Right
		if pattern isa RELiteralExpression:
			return ExpandRegexPatternWithBinding(matchValue, pattern, name)

		return ExpandObjectPattern(matchValue, name, pattern)

	def ExpandObjectPattern(matchValue as Expression, node as MethodInvocationExpression) as Expression:
		if len(node.NamedArguments) == 0 and len(node.Arguments) == 0:
			return [| $matchValue isa $(TypeRefFrom(node)) |]
		return ExpandObjectPattern(matchValue, NewTemp(node), node)

	def ExpandObjectPattern(matchValue as Expression, temp as ReferenceExpression, node as MethodInvocationExpression) as Expression:

		condition = [| ($matchValue isa $(TypeRefFrom(node))) and @($temp = cast($(TypeRefFrom(node)), $matchValue), true) |]
		condition.LexicalInfo = node.LexicalInfo

		for member in node.Arguments:
			assert member isa ReferenceExpression, "Invalid argument '${member}' in pattern '${node}'."
			memberRef = MemberReferenceExpression(member.LexicalInfo, temp.CloneNode(), member.ToString())
			condition = [| $condition and @($member = $memberRef, true) |]

		for member in node.NamedArguments:
			namedArgCondition = ExpandMemberPattern(temp.CloneNode(), member)
			condition = [| $condition and $namedArgCondition |]

		return condition

	class QuasiquotePatternBuilder(DepthFirstVisitor):

		static final Ast = [| Boo.Lang.Compiler.Ast |]

		_parent as PatternExpander
		_pattern as Expression

		def constructor(parent as PatternExpander):
			_parent = parent

		def Build(node as QuasiquoteExpression):
			return Expand(node.Node)

		def Expand(node as Node):
			node.Accept(self)
			expansion = _pattern
			_pattern = null
			assert expansion is not null, "Unsupported pattern '${node}'"
			return expansion

		def Push(srcNode as Node, e as Expression):
			assert _pattern is null
			e.LexicalInfo = srcNode.LexicalInfo
			_pattern = e

		override def OnSpliceExpression(node as SpliceExpression):
			_pattern = node.Expression

		override def OnSpliceTypeReference(node as SpliceTypeReference):
			_pattern = node.Expression

		def ExpandFixedSize(items):
			a = [| (,) |]
			for item in items:
				a.Items.Add(Expand(item))
			return a

		override def OnOmittedExpression(node as OmittedExpression):
			_pattern = [| $Ast.OmittedExpression.Default |]

		override def OnSlice(node as Slice):
			ctor = [| $Ast.Slice() |]
			ExpandProperty ctor, "Begin", node.Begin
			ExpandProperty ctor, "End", node.End
			ExpandProperty ctor, "Step", node.Step
			Push node, ctor

		def ExpandProperty(ctor as MethodInvocationExpression, name as string, value as Expression):
			if value is null: return
			ctor.NamedArguments.Add(ExpressionPair(First: ReferenceExpression(name), Second: Expand(value)))

		override def OnMacroStatement(node as MacroStatement):
			if len(node.Arguments) > 0:
				Push node, [| $Ast.MacroStatement(Name: $(node.Name), Arguments: $(ExpandFixedSize(node.Arguments))) |]
			else:
				Push node, [| $Ast.MacroStatement(Name: $(node.Name)) |]

		override def OnSlicingExpression(node as SlicingExpression):
			Push node, [| $Ast.SlicingExpression(Target: $(Expand(node.Target)), Indices: $(ExpandFixedSize(node.Indices))) |]

		override def OnTryCastExpression(node as TryCastExpression):
			Push node, [| $Ast.TryCastExpression(Target: $(Expand(node.Target)), Type: $(Expand(node.Type))) |]

		override def OnCastExpression(node as CastExpression):
			Push node, [| $Ast.CastExpression(Target: $(Expand(node.Target)), Type: $(Expand(node.Type))) |]

		override def OnMethodInvocationExpression(node as MethodInvocationExpression):
			if len(node.Arguments) > 0:
				pattern = [| $Ast.MethodInvocationExpression(Target: $(Expand(node.Target)), Arguments: $(ExpandFixedSize(node.Arguments))) |]
			else:
				pattern = [| $Ast.MethodInvocationExpression(Target: $(Expand(node.Target))) |]
			Push node, pattern

		override def OnTypeofExpression(node as TypeofExpression):
			Push node, [| $Ast.TypeofExpression(Type: $(Expand(node.Type))) |]

		override def OnListLiteralExpression(node as ListLiteralExpression):
			Push node, [| $Ast.ListLiteralExpression(Items: $(ExpandFixedSize(node.Items))) |]

		override def OnBoolLiteralExpression(node as BoolLiteralExpression):
			Push node, [| $Ast.BoolLiteralExpression(Value: $node) |]

		override def OnIntegerLiteralExpression(node as IntegerLiteralExpression):
			Push node, [| $Ast.IntegerLiteralExpression(Value: $node) |]

		override def OnStringLiteralExpression(node as StringLiteralExpression):
			Push node, [| $Ast.StringLiteralExpression(Value: $node) |]

		override def OnNullLiteralExpression(node as NullLiteralExpression):
			Push node, [| $Ast.NullLiteralExpression() |]

		override def OnUnaryExpression(node as UnaryExpression):
			Push node, [| $Ast.UnaryExpression(Operator: $Ast.UnaryOperatorType.$(node.Operator.ToString()), Operand: $(Expand(node.Operand))) |]

		override def OnBinaryExpression(node as BinaryExpression):
			Push node, [| $Ast.BinaryExpression(Operator: $Ast.BinaryOperatorType.$(node.Operator.ToString()), Left: $(Expand(node.Left)), Right: $(Expand(node.Right))) |]

		override def OnReferenceExpression(node as ReferenceExpression):
			Push node, [| $Ast.ReferenceExpression(Name: $(node.Name)) |]

		override def OnSuperLiteralExpression(node as SuperLiteralExpression):
			Push node, [| $Ast.SuperLiteralExpression() |]

		override def OnSelfLiteralExpression(node as SelfLiteralExpression):
			Push node, [| $Ast.SelfLiteralExpression() |]

		override def OnMemberReferenceExpression(node as MemberReferenceExpression):
			Push node, [| $Ast.MemberReferenceExpression(Target: $(Expand(node.Target)), Name: $(node.Name)) |]

		override def OnGenericReferenceExpression(node as GenericReferenceExpression):
			Push node, [| $Ast.GenericReferenceExpression(Target: $(Expand(node.Target)), GenericArguments: $(ExpandFixedSize(node.GenericArguments))) |]

		override def OnSimpleTypeReference(node as SimpleTypeReference):
			Push node, [| $Ast.SimpleTypeReference(Name: $(node.Name)) |]

		override def OnGenericTypeReference(node as GenericTypeReference):
			Push node, [| $Ast.GenericTypeReference(Name: $(node.Name), GenericArguments: $(ExpandFixedSize(node.GenericArguments))) |]

	def ObjectPatternFor(node as QuasiquoteExpression):
		return QuasiquotePatternBuilder(self).Build(node)

	def ExpandQuasiquotePattern(matchValue as Expression, node as QuasiquoteExpression) as Expression:
		return ExpandObjectPattern(matchValue, ObjectPatternFor(node))

	def ExpandMemberPattern(matchValue as Expression, member as ExpressionPair):
		memberRef = ExpandMemberReference(member.First, matchValue)
		return Expand(memberRef, member.Second)

	def ExpandMemberReference(member as Expression, target as Expression) as Expression:
		memberRef = member as MemberReferenceExpression
		if memberRef is not null:
			return MemberReferenceExpression(member.LexicalInfo, ExpandMemberReference(memberRef.Target, target), memberRef.Name)
		return MemberReferenceExpression(member.LexicalInfo, target, member.ToString())

	def ExpandFixedSizePattern(matchValue as Expression, pattern as ArrayLiteralExpression):
		patternLen = len(pattern.Items)
		condition = [| $(patternLen) == len($matchValue) |]

		if patternLen == 0: return condition

		if IsCatchAllPattern(last = pattern.Items[patternLen-1]):
			pattern.Items.Remove(last)
			condition = [| $(patternLen-1) <= len($matchValue) |]

		i = 0
		for item in pattern.Items:
			itemValue = [| $matchValue[$i] |]
			itemPattern = Expand(itemValue, item)
			condition = [| $condition and $itemPattern |]
			++i
		return condition

	def TypeRefFrom(node as MethodInvocationExpression):
		return node.Target

	def IsCatchAllPattern(pattern as Expression) as bool:
		last = pattern as UnaryExpression
		if AstUtil.IsExplodeExpression(last):
			last_re = last.Operand as ReferenceExpression
			return last_re and last_re.Name == Dummy
		return false

internal def NewTemp(e as Expression):
	return ReferenceExpression(
			LexicalInfo: e.LexicalInfo,
			Name: CompilerContext.Current.GetUniqueName("match"))


Update build script to use generated packages

include "build_support.boo"

solution = "MarkEmbling.PostcodesIO.sln"

configuration = "release"

test_assembly = "MarkEmbling.PostcodesIO.Tests/bin/${configuration}/MarkEmbling.PostcodesIO.Tests.dll"

bin_path = "build/bin"

target default, (compile, test, prep):

  pass

desc "Compiles the solution"

target compile:

  msbuild(file: solution, configuration: configuration, version: "4.0")

desc "Executes the tests"

target test, (compile):

  with nunit(assembly: test_assembly, toolPath: "packages/NUnit.Runners.2.6.4/tools/nunit-console.exe")

desc "Copies the binaries to the 'build/bin' directory"

target prep, (compile, test):

  rmdir(bin_path)

  with FileList("MarkEmbling.PostcodesIO/bin/${configuration}"):

    .Include("*.{dll,exe,config,nupkg}")

    .ForEach def(file):

      file.CopyToDirectory(bin_path)

desc "Publishes the NuGet packages"

target publish, (prep):

  api_key = read_nuget_api_key()

  with FileList(bin_path):

    .Include("*.nupkg")

    .ForEach def(file):

      filename = Path.GetFileName(file.FullName)

      if prompt("Publish ${filename}...?"):

        with nuget_push():

          .toolPath =  ".nuget/NuGet.exe"

          .packagePath = file.FullName

          .apiKey = api_key

include "build_support.boo"

solution = "MarkEmbling.PostcodesIO.sln"

configuration = "release"

test_assembly = "MarkEmbling.PostcodesIO.Tests/bin/${configuration}/MarkEmbling.PostcodesIO.Tests.dll"

bin_path = "build/bin"

package_path = "build/nuget"

target default, (compile, test, prep):

  pass

desc "Compiles the solution"

target compile:

  msbuild(file: solution, configuration: configuration, version: "4.0")

desc "Executes the tests"

target test, (compile):

  with nunit(assembly: test_assembly, toolPath: "packages/NUnit.Runners.2.6.4/tools/nunit-console.exe")

desc "Copies the binaries to the 'build/bin' directory"

target prep, (compile, test):

  rmdir(bin_path)

  with FileList("MarkEmbling.PostcodesIO/bin/${configuration}"):

    .Include("*.{dll,exe,config}")

    .ForEach def(file):

      file.CopyToDirectory(bin_path)

desc "Creates the NuGet packages"

target package, (prep):

  with FileList(package_path):

    .Include("*.nupkg")

    .ForEach def(file):

      file.Delete()

  with nuget_pack():

    .toolPath = "packages/NuGet.CommandLine.2.8.5/tools/NuGet.exe"

    .nuspecFile = "build/nuget/MarkEmbling.PostcodesIO.nuspec"

    .outputDirectory = package_path

desc "Publishes the NuGet packages"

target publish, (package):

  api_key = read_nuget_api_key()

  with FileList(package_path):

    .Include("*.nupkg")

    .ForEach def(file):

      filename = Path.GetFileName(file.FullName)

      if prompt("Publish ${filename}...?"):

        with nuget_push():

          .toolPath =  "packages/NuGet.CommandLine.2.8.5/tools/NuGet.exe"

          .packagePath = file.FullName

          .apiKey = api_key


Fixed the handling of return inside generators

namespace BooJs.Compiler.Steps

import System
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):
            # 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 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
            State = loopstate = CreateState()

            # 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
            State = trystate = CreateState()

            # 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 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)

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

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

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

            # Continue in the after state
            State = afterstate

    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.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):
            # 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 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) |])
                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
            State = loopstate = CreateState()

            # 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
            State = trystate = CreateState()

            # 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 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)

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

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

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

            # Continue in the after state
            State = afterstate

    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)


simpler code

simpler code

git-svn-id: c3b8989232654601f1acea7403f771adaaa44a81@483 2c1201b4-01cd-e047-a400-b836ae1fbc61

namespace BooExplorer

import System
import System.Environment
import System.IO
import System.ComponentModel
import System.Windows.Forms
import System.Drawing
import WeifenLuo.WinFormsUI

class MainForm(Form):

	_dockPanel as DockPanel
	_status as StatusBar
	_statusPanel1 as StatusBarPanel
	_timer as Timer

	[getter(DocumentOutline)]
	_documentOutline = BooExplorer.DocumentOutline()

	[getter(TaskList)]
	_taskList = BooExplorer.TaskList(self)

	[getter(OutputPane)]
	_outputPane = BooExplorer.OutputPane()

	_argv as (string)

	_menuItemClose as MenuItem
	_menuItemSave as MenuItem
	_menuItemSaveAs as MenuItem

	def constructor(argv as (string)):
		_argv = argv
		_dockPanel = DockPanel(Dock: DockStyle.Fill,
						ActiveAutoHideContent: null,
						TabIndex: 1,
						ActiveDocumentChanged: _dockPanel_ActiveDocumentChanged)

		_statusPanel1 = StatusBarPanel(AutoSize: StatusBarPanelAutoSize.Contents)

		_status = StatusBar(ShowPanels: true, TabIndex: 2)
		_status.Panels.Add(_statusPanel1)

		SuspendLayout()

		self.Size = System.Drawing.Size(800, 600)
		self.Menu = CreateMainMenu()
		self.Text = "Boo Explorer"
		self.IsMdiContainer = true

		Controls.AddRange((
					_dockPanel,
					_status))
		ResumeLayout(false)

		_timer = Timer(Tick: _timer_Tick, Interval: 50ms.TotalMilliseconds)
		_timer.Enabled = true

	private def CreateMainMenu():

		menu = MainMenu()
		file = MenuItem(Text: "&File", MergeOrder: 0)
		file.MenuItems.Add(MenuItem(Text: "&Open...",
									Click: _menuItemOpen_Click,
									Shortcut: Shortcut.CtrlO))
		file.MenuItems.Add(MenuItem(Text: "&New",
									Click: _menuItemNew_Click,
									Shortcut: Shortcut.CtrlN))

		file.MenuItems.Add(MenuItem("-"))

		file.MenuItems.Add(_menuItemSave = MenuItem(Text: "&Save",
									Enabled: false,
									Click: _menuItemSave_Click,
									Shortcut: Shortcut.CtrlS))
		file.MenuItems.Add(_menuItemSaveAs = MenuItem(Text: "S&ave as...",
									Enabled: false,
									Click: _menuItemSaveAs_Click))

		file.MenuItems.Add(_menuItemClose = MenuItem(Text: "&Close",
									Click: _menuItemClose_Click,
									Shortcut: Shortcut.CtrlW,
									Enabled: false))

		file.MenuItems.Add(MenuItem("-"))
		file.MenuItems.Add(MenuItem(Text: "E&xit",
									Shortcut: Shortcut.CtrlQ,
									Click: _menuItemExit_Click))

		view = MenuItem(Text: "&View", MergeOrder: 4)
		view.MenuItems.AddRange(
			(
				MenuItem(Text: "Document Outline",
						Click: _menuItemDocumentOutline_Click,
						Shortcut: Shortcut.CtrlShiftD),
				MenuItem(Text: "Task List",
						Click: _menuItemTaskList_Click,
						Shortcut: Shortcut.CtrlShiftT),
				MenuItem(Text: "Output",
						Click: _menuItemOutputPane_Click,
						Shortcut: Shortcut.CtrlShiftO)
			))

		menu.MenuItems.AddRange((file, view))
		return menu

	def _timer_Tick(sender, args as EventArgs):
		_timer.Enabled = false

		if File.Exists(GetDockStateXmlFileName()):
			LoadDockState()
			OpenDocuments(_argv)
		else:
			if len(_argv):
				OpenDocuments(_argv)
			else:
				NewDocument()
			ShowDocumentOutline()

	StatusText as string:
		set:
			_statusPanel1.Text = value

	def ShowDocumentOutline():
		ShowContent(_documentOutline)

	private def ShowContent(content as DockContent):
		content.Show(_dockPanel)
		if DockState.Unknown == content.DockState:
			content.Pane.DockState = content.ShowHint

	def NewDocument():
		editor = BooEditor(self)
		editor.Show(_dockPanel)
		editor.TextArea.Focus()

	def OpenDocuments([required] fnames):
		for fname in fnames:
			try:
				OpenDocument(fname)
			except x:
				print(x)

	def OpenDocument([required] filename as string):
		filename = Path.GetFullPath(filename)
		content = FindEditor(filename)
		if content is null:
			editor = CreateEditor(filename)
			editor.Show(_dockPanel)
			editor.TextArea.Focus()
			return editor
		else:
			content.Show(_dockPanel)
			content.TextArea.Focus()
			return content

	private def CreateEditor([required] fname as string):
		editor = BooEditor(self)
		editor.Open(fname)
		return editor

	def _dockPanel_ActiveDocumentChanged(sender, args as EventArgs):
		document = _dockPanel.ActiveDocument
		editor = document as BooEditor
		_documentOutline.ActiveDocument = editor
		_menuItemClose.Enabled = document is not null
		_menuItemSaveAs.Enabled = _menuItemSave.Enabled = document is not null

	def _menuItemSaveAs_Click(sender, args as EventArgs):
		cast(BooEditor, _dockPanel.ActiveDocument).SaveAs()

	def _menuItemSave_Click(sender, args as EventArgs):
		cast(BooEditor, _dockPanel.ActiveDocument).Save()

	def _menuItemExit_Click(sender, args as EventArgs):
		self.Close()

	def _menuItemClose_Click(sender, args as EventArgs):
		_dockPanel.ActiveDocument.Close()

	def _menuItemDocumentOutline_Click(sender, args as EventArgs):
		ShowDocumentOutline()

	def _menuItemTaskList_Click(sender, args as EventArgs):
		ShowTaskList()

	def ShowTaskList():
		ShowContent(_taskList)

	def ShowOutputPane():
		ShowContent(_outputPane)

	def _menuItemOutputPane_Click(sender, args as EventArgs):
		ShowOutputPane()

	def _menuItemOpen_Click(sender, args as EventArgs):
		dlg = OpenFileDialog(
					Filter: "boo files (*.boo)|*.boo|All files (*.*)|*.*",
					Multiselect: true)
		if DialogResult.OK == dlg.ShowDialog(self):
			for fname in dlg.FileNames:
				OpenDocument(fname)

	def _menuItemNew_Click(sender, args as EventArgs):
		NewDocument()

	def GetApplicationDataFolder():
		folder = Path.Combine(
				GetFolderPath(SpecialFolder.ApplicationData),
				"boox")

		Directory.CreateDirectory(folder) unless Directory.Exists(folder)
		return folder

	def GetDockStateXmlFileName():
		return Path.Combine(GetApplicationDataFolder(), "dockstate.xml")

	def SaveDockState():
		_dockPanel.SaveAsXml(GetDockStateXmlFileName())

	def LoadDockState():
		delegate = Delegate.CreateDelegate(DeserializeDockContent, self, "OnDeserializeDockContent")
		_dockPanel.LoadFromXml(GetDockStateXmlFileName(), delegate)

	def OnDeserializeDockContent(persistString as string) as DockContent:
		type, content = /\|/.Split(persistString)
		print("type: ${type}, content: ${content}")
		if "DocumentOutline" == type:
			return _documentOutline
		if "TaskList" == type:
			return _taskList
		if "OutputPane" == type:
			return _outputPane
		if "BooEditor" == type:
			editor = BooEditor(self)
			editor.Open(content) if File.Exists(content)
			return editor
		raise ArgumentException("Invalid persistence string: ${persistString}")

	override protected def OnClosed(args as EventArgs):
		super(args)
		SaveDockState()

	override protected def OnClosing(args as CancelEventArgs):
		super(args)
		if not args.Cancel:
			dirtyDocuments = [
							editor.GetSafeFileName()
							for document in _dockPanel.Documents
							if (editor=(document as BooEditor)) and editor.IsDirty
							]
			return unless len(dirtyDocuments)

			args.Cancel = (
							DialogResult.Yes !=
							MessageBox.Show("The following files were modified:\n\n\t" +
											join(dirtyDocuments, "\n\t") +
											"\n\nAre you sure you want to leave and lose all your changes?",
											"Boo Explorer",
											MessageBoxButtons.YesNo))

	def FindEditor(fname as string):
		for document in _dockPanel.Documents:
			editor = document as BooEditor
			if editor and editor.FileName == fname:
				return editor
		return null

namespace BooExplorer

import System
import System.Environment
import System.IO
import System.ComponentModel
import System.Windows.Forms
import System.Drawing
import WeifenLuo.WinFormsUI

class MainForm(Form):

	_dockPanel as DockPanel
	_status as StatusBar
	_statusPanel1 as StatusBarPanel
	_timer as Timer

	[getter(DocumentOutline)]
	_documentOutline = BooExplorer.DocumentOutline()

	[getter(TaskList)]
	_taskList = BooExplorer.TaskList(self)

	[getter(OutputPane)]
	_outputPane = BooExplorer.OutputPane()

	_argv as (string)

	_menuItemClose as MenuItem
	_menuItemSave as MenuItem
	_menuItemSaveAs as MenuItem

	def constructor(argv as (string)):
		_argv = argv
		_dockPanel = DockPanel(Dock: DockStyle.Fill,
						ActiveAutoHideContent: null,
						TabIndex: 1,
						ActiveDocumentChanged: _dockPanel_ActiveDocumentChanged)

		_statusPanel1 = StatusBarPanel(AutoSize: StatusBarPanelAutoSize.Contents)

		_status = StatusBar(ShowPanels: true, TabIndex: 2)
		_status.Panels.Add(_statusPanel1)

		SuspendLayout()

		self.Size = System.Drawing.Size(800, 600)
		self.Menu = CreateMainMenu()
		self.Text = "Boo Explorer"
		self.IsMdiContainer = true

		Controls.AddRange((
					_dockPanel,
					_status))
		ResumeLayout(false)

		_timer = Timer(Tick: _timer_Tick, Interval: 50ms.TotalMilliseconds)
		_timer.Enabled = true

	private def CreateMainMenu():

		menu = MainMenu()
		file = MenuItem(Text: "&File", MergeOrder: 0)
		file.MenuItems.Add(MenuItem(Text: "&Open...",
									Click: _menuItemOpen_Click,
									Shortcut: Shortcut.CtrlO))
		file.MenuItems.Add(MenuItem(Text: "&New",
									Click: _menuItemNew_Click,
									Shortcut: Shortcut.CtrlN))

		file.MenuItems.Add(MenuItem("-"))

		file.MenuItems.Add(_menuItemSave = MenuItem(Text: "&Save",
									Enabled: false,
									Click: _menuItemSave_Click,
									Shortcut: Shortcut.CtrlS))
		file.MenuItems.Add(_menuItemSaveAs = MenuItem(Text: "S&ave as...",
									Enabled: false,
									Click: _menuItemSaveAs_Click))

		file.MenuItems.Add(_menuItemClose = MenuItem(Text: "&Close",
									Click: _menuItemClose_Click,
									Shortcut: Shortcut.CtrlW,
									Enabled: false))

		file.MenuItems.Add(MenuItem("-"))
		file.MenuItems.Add(MenuItem(Text: "E&xit",
									Shortcut: Shortcut.CtrlQ,
									Click: _menuItemExit_Click))

		view = MenuItem(Text: "&View", MergeOrder: 4)
		view.MenuItems.AddRange(
			(
				MenuItem(Text: "Document Outline",
						Click: _menuItemDocumentOutline_Click,
						Shortcut: Shortcut.CtrlShiftD),
				MenuItem(Text: "Task List",
						Click: _menuItemTaskList_Click,
						Shortcut: Shortcut.CtrlShiftT),
				MenuItem(Text: "Output",
						Click: _menuItemOutputPane_Click,
						Shortcut: Shortcut.CtrlShiftO)
			))

		menu.MenuItems.AddRange((file, view))
		return menu

	def _timer_Tick(sender, args as EventArgs):
		_timer.Enabled = false

		if File.Exists(GetDockStateXmlFileName()):
			LoadDockState()
			OpenDocuments(_argv)
		else:
			if len(_argv):
				OpenDocuments(_argv)
			else:
				NewDocument()
			ShowDocumentOutline()

	StatusText as string:
		set:
			_statusPanel1.Text = value

	def ShowDocumentOutline():
		ShowContent(_documentOutline)

	private def ShowContent(content as DockContent):
		content.Show(_dockPanel)
		if DockState.Unknown == content.DockState:
			content.Pane.DockState = content.ShowHint

	def NewDocument():
		editor = BooEditor(self)
		editor.Show(_dockPanel)
		editor.TextArea.Focus()

	def OpenDocuments([required] fnames):
		for fname in fnames:
			try:
				OpenDocument(fname)
			except x:
				print(x)

	def OpenDocument([required] filename as string):
		filename = Path.GetFullPath(filename)
		content = FindEditor(filename)
		if content is null:
			editor = CreateEditor(filename)
			editor.Show(_dockPanel)
			editor.TextArea.Focus()
			return editor
		else:
			content.Show(_dockPanel)
			content.TextArea.Focus()
			return content

	private def CreateEditor([required] fname as string):
		editor = BooEditor(self)
		editor.Open(fname)
		return editor

	def _dockPanel_ActiveDocumentChanged(sender, args as EventArgs):
		document = _dockPanel.ActiveDocument
		editor = document as BooEditor
		_documentOutline.ActiveDocument = editor
		_menuItemClose.Enabled = document is not null
		_menuItemSaveAs.Enabled = _menuItemSave.Enabled = document is not null

	def _menuItemSaveAs_Click(sender, args as EventArgs):
		cast(BooEditor, _dockPanel.ActiveDocument).SaveAs()

	def _menuItemSave_Click(sender, args as EventArgs):
		cast(BooEditor, _dockPanel.ActiveDocument).Save()

	def _menuItemExit_Click(sender, args as EventArgs):
		self.Close()

	def _menuItemClose_Click(sender, args as EventArgs):
		_dockPanel.ActiveDocument.Close()

	def _menuItemDocumentOutline_Click(sender, args as EventArgs):
		ShowDocumentOutline()

	def _menuItemTaskList_Click(sender, args as EventArgs):
		ShowTaskList()

	def ShowTaskList():
		ShowContent(_taskList)

	def ShowOutputPane():
		ShowContent(_outputPane)

	def _menuItemOutputPane_Click(sender, args as EventArgs):
		ShowOutputPane()

	def _menuItemOpen_Click(sender, args as EventArgs):
		dlg = OpenFileDialog(
					Filter: "boo files (*.boo)|*.boo|All files (*.*)|*.*",
					Multiselect: true)
		if DialogResult.OK == dlg.ShowDialog(self):
			for fname in dlg.FileNames:
				OpenDocument(fname)

	def _menuItemNew_Click(sender, args as EventArgs):
		NewDocument()

	def GetApplicationDataFolder():
		folder = Path.Combine(
				GetFolderPath(SpecialFolder.ApplicationData),
				"boox")

		Directory.CreateDirectory(folder) unless Directory.Exists(folder)
		return folder

	def GetDockStateXmlFileName():
		return Path.Combine(GetApplicationDataFolder(), "dockstate.xml")

	def SaveDockState():
		_dockPanel.SaveAsXml(GetDockStateXmlFileName())

	def LoadDockState():
		delegate = Delegate.CreateDelegate(DeserializeDockContent, self, "OnDeserializeDockContent")
		_dockPanel.LoadFromXml(GetDockStateXmlFileName(), delegate)

	def OnDeserializeDockContent(persistString as string) as DockContent:
		type, content = /\|/.Split(persistString)
		print("type: ${type}, content: ${content}")
		if "DocumentOutline" == type:
			return _documentOutline
		if "TaskList" == type:
			return _taskList
		if "OutputPane" == type:
			return _outputPane
		if "BooEditor" == type:
			editor = BooEditor(self)
			editor.Open(content) if File.Exists(content)
			return editor
		raise ArgumentException("Invalid persistence string: ${persistString}")

	override protected def OnClosed(args as EventArgs):
		super(args)
		SaveDockState()

	override protected def OnClosing(args as CancelEventArgs):
		super(args)
		if not args.Cancel:
			dirtyDocuments = [
							cast(BooEditor, editor).GetSafeFileName()
							for editor in _dockPanel.Documents
							if editor isa BooEditor and cast(BooEditor, editor).IsDirty
							]
			return unless len(dirtyDocuments)

			args.Cancel = (
							DialogResult.Yes !=
							MessageBox.Show("The following files were modified:\n\n\t" +
											join(dirtyDocuments, "\n\t") +
											"\n\nAre you sure you want to leave and lose all your changes?",
											"Boo Explorer",
											MessageBoxButtons.YesNo))

	def FindEditor(fname as string):
		for document in _dockPanel.Documents:
			editor = document as BooEditor
			if editor and editor.FileName == fname:
				return editor
		return null


Commented latests changes to process method bodies

namespace BooJs.Compiler.Steps

import Boo.Lang.Compiler.Steps
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.TypeSystem
import Boo.Lang.Compiler.TypeSystem(ExternalMethod, IExternalEntity)

import Boo.Lang.Environments
import BooJs.Compiler.TypeSystem(RuntimeMethodCache)

class OverrideProcessMethodBodies(ProcessMethodBodiesWithDuckTyping):
""" Overrides some methods of the step to skip some modification made originally
"""

    class Pmb(IQuackFu):
    """ Uses reflection to call private methods in the ancestor class ProcessMethodBodies via a
        IQuackFu api.
    """
        pmb = typeof(ProcessMethodBodies)
        instance as ProcessMethodBodies
        def constructor(instance):
            self.instance = instance

        def QuackGet(prop as string, args as (object)):
            pass
        def QuackSet(prop as string, args as (object), val as object):
            pass
        def QuackInvoke(method as string, params as (object)) as object:
            types = array(System.Type, param.GetType() for param in params)
            mi = pmb.GetMethod(method, BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance,
                               null, types, null)
            if not mi:
                raise 'Private method {0} not found' % method
            return mi.Invoke(instance, params)

    # Obtain an instance to call ProcessMethodBodies private methods
    pmb = Pmb(self)

    override def Initialize(context as Boo.Lang.Compiler.CompilerContext):
        super(context)

    #override protected def ProcessInvocationOnUnknownCallableExpression(node as MethodInvocationExpression):
    #    print node
    override def OnMethodInvocationExpression(node as MethodInvocationExpression):

        super(node)

        # HACK: The ICallable interface declares a generic Call method with object arguments
        #       and return type. In order to support jQuery style APIs we detect the ICallable
        #       trying to modify it by using a better match among the methods named Call in
        #       the declarting type.
        # TODO: Perhaps it's better to keep ICallable implementation as is and use an attribute
        #       or a custom interface to signal this behaviour.
        target = node.Target as MemberReferenceExpression
        if target and target.Entity == MethodCache.ICallable_Call:
            # Find the original declaring type and fetch all its Call methods
            type = target.Target.ExpressionType as Reflection.ExternalType
            methods = List[of IMethod]()
            for member in type.GetMembers():
                if member isa ExternalMethod and member.Name == 'Call':
                    methods.Add(member)

            # Resolve the best match among the Call methods for the given params
            args = (node.Arguments[0] as ArrayLiteralExpression).Items
            resolved as ExternalMethod = CallableResolutionService.ResolveCallableReference(args, methods.ToArray())
            if resolved:
                BindExpressionType(node, resolved.ReturnType)
                BindExpressionType(node.Target, resolved.Type)

        # Detect invokes for internal callables
        # TODO: We could move this to a custom step in order to allow it to be optional
        #       In debug mode we might want to dispatch via our runtime to detect and
        #       properly report errors.
        if target and target.Entity:
            targetType = target.Entity as Internal.InternalMethod
            if targetType:
                invoke as IMethod = ResolveMethod(targetType.DeclaringType, 'Invoke')
                if invoke is targetType:
                    node.Target = target.Target

        return

    override def LeaveUnaryExpression(node as UnaryExpression):
        # Increment/Decrement operators are resolved using a fairly complex eval. Since
        # JS supports this operators natively we can simplify the case by just binding
        # the correct type to the expression.
        if node.Operator in (UnaryOperatorType.Increment, UnaryOperatorType.Decrement,
                             UnaryOperatorType.PostDecrement, UnaryOperatorType.PostIncrement):
            # TODO: This doesn't take into account any operator overloading logic

            # Bind the type of the operand to the operation
            node.ExpressionType = GetExpressionType(node.Operand)
            return

        # Fall back to the original logic
        super(node)

    override def ProcessBuiltinInvocation(node as MethodInvocationExpression, func as BuiltinFunction):
        # Replace len(val) with val.length
        if func.FunctionType == BuiltinFunctionType.Len:
            target = node.Arguments[0]
            result = MemberReferenceExpression(node.LexicalInfo, target, 'length')
            result.ExpressionType = TypeSystemServices.IntType

            node.ParentNode.Replace(node, result)
            return

        super(node, func)

    override def LeaveDeclarationStatement(node as DeclarationStatement):
        # If the declaration has been annotated as global via the `global` macro
        # we must avoid generating a declaration for it, either removing it
        # or just setting a value to it if the used supplied an initializer
        decl = node.Declaration
        if decl.ContainsAnnotation('global'):
            if not decl.Type:
                decl.Type = CodeBuilder.CreateTypeReference(decl.LexicalInfo, TypeSystemServices.DuckType)

            type = GetType(decl.Type)
            entity as Internal.InternalLocal = DeclareLocal(node, decl.Name, type, false)
            entity.OriginalDeclaration = decl

            if node.Initializer:
                node.ReplaceBy(
                    ExpressionStatement(
                        CodeBuilder.CreateAssignment(
                            node.LexicalInfo,
                            CodeBuilder.CreateReference(entity),
                            node.Initializer)
                        )
                    )
            else:
                (node.ParentNode as Block).Statements.Remove(node)

            return

        super(node)

    override protected def BindBinaryExpression(node as BinaryExpression):
        # Boo would directly allow all arithmetic on number types. We have to
        # specifically process those that are not valid in javascript

        ltype = GetExpressionType(node.Left)
        rtype = GetExpressionType(node.Right)

        if node.Operator == BinaryOperatorType.Exponentiation:
            #pmb.BindNullableOperation(node)
            if not pmb.ResolveOperator(node):
                pmb.InvalidOperatorForTypes(node)
        elif node.Operator == BinaryOperatorType.Division and \
             TypeSystemServices.IsIntegerNumber(ltype) and \
             TypeSystemServices.IsIntegerNumber(rtype):

             #pmb.BindNullableOperator(node)
             if not pmb.ResolveOperator(node):
                pmb.InvalidOperatorForTypes(node)
        else:
            super(node)

namespace BooJs.Compiler.Steps

import Boo.Lang.Compiler.Steps
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.TypeSystem
import Boo.Lang.Compiler.TypeSystem(ExternalMethod, IExternalEntity)

import Boo.Lang.Environments
import BooJs.Compiler.TypeSystem(RuntimeMethodCache)

class OverrideProcessMethodBodies(ProcessMethodBodiesWithDuckTyping):
""" Overrides some methods of the step to skip some modification made originally
"""

    class Pmb(IQuackFu):
    """ Uses reflection to call private methods in the ancestor class ProcessMethodBodies via a
        IQuackFu api.
    """
        pmb = typeof(ProcessMethodBodies)
        instance as ProcessMethodBodies
        def constructor(instance):
            self.instance = instance

        def QuackGet(prop as string, args as (object)):
            pass
        def QuackSet(prop as string, args as (object), val as object):
            pass
        def QuackInvoke(method as string, params as (object)) as object:
            types = array(System.Type, param.GetType() for param in params)
            mi = pmb.GetMethod(method, BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance,
                               null, types, null)
            if not mi:
                raise 'Private method {0} not found' % method
            return mi.Invoke(instance, params)

    # Obtain an instance to call ProcessMethodBodies private methods
    pmb = Pmb(self)

    override def Initialize(context as Boo.Lang.Compiler.CompilerContext):
        super(context)

    #override protected def ProcessInvocationOnUnknownCallableExpression(node as MethodInvocationExpression):
    #    print node
    override def OnMethodInvocationExpression(node as MethodInvocationExpression):

        super(node)

        # HACK: The ICallable interface declares a generic Call method with object arguments
        #       and return type. In order to support jQuery style APIs we detect the ICallable
        #       trying to modify it by using a better match among the methods named Call in
        #       the declarting type.
        target = node.Target as MemberReferenceExpression
        if target and target.Entity == MethodCache.ICallable_Call:
            # Find the original declaring type and fetch all its Call methods
            type = target.Target.ExpressionType as Reflection.ExternalType
            methods = List[of IMethod]()
            for member in type.GetMembers():
                if member isa ExternalMethod and member.Name == 'Call':
                    methods.Add(member)

            # Resolve the best match among the Call methods for the given params
            args = (node.Arguments[0] as ArrayLiteralExpression).Items
            resolved as ExternalMethod = CallableResolutionService.ResolveCallableReference(args, methods.ToArray())
            if resolved:
                BindExpressionType(node, resolved.ReturnType)
                BindExpressionType(node.Target, resolved.Type)

        # Detect invokes for internal callables
        if target and target.Entity:
            targetType = target.Entity as Internal.InternalMethod
            if targetType:
                invoke as IMethod = ResolveMethod(targetType.DeclaringType, 'Invoke')
                if invoke is targetType:
                    node.Target = target.Target

        return

    override def LeaveUnaryExpression(node as UnaryExpression):
        # Increment/Decrement operators are resolved using a fairly complex eval. Since
        # JS supports this operators natively we can simplify the case by just binding
        # the correct type to the expression.
        if node.Operator in (UnaryOperatorType.Increment, UnaryOperatorType.Decrement,
                             UnaryOperatorType.PostDecrement, UnaryOperatorType.PostIncrement):
            # TODO: This doesn't take into account any operator overloading logic

            # Bind the type of the operand to the operation
            node.ExpressionType = GetExpressionType(node.Operand)
            return

        # Fall back to the original logic
        super(node)

    override def ProcessBuiltinInvocation(node as MethodInvocationExpression, func as BuiltinFunction):
        # Replace len(val) with val.length
        if func.FunctionType == BuiltinFunctionType.Len:
            target = node.Arguments[0]
            result = MemberReferenceExpression(node.LexicalInfo, target, 'length')
            result.ExpressionType = TypeSystemServices.IntType

            node.ParentNode.Replace(node, result)
            return

        super(node, func)

    override def LeaveDeclarationStatement(node as DeclarationStatement):
        # If the declaration has been annotated as global via the `global` macro
        # we must avoid generating a declaration for it, either removing it
        # or just setting a value to it if the used supplied an initializer
        decl = node.Declaration
        if decl.ContainsAnnotation('global'):
            if not decl.Type:
                decl.Type = CodeBuilder.CreateTypeReference(decl.LexicalInfo, TypeSystemServices.DuckType)

            type = GetType(decl.Type)
            entity as Internal.InternalLocal = DeclareLocal(node, decl.Name, type, false)
            entity.OriginalDeclaration = decl

            if node.Initializer:
                node.ReplaceBy(
                    ExpressionStatement(
                        CodeBuilder.CreateAssignment(
                            node.LexicalInfo,
                            CodeBuilder.CreateReference(entity),
                            node.Initializer)
                        )
                    )
            else:
                (node.ParentNode as Block).Statements.Remove(node)

            return

        super(node)

    override protected def BindBinaryExpression(node as BinaryExpression):
        # Boo would directly allow all arithmetic on number types. We have to
        # specifically process those that are not valid in javascript

        ltype = GetExpressionType(node.Left)
        rtype = GetExpressionType(node.Right)

        if node.Operator == BinaryOperatorType.Exponentiation:
            #pmb.BindNullableOperation(node)
            if not pmb.ResolveOperator(node):
                pmb.InvalidOperatorForTypes(node)
        elif node.Operator == BinaryOperatorType.Division and \
             TypeSystemServices.IsIntegerNumber(ltype) and \
             TypeSystemServices.IsIntegerNumber(rtype):

             #pmb.BindNullableOperator(node)
             if not pmb.ResolveOperator(node):
                pmb.InvalidOperatorForTypes(node)
        else:
            super(node)


Changing behavior of growing involved rules in indirect left recursion.

namespace Boo.OMeta

import Boo.Adt
import System.Collections.Generic
import System.Collections.Specialized

interface OMetaEvaluationContext:
	def Eval(rule as string, input as OMetaInput) as OMetaMatch

class NullEvaluationContext(OMetaEvaluationContext):

	final _grammar as OMetaGrammar

	def constructor([required] grammar as OMetaGrammar):
		_grammar = grammar

	def Eval(rule as string, input as OMetaInput) as OMetaMatch:
		return _grammar.Apply(self, rule, input)

class RuleSet(Set[of string]):
	def constructor():
		super()
	def constructor(other as RuleSet):
		super(other)

data LeftRecursion(@seed as OMetaMatch, rule as string, @head as Head)
data Head(rule as string, involvedSet as RuleSet, @evalSet as RuleSet)

class OMetaEvaluationContextImpl(OMetaEvaluationContext):
"""
Evaluation context with full support
for indirectly/mutually left recursive rules.
"""

	final _memo = HybridDictionary()

	final _lrStack = Stack[of LeftRecursion]()

	final _heads = HybridDictionary()

	final _grammar as OMetaGrammar

	def constructor([required] grammar as OMetaGrammar):
		_grammar = grammar

	def Eval(rule as string, input as OMetaInput) as OMetaMatch:
		memoKey = MemoKey(rule, input)
		m = Recall(memoKey)
		if m is null:
			lr = LeftRecursion(Fail(input), rule, null)
			_lrStack.Push(lr)
			_memo[memoKey] = lr
			ans = Apply(rule, input)
			_lrStack.Pop()
			if lr.head is not null:
				lr.seed = ans
				return LRAnswer(memoKey, lr)
			else:
				_memo[memoKey] = ans
				return ans
		else:
			lr = m as LeftRecursion
			if lr is not null:
				SetupLR(rule, lr)
				return lr.seed
			else:
				return m

	private def Apply(rule as string, input as OMetaInput):
		return _grammar.Apply(self, rule, input)

	def Fail(input as OMetaInput):
		return FailedMatch(input, LeftRecursionFailure())

	def SetupLR(rule as string, lr as LeftRecursion):
		if lr.head is null:
			lr.head = Head(rule, RuleSet(), RuleSet())

		head = lr.head
		for s in _lrStack:
			break if s.head is head
			s.head = head
			head.involvedSet.Add(s.rule)

	def LRAnswer(memoKey as MemoKey, lr as LeftRecursion):
		h = lr.head
		r = memoKey.rule
		if h.rule != r:
			return lr.seed
		_memo[memoKey] = lr.seed
		if lr.seed isa FailedMatch:
			return lr.seed
		return GrowLR(memoKey, lr)

	def Recall(memoKey as MemoKey):
		m = _memo[memoKey]
		input = memoKey.input
		h as Head = _heads[input]
		if h is null:
			return m

		r = memoKey.rule
		if m is null and h.rule != r and not h.involvedSet.Contains(r):
			return Fail(input)
		if h.evalSet.Contains(r):
			h.evalSet.Remove(r)
			m = Apply(r, input)
			if _memo[memoKey] is null or (not (m isa FailedMatch and _memo[memoKey] isa SuccessfulMatch) ):
				_memo[memoKey] = m
			else:
				return _memo[memoKey]
		return m

	def GrowLR(memoKey as MemoKey, lr as LeftRecursion):
		rule = memoKey.rule

		input = memoKey.input
		h = lr.head
		lastSuccessfulMatch as OMetaMatch = lr.seed

		_heads[input] = h
		while true:
			h.evalSet = RuleSet(h.involvedSet)
			m = Apply(rule, input)
			if m isa FailedMatch or m.Input.Position <= lastSuccessfulMatch.Input.Position:
				break
			_memo[memoKey] = lastSuccessfulMatch = m

		_heads.Remove(input)
		return lastSuccessfulMatch

namespace Boo.OMeta

import Boo.Adt
import System.Collections.Generic
import System.Collections.Specialized

interface OMetaEvaluationContext:
	def Eval(rule as string, input as OMetaInput) as OMetaMatch

class NullEvaluationContext(OMetaEvaluationContext):

	final _grammar as OMetaGrammar

	def constructor([required] grammar as OMetaGrammar):
		_grammar = grammar

	def Eval(rule as string, input as OMetaInput) as OMetaMatch:
		return _grammar.Apply(self, rule, input)

class RuleSet(Set[of string]):
	def constructor():
		super()
	def constructor(other as RuleSet):
		super(other)

data LeftRecursion(@seed as OMetaMatch, rule as string, @head as Head)
data Head(rule as string, involvedSet as RuleSet, @evalSet as RuleSet)

class OMetaEvaluationContextImpl(OMetaEvaluationContext):
"""
Evaluation context with full support
for indirectly/mutually left recursive rules.
"""

	final _memo = HybridDictionary()

	final _lrStack = Stack[of LeftRecursion]()

	final _heads = HybridDictionary()

	final _grammar as OMetaGrammar

	def constructor([required] grammar as OMetaGrammar):
		_grammar = grammar

	def Eval(rule as string, input as OMetaInput) as OMetaMatch:
		memoKey = MemoKey(rule, input)
		m = Recall(memoKey)
		if m is null:
			lr = LeftRecursion(Fail(input), rule, null)
			_lrStack.Push(lr)
			_memo[memoKey] = lr
			ans = Apply(rule, input)
			_lrStack.Pop()
			if lr.head is not null:
				lr.seed = ans
				return LRAnswer(memoKey, lr)
			else:
				_memo[memoKey] = ans
				return ans
		else:
			lr = m as LeftRecursion
			if lr is not null:
				SetupLR(rule, lr)
				return lr.seed
			else:
				return m

	private def Apply(rule as string, input as OMetaInput):
		return _grammar.Apply(self, rule, input)

	def Fail(input as OMetaInput):
		return FailedMatch(input, LeftRecursionFailure())

	def SetupLR(rule as string, lr as LeftRecursion):
		if lr.head is null:
			lr.head = Head(rule, RuleSet(), RuleSet())

		head = lr.head
		for s in _lrStack:
			break if s.head is head
			s.head = head
			head.involvedSet.Add(s.rule)

	def LRAnswer(memoKey as MemoKey, lr as LeftRecursion):
		h = lr.head
		r = memoKey.rule
		if h.rule != r:
			return lr.seed
		_memo[memoKey] = lr.seed
		if lr.seed isa FailedMatch:
			return lr.seed
		return GrowLR(memoKey, lr)

	def Recall(memoKey as MemoKey):
		m = _memo[memoKey]
		input = memoKey.input
		h as Head = _heads[input]
		if h is null:
			return m

		r = memoKey.rule
		if m is null and h.rule != r and not h.involvedSet.Contains(r):
			return Fail(input)
		if h.evalSet.Contains(r):
			h.evalSet.Remove(r)
			m = Apply(r, input)
			_memo[memoKey] = m
		return m

	def GrowLR(memoKey as MemoKey, lr as LeftRecursion):
		rule = memoKey.rule
		input = memoKey.input
		h = lr.head
		lastSuccessfulMatch as OMetaMatch = lr.seed

		_heads[input] = h
		while true:
			h.evalSet = RuleSet(h.involvedSet)
			m = Apply(rule, input)
			if m isa FailedMatch or m.Input.Position <= lastSuccessfulMatch.Input.Position:
				break
			_memo[memoKey] = lastSuccessfulMatch = m

		_heads.Remove(input)
		return lastSuccessfulMatch


소스 주석 처리 수정.

소스 주석 처리 수정.

import UnityEngine
import System.Collections

class HelloBoo (MonoBehaviour):
	public obj as GameObject

	def Start ():
		pass

	def Update ():
		pass

	def PrintOnConsol(str as string) :
		print(str)

	def PrintHelloBoo():
		PrintOnConsol(GetHelloBoo())

	def GetHelloBoo() as string :
		return "HelloBoo"

	def SendMessageToCShapComponent():
		//print "SendMessageToCShapComponent"	// Don't Show on consol
		print ("SendMessageToCShapComponent")
		obj = GameObject.Find("GameObjectWithC#")
		obj.GetComponent("CShapDemo").SendMessage("CallFromHellBoo", "Call OK !!!")

	def ReturnValue(obj as Hashtable):
		obj["Return"] = "Return String"

import UnityEngine
import System.Collections

class HelloBoo (MonoBehaviour):
	public obj as GameObject

	def Start ():
		pass

	def Update ():
		pass

	def PrintOnConsol(str as string) :
		print(str)

	def PrintHelloBoo():
		PrintOnConsol(GetHelloBoo())

	def GetHelloBoo() as string :
		return "HelloBoo"

	def SendMessageToCShapComponent():
		// -> print "SendMessageToCShapComponent"	// Don't Show on consol
		print ("SendMessageToCShapComponent")
		obj = GameObject.Find("GameObjectWithC#")
		obj.GetComponent("CShapDemo").SendMessage("CallFromHellBoo", "Call OK !!!")

	def ReturnValue(obj as Hashtable):
		obj["Return"] = "Return String"