Fix return statements for block expressions inside constructors

namespace BooJs.Compiler.Visitors

from Boo.Lang.Environments import My
from Boo.Lang.Compiler.Ast import *
from Boo.Lang.Compiler.TypeSystem import *
from Boo.Lang.PatternMatching import *

from BooJs.Compiler.Utils import *
from BooJs.Compiler import UniqueNameProvider, Mozilla as Moz
from BooJs.Compiler.TypeSystem import RuntimeMethodCache

class MozillaAstVisitor(FastDepthFirstVisitor):
"""
Transforms a Boo AST into a Mozilla AST
"""
    # Holds the latest conversion from a Boo node to a Moz node
    _return as Moz.Node

    protected def Return(node as Moz.Node):
        _return = node

    protected def Apply(node as Node) as Moz.Node:
        _return = null
        Visit node
        return _return

    protected def loc(node as Node):
        obj = Moz.SourceLocation()
        obj.source = node.LexicalInfo.FileName
        obj.start = Moz.SourceLocation.Position(line: node.LexicalInfo.Line, column: node.LexicalInfo.Column-1)

        # Compute an approximate end position if not available
        if node.EndSourceLocation and node.EndSourceLocation.Line >= 0:
            line, column = (node.EndSourceLocation.Line, node.EndSourceLocation.Column-1)
        else:
            parts = node.ToCodeString().Split(char('\n'))
            line, column = (node.LexicalInfo.Line, node.LexicalInfo.Column-1)
            if len(parts) > 1:
                line += len(parts) - 1
                column = 0
            column += len(parts[-1])

        obj.end = Moz.SourceLocation.Position(line: line, column: column)

        return obj

    def Run(node as Node) as Moz.Node:
        Visit node
        return _return

    def OnBoolLiteralExpression(node as BoolLiteralExpression):
        Return Moz.Literal(loc: loc(node), value: node.Value)

    def OnNullLiteralExpression(node as NullLiteralExpression):
        Return Moz.Literal(loc: loc(node), value: null)

    def OnIntegerLiteralExpression(node as IntegerLiteralExpression):
        n as Moz.IExpression
        if node.Value < 0:
            n = Moz.Literal(loc: loc(node), value: -node.Value)
            n = Moz.UnaryExpression(loc: loc(node), operator: '-', argument: n)
         else:
            n = Moz.Literal(loc: loc(node), value: node.Value)

        Return n

    def OnDoubleLiteralExpression(node as DoubleLiteralExpression):
        n as Moz.IExpression
        if node.Value < 0:
            n = Moz.Literal(loc: loc(node), value: -node.Value)
            n = Moz.UnaryExpression(loc: loc(node), operator: '-', argument: n)
         else:
            n = Moz.Literal(loc: loc(node), value: node.Value)

        Return n

    def OnStringLiteralExpression(node as StringLiteralExpression):
        Return Moz.Literal(loc: loc(node), value: node.Value)

    def OnRELiteralExpression(node as RELiteralExpression):
        Return Moz.Literal(loc: loc(node), value: node.Value, regexp: true)

    def OnSelfLiteralExpression(node as SelfLiteralExpression):
    """ If it's inside a constructor we keep the `self` identifier since they
        act as a factory. Otherwise we create a `this` keyword
    """
        if node.GetAncestor[of Constructor]():
            Return Moz.Identifier(loc: loc(node), 'self')
        else:
            Return Moz.ThisExpression(loc: loc(node))

    def OnListLiteralExpression(node as ListLiteralExpression):
        n = Moz.ArrayExpression(loc: loc(node))
        for itm in node.Items:
            exp = Apply(itm)
            n.elements.Add(exp)
        Return n

    def OnHashLiteralExpression(node as HashLiteralExpression):
        n = Moz.ObjectExpression(loc: loc(node))
        for pair in node.Items:
            prop = Moz.ObjectExpression.Prop(key: Apply(pair.First), value: Apply(pair.Second))
            n.properties.Add(prop)
        Return n

    def OnEnumDefinition(node as EnumDefinition):
        # Create an object with the enum values
        o = Moz.ObjectExpression(loc: loc(node))
        for member as EnumMember in node.Members:
            assert member.Initializer != null, 'Enum definition without an initializer value!'
            o.properties.Add(
                Moz.ObjectExpression.Prop(
                    key: Moz.Literal(value: member.Name),
                    value: Apply(member.Initializer)
                )
            )

        # Assign the object to a variable
        Return CreateVar(node, node.Name, o)

    protected def CreateVar(node as Node, name as string, value as Moz.IExpression):
        v = Moz.VariableDeclarator(loc: loc(node))
        v.id = Moz.Identifier(loc: loc(node), name)
        v.init = value
        n = Moz.VariableDeclaration(loc: loc(node), kind: 'var')
        n.declarations.Add(v)
        return n

    protected def CreateAssign(loc as Moz.SourceLocation, ident as string, value as Moz.IExpression):
        a = Moz.AssignmentExpression(loc: loc, operator:'=')
        a.left = Moz.Identifier(loc: loc, name: ident)
        a.right = value
        return a

    def OnModule(node as Module):
        n = Moz.Program(loc: loc(node))

        deps = List of Moz.IExpression() { Moz.Literal('Boo') }
        refs = List of Moz.IPattern() { Moz.Identifier('Boo') }

        # Get namespace mapping annotations
        mapping as Hash = node['nsmapping']
        asmrefs as Hash = node['nsasmrefs']

        for ns in mapping.Keys:
            if ns in asmrefs:
                context().TraceInfo('Namespace mapping {0} => {1}', ns, mapping[ns] + ':' + asmrefs[ns])
                deps.Add( Moz.Literal(ns + ':' + asmrefs[ns]) )
            else:
                context().TraceInfo('Namespace mapping {0} => {1}', ns, mapping[ns])
                deps.Add( Moz.Literal(ns) )
            refs.Add( Moz.Identifier(mapping[ns]) )

        # Use Boo.define to bootstrap the module contents
        call = Moz.CallExpression()
        call.callee = Moz.MemberExpression(
            object: Moz.Identifier(name: 'Boo'),
            property: Moz.Identifier(name: 'define')
        )
        call.arguments.Add(Moz.Literal(value: (node.Namespace.Name if node.Namespace else '')))
        call.arguments.Add(Moz.ArrayExpression(elements: deps))
        fn = Moz.FunctionExpression(params: refs, body: Moz.BlockStatement())
        call.arguments.Add(fn)
        n.body.Add(Moz.ExpressionStatement(call))

        st as Moz.IStatement
        for member in node.Members:
            st = Apply(member)
            if st is null:
                continue

            if st isa Moz.BlockStatement:
                fn.body.body += (st as Moz.BlockStatement).body
            else:
                fn.body.body.Add(st)

        # Export public symbols
        # js: exports.symbol = symbol
        members = TypeMemberCollection()
        for member in node.Members:
            if member.IsSynthetic and member.Name.EndsWith('Module'):
                members.AddRange((member as ClassDefinition).Members)
            else:
                members.Add(member)

        for member in members:
            continue unless member.IsPublic and not member.IsInternal

            expr = Moz.BinaryExpression(
                left: Moz.MemberExpression(
                    object: Moz.Identifier('exports'),
                    property: Moz.Identifier(member.Name)
                ),
                operator: '=',
                right: Moz.Identifier(member.Name)
            )

            st = Moz.ExpressionStatement(expr)
            fn.body.body.Add(st)

        if not node.Globals.IsEmpty:
            rcall = Moz.CallExpression()
            rcall.callee = Moz.MemberExpression(
                object: Moz.Identifier(name: 'Boo'),
                property: Moz.Identifier(name: 'require')
            )
            # Clone the deps list replacing 'exports' by the namespace
            deps = deps.GetRange(0, len(deps))
            #deps[0] = Moz.Literal((node.Namespace.Name if node.Namespace else ''))
            rcall.arguments.Add(Moz.ArrayExpression(elements: deps))
            fn = Moz.FunctionExpression(params: refs, body: Moz.BlockStatement())
            rcall.arguments.Add(fn)
            n.body.Add(Moz.ExpressionStatement(rcall))

            for global in node.Globals.Statements:
                st = Apply(global)
                fn.body.body.Add(st)

        Return n

    def OnClassDefinition(node as ClassDefinition):
        n = Moz.BlockStatement()

        # Detect module class and output its members directly
        if node.IsSynthetic and node.IsFinal and node.Name.EndsWith('Module'):
            members = [m for m in node.Members if m.NodeType != NodeType.Constructor]
            st as Moz.IStatement
            for member in members:
                match Apply(member):
                    case null:
                        continue
                    case bs=Moz.BlockStatement():
                        n.body += bs.body
                    case _:
                        n.body.Add(_)

            Return n
            return

        # TODO: Handle visibility (internals should not be exposed)

        # Bar = (function (_super_) {
        #     // Constructor
        #     function Bar(_init_) {
        #         // Call constructor (for calls from JS or duck code)
        #         // TODO: Use `this instanceof Bar` test instead?
        #         if (_init_ !== Boo.INIT)
        #             return Boo.overload(arguments, [ [], ['string', 'int'] ], [Foo.ctor$0, Foo.ctor$1]);
        #
        #         // Initialize fields (TODO: can this be moved to the prototype?)
        #         this.field = '';
        #
        #         // Bind public instance methods (NOTE: also inherited) (TODO: Support annotation to avoid binding)
        #         this.foo = Boo.bind(this.foo, this);
        #     }
        #
        #     // Inherit "static" members
        #     Boo.extend(Bar, _super_);
        #
        #     // Static constructor factories (called from typed code)
        #     Bar.ctor$0 = function Bar$ctor$0() {
        #         var self = this instanceof Foo ? this : new Foo(Boo.INIT);
        #         // Call parent constructor
        #         _super_.call(self);
        #         // Call constructor overload
        #         Bar.ctor$1.call(self, 'foo')
        #         // constructor logic
        #         ...
        #         return self;
        #     }
        #
        #     // Static members
        #     Bar.bars = function Bar$$bars() {};
        #
        #     // Setup inheritance (TODO: move this into Boo.extend?)
        #     Bar.prototype = Boo.create(_super_.prototype);
        #     Bar.prototype.constructor = Bar;
        #
        #     // Reflect metadata
        #     Bar.prototype.$boo$interfaces = [IEnumerator];
        #     Bar.prototype.$boo$super = _super_
        #
        #     // Alias static members to instance (for JavaScript access)
        #     // TODO: Shall we differ from Boo an only allow static access on type?
        #     Bar.prototype.bars = Bar.bars;
        #
        #     // Instance members
        #     Bar.prototype.foo = function Bar$foo() {
        #         // Call parent method
        #         _super_.prototype.foo.call(this);
        #     };
        #
        #     // Static constructor logic (in .Net this only runs when the type is first accessed)
        #     ...
        #
        #     return Bar;
        # })(Foo);

        # TODO: Support constructor overloading
        constructors = [c for c in node.Members if c.NodeType == NodeType.Constructor]
        if len(constructors) > 1:
            raise "Constructor overloading not supported yet"

        block = Moz.BlockStatement()

        # Constructor
        # js: function Bar(_init_) {}
        cons = Moz.FunctionDeclaration(loc: loc(node))
        cons.params = List[of Moz.IPattern]() { Moz.Identifier('_init_') }
        cons.body = Moz.BlockStatement()
        cons.id = Moz.Identifier(node.Name)
        block.Add(cons)

        # Call constructor factory
        # js: if (_init_ !== Boo.INIT) return Boo.overload(arguments, [ ... ], [ cons$0, cons$1 ])
        ifst = Moz.IfStatement(loc: loc(node))
        ifst.test = Moz.BinaryExpression(
            operator: '!==',
            left: Moz.Identifier('_init_'),
            right: Moz.Identifier('Boo.INIT')
        )
        # TODO: Build a proper overload call
        ifst.consequent = Moz.ReturnStatement(
            argument: Moz.CallExpression(
                callee: Moz.Identifier(node.Name + '.constructor.apply'),
                arguments: List[of Moz.IExpression]() {
                    Moz.Literal(value: null),
                    Moz.Identifier('arguments')
                }
            )
            # argument: Moz.CallExpression(
            #     callee: Moz.Identifier('Boo.overload'),
            #     arguments: List[of Moz.IExpression]() {
            #         Moz.Identifier('arguments'),
            #         Moz.Literal(value: []),
            #         Moz.Literal(value: [])
            #     }
            # )
        )
        cons.body.Add(ifst)

        # Bind instance methods
        # js: this.foo = Boo.bind(this.foo, this);
        for m in node.Members:
            continue unless m.NodeType == NodeType.Method and not m.IsStatic
            assign = CreateAssign(loc(m), "this.$(m.Name)", Moz.CallExpression(
                Moz.Identifier('Boo.bind'),
                Moz.MemberExpression(
                    object: Moz.ThisExpression(),
                    property: Moz.Identifier(m.Name)
                ),
                Moz.ThisExpression()
            ))
            cons.body.Add(assign)

        # Events
        # TODO: Do it properly (ie: static events?)
        for e as Event in [m for m in node.Members if m.NodeType == NodeType.Event]:
            assign = CreateAssign(loc(e), "this.$(e.Name)", null)
            assign.right = Moz.CallExpression(callee: Moz.Identifier('Boo.event'))
            cons.body.Add(assign)

        # Initialize fields
        # TODO: Shouldn't fields with primitive initializers be moved to the prototype?
        # TODO: Boo already moves field initialization to the constructors
        for f as Field in [m for m in node.Members if m.NodeType == NodeType.Field]:
            # TODO: Define fields without initializers too
            if f.Initializer:
                assign = CreateAssign(loc(f), "this.$(f.Name)", Apply(f.Initializer))
                cons.body.Add(assign)

        # Inherit "static" members from the parent type
        # js: Boo.extend(Bar, _super_)
        block.Add(Moz.CallExpression(
            Moz.Identifier('Boo.extend'),
            Moz.Identifier(node.Name),
            Moz.Identifier('_super_')
        ))

        # Include constructor factories as private scoped functions
        members = [m for m in node.Members if m.NodeType == NodeType.Constructor]
        for c as Constructor in members:
            continue if not c

            assign = CreateAssign(loc(node), "$(node.Name).$(c.Name)", null)
            fn = Apply(c) as Moz.FunctionDeclaration
            assign.right = Moz.FunctionExpression(
                loc: fn.loc,
                id: Moz.Identifier(node.FullName.Replace('.', '$') + '$$' + c.Name),
                params: fn.params,
                body: fn.body
            )
            block.Add(assign)

        # Static members
        # js: Bar.sbar = function Bar$$sbar (arg) { }
        # TODO: Handle static fields too!
        for m in node.Members:
            continue unless m.NodeType == NodeType.Method and m.IsStatic
            assign = CreateAssign(loc(m), "$(node.Name).$(m.Name)", null)
            fn = Apply(m) as Moz.FunctionDeclaration
            assign.right = Moz.FunctionExpression(
                loc: fn.loc,
                id: Moz.Identifier(node.FullName.Replace('.', '$') + '$$' + m.Name),
                params: fn.params,
                body: fn.body
            )
            block.Add(assign)

        # Setup inheritance
        # js: Bar.prototype = Boo.create(_super_.prototype)
        assign = CreateAssign(loc(node), "$(node.Name).prototype", Moz.CallExpression(
            Moz.Identifier('Boo.create'),
            Moz.Identifier('_super_.prototype')
        ))
        block.Add(assign)
        # js: Bar.prototype.constructor = Bar
        assign = CreateAssign(loc(node), "$(node.Name).prototype.constructor", Moz.Identifier(node.Name))
        block.Add(assign)

        # Metadata
        # js: Bar.prototype.$boo$interfaces = [ IEnumerable ]
        interfaces = Moz.ArrayExpression()
        for t in node.BaseTypes:
            continue if t is node.BaseTypes.First
            continue if typeSystem().IsSystemObject(t.Entity)
            interfaces.elements.Add(
                Moz.Identifier(t.Entity.FullName)
            )
        assign = CreateAssign(loc(node), node.Name + '.prototype.$boo$interfaces', interfaces)
        block.Add(assign)
        # js: Bar.prototype.$boo$super = _super_
        assign = CreateAssign(loc(node), node.Name + '.prototype.$boo$super', Moz.Identifier('_super_'))
        block.Add(assign)

        # Alias static members to instance (for JavaScript access)
        # js: Bar.prototype.bars = Bar.bars;
        # TODO: Shall we differ from Boo an only allow static access on type?
        for m in node.Members:
            continue unless m.NodeType == NodeType.Method and m.IsStatic
            assign = CreateAssign(loc(m), node.Name + '.prototype.' + m.Name, Moz.Identifier(node.Name + '.' + m.Name))
            block.Add(assign)

        # Instance members
        # js: Bar.prototype.bar = function Bar$bar (arg) { }
        members = [m for m in node.Members if m.NodeType == NodeType.Method and not m.IsStatic]
        for method as Method in members:
            assign = CreateAssign(loc(node), node.Name + '.prototype.' + method.Name, null)
            fn = Apply(method) as Moz.FunctionDeclaration
            assign.right = Moz.FunctionExpression(
                loc: fn.loc,
                id: Moz.Identifier(node.FullName.Replace('.', '$') + '$' + fn.id.name),
                params: fn.params,
                body: fn.body
            )
            block.Add(assign)

        # TODO: Include static constructor logic to run when creating the type

        # Wrap everything in a self calling function and assign to a variable
        # js: Bar = (function(_super_){ ... return Bar; })(Object)
        block.body.Add(
            Moz.ReturnStatement(argument: Moz.Identifier(node.Name))
        )

        func = Moz.FunctionExpression(body:block)
        func.params = List of Moz.IPattern() { Moz.Identifier('_super_') }
        call = Moz.CallExpression(callee:func)
        call.arguments.Add(
            Moz.Identifier(
                ('Object' if typeSystem().IsSystemObject(node.BaseTypes.First.Entity)
                    else node.BaseTypes.First.Entity.FullName)
            )
        )

        Return CreateVar(node, node.Name, call)

    def OnField(node as Field):
        pass

    def OnConstructor(node as Constructor):
        n = Moz.FunctionDeclaration(loc: loc(node))
        n.id = Moz.Identifier(loc: loc(node), name: node.FullName.Replace('.', '$'))
        for param in node.Parameters:
            p = Moz.Identifier(loc: loc(param), name: param.Name)
            n.params.Add(p)

        n.body = Apply(node.Body)

        // js: var self = this instanceof Foo ? this : new Foo(Boo.INIT);
        n.body.body.Insert(0, CreateVar(
            node,
            'self',
            Moz.ConditionalExpression(
                test: Moz.BinaryExpression(
                    operator: 'instanceof',
                    left: Moz.ThisExpression(),
                    right: Moz.Identifier(node.DeclaringType.Name)
                ),
                consequent: Moz.ThisExpression(),
                alternate: Moz.NewExpression(
                    _constructor: Moz.Identifier(node.DeclaringType.Name),
                    arguments: List[of Moz.IExpression]() {
                        Moz.Identifier('Boo.INIT')
                    }
                )
            )
        ))
        // js: return self
        n.body.Add(
            Moz.ReturnStatement(
                argument: Moz.Identifier('self')
            )
        )

        Return n

    def OnMethod(node as Method):
        n = Moz.FunctionDeclaration(loc: loc(node))
        n.id = Moz.Identifier(loc: loc(node), name: node.Name)
        for param in node.Parameters:
            p = Moz.Identifier(loc: loc(param), name: param.Name)
            n.params.Add(p)

        n.body = Apply(node.Body)
        Return n

    def OnDeclarationStatement(node as DeclarationStatement):
        # TODO: Generate type annotated comments
        Return CreateVar(node, node.Declaration.Name, Apply(node.Initializer))

    def OnBlock(node as Block):
        b = Moz.BlockStatement(loc: loc(node))
        for st in node.Statements:
            mst = Apply(st) as Moz.IStatement
            if mst:
                b.body.Add(mst)

        Return b

    def OnExpressionStatement(node as ExpressionStatement):
        n = Moz.ExpressionStatement(loc: loc(node))
        n.expression = Apply(node.Expression)
        Return n

    def OnReturnStatement(node as ReturnStatement):
        n = Moz.ReturnStatement(loc: loc(node))

        if node.GetAncestor[of BlockExpression]():
            n.argument = Apply(node.Expression)
        # Inside constructors always return `self`
        elif node.GetAncestor[of Constructor]():
            n.argument = Moz.Identifier('self')
        else:
            n.argument = Apply(node.Expression)

        Return n

    def OnIfStatement(node as IfStatement):

        ifst = Moz.IfStatement(loc: loc(node))
        ifst.test = Apply(node.Condition)
        ifst.consequent = Apply(node.TrueBlock)
        if node.FalseBlock and not node.FalseBlock.IsEmpty:
            blk = node.FalseBlock
            if len(blk.Statements) == 1 and blk.FirstStatement isa IfStatement:
                ifst.alternate = Apply(blk.FirstStatement)
            else:
                ifst.alternate = Apply(blk)

        Return ifst

    def OnForStatement(node as ForStatement):
        # TODO: Optimize range(items.length) to cache the length value
        # TODO: Optimize range(f, t) and range(f, t, s)
        #       for (i=f; (s > 0 && i < t) || (s < 0 && i > t); i += s)

        # Only those for statements iterating over simple `range` should have survived
        mie = node.Iterator as MethodInvocationExpression
        if len(mie.Arguments) == 1:
            start = Moz.Literal(0)
            length = Apply(mie.Arguments[0])
        else:
            start = Apply(mie.Arguments[0])
            length = Apply(mie.Arguments[1])

        index = Moz.Identifier(node.Declarations[0].Name)

        fst = Moz.ForStatement(loc: loc(node))
        fst.init = CreateAssign(loc(node), index.name, start)
        fst.test = Moz.BinaryExpression(
            left: index,
            operator: '<',
            right: length
        )
        fst.update = Moz.UpdateExpression(
            argument: index,
            operator: '++',
            prefix: false
        )

        fst.body = Apply(node.Block)
        Return fst

    def OnWhileStatement(node as WhileStatement):
        wst = Moz.WhileStatement(loc: loc(node))
        wst.test = Apply(node.Condition)
        wst.body = Apply(node.Block)

        Return wst

    def OnTryStatement(node as TryStatement):
        n = Moz.TryStatement(loc: loc(node))
        n.block = Apply(node.ProtectedBlock)

        assert len(node.ExceptionHandlers) <= 1, 'Multiple exceptions handlers should be processed in previous steps'
        if len(node.ExceptionHandlers):
            hdl = node.ExceptionHandlers[0]
            h = Moz.CatchClause(loc: loc(hdl))
            h.param = Moz.Identifier(loc: loc(hdl.Declaration), name: hdl.Declaration.Name)
            h.body = Apply(hdl.Block)
            n.handlers.Add(h)

        if node.EnsureBlock:
            n.finalizer = Apply(node.EnsureBlock)

        Return n

    def OnBreakStatement(node as BreakStatement):
        Return Moz.BreakStatement(loc: loc(node))

    def OnContinueStatement(node as ContinueStatement):
        Return Moz.ContinueStatement(loc: loc(node))

    def OnLabelStatement(node as LabelStatement):
        n = Moz.LabeledStatement(loc: loc(node))
        n.label = Moz.Identifier(loc: loc(node), name: node.Name)

        # In Mozilla AST labels are always associated with a statement.
        stmts = (node.ParentNode as Block).Statements
        idx = stmts.IndexOf(node)
        st = stmts[idx + 1]
        stmts.RemoveAt(idx + 1)

        if st.NodeType not in (NodeType.ForStatement, NodeType.WhileStatement):
            raise 'Javascript only allows label on looping statements'
        n.body = Apply(st)

        Return n

    def OnGotoStatement(node as GotoStatement):
        n = Moz.ContinueStatement(loc: loc(node))
        n.label = Moz.Identifier(loc: loc(node.Label), name: node.Label.Name)
        Return n

    def OnRaiseStatement(node as RaiseStatement):
        # TODO: Move this to clean step
        /*
        if false and Context.Parameters.Debug:
            n = Moz.CallExpression(loc: loc(node))
            n.callee = Moz.MemberExpression(
                loc: loc(node),
                object: Moz.Identifier(name: 'Boo'),
                property: Moz.Identifier(name: 'raise')
            )
            # TODO: We should make sure it's a constructor
            if node.Exception isa MethodInvocationExpression:
                c = Moz.NewExpression(loc: loc(node.Exception))
                c._constructor = Apply(node.Exception)
                n.arguments.Add(c)
            else:
                n.arguments.Add(Apply(node.Exception))

            lex = node.Exception.LexicalInfo
            n.arguments.Add(Moz.Literal(value: lex.FileName))
            n.arguments.Add(Moz.Literal(value: lex.Line))

            Return Moz.ExpressionStatement(loc: loc(node), expression: n)
        else:
        */
        t = Moz.ThrowStatement(loc: loc(node))
        t.argument = Apply(node.Exception)
        # TODO: We should make sure it's a constructor
        /*
        if false and node.Exception isa MethodInvocationExpression:
            c = Moz.NewExpression(loc: loc(node.Exception))
            c._constructor = Apply(node.Exception)
            t.argument = c
        else:
        */

        Return t

    def OnReferenceExpression(node as ReferenceExpression):
        Return Moz.Identifier(loc: loc(node), name: node.Name)

    def OnMemberReferenceExpression(node as MemberReferenceExpression):
        n = Moz.MemberExpression(loc: loc(node), property: Moz.Identifier(loc: loc(node), name: node.Name))
        n.object = Apply(node.Target)
        Return n

    def OnConditionalExpression(node as ConditionalExpression):
    """ Convert to the ternary operator.
            (10 if true else 20)  -->  true ? 10 : 20
    """
        c = Moz.ConditionalExpression(loc: loc(node))
        c.test = Apply(node.Condition)
        c.consequent = Apply(node.TrueValue)
        c.alternate = Apply(node.FalseValue)
        Return c

    def OnSlicingExpression(node as SlicingExpression):
        n = Moz.MemberExpression(
            loc: loc(node),
            object: Apply(node.Target),
            property: Apply(node.Indices[0].Begin),
            computed: true
        )

        Return n

    def OnMacroStatement(node as MacroStatement):
        # Special handling for switch/case constructs (used in generators)
        if node.Name == 'switch':
            switch = Moz.SwitchStatement(loc: loc(node))
            switch.discriminant = Apply(node.Arguments[0])
            for casemacro as MacroStatement in node.Body.Statements:
                assert casemacro isa MacroStatement

                case = Moz.SwitchCase(loc: loc(casemacro))
                case.test = Apply(casemacro.Arguments[0])
                for st in casemacro.Body.Statements:
                    case.consequent.Add(Apply(st))

                switch.cases.Add(case)

            Return switch
        else:
            raise 'Macro statements should have been already resolved'

    def OnSuperLiteralExpression(node as SuperLiteralExpression):
        n = Moz.Identifier('_super_')
        Return n

    def OnMethodInvocationExpression(node as MethodInvocationExpression):

        # Detect constructors
        if node.ContainsAnnotation('constructor') or \
           node.Target.Entity and node.Target.Entity.EntityType == EntityType.Constructor and not isFactory(node.Target):
           #node.Target.Entity isa IConstructor and not isFactory(node.Target):
            # TODO: Check if we know for sure there is a constructor factory
            c = Moz.NewExpression(loc: loc(node))
            c._constructor = Apply(node.Target)
            for arg in node.Arguments:
                c.arguments.Add(Apply(arg))
            Return c
            # ce = Moz.CallExpression(loc: loc(node))
            # ce.callee = Moz.MemberExpression(
            #     object: Apply(node.Target),
            #     property: Moz.Identifier('constructor')
            # )
            # for arg in node.Arguments:
            #     ce.arguments.Add(Apply(arg))
            # Return ce
        # Detect sequences
        elif node.Target.ToString() == '@':
            s = Moz.SequenceExpression(loc: loc(node))
            for arg in node.Arguments:
                s.expressions.Add(Apply(arg))
            Return s
        # Detect super()
        elif node.Target.NodeType == NodeType.SuperLiteralExpression:
            method = node.GetAncestor[of Method]()
            n = Moz.CallExpression(loc: loc(node))
            n.callee = Moz.Identifier('_super_.prototype.' + method.Name + '.call')
            n.arguments.Add(Moz.ThisExpression())
            for arg in node.Arguments:
                n.arguments.Add(Apply(arg))
            Return n

        else:
            n = Moz.CallExpression(loc: loc(node))
            n.callee = Apply(node.Target)
            for arg in node.Arguments:
                n.arguments.Add(Apply(arg))

            # Detect calls to eval()
            cache = My[of RuntimeMethodCache].Instance
            if node.Target.Entity is cache.Eval:
                n.callee = Moz.Identifier('eval')
                # Check if we can use verbatim code for the node
                if node.ContainsAnnotation('verbatim') and \
                   arg = node.Arguments.First and arg.NodeType == NodeType.StringLiteralExpression:
                    n.verbatim = (arg as StringLiteralExpression).Value

            Return n

    def OnBlockExpression(node as BlockExpression):
        n = Moz.FunctionExpression(loc: loc(node))
        for param in node.Parameters:
            p = Moz.Identifier(loc: loc(param), name: param.Name)
            n.params.Add(p)

        n.body = Apply(node.Body)
        Return n

    def OnBinaryExpression(node as BinaryExpression):
        n = Moz.BinaryExpression(loc: loc(node))
        match node.Operator:
            case BinaryOperatorType.ReferenceEquality:
                if NodeType.NullLiteralExpression in (node.Left.NodeType, node.Right.NodeType):
                    Return(ProcessIsNull(node))
                    return

                n.operator = '==='
            case BinaryOperatorType.ReferenceInequality:
                if NodeType.NullLiteralExpression in (node.Left.NodeType, node.Right.NodeType):
                    ue = Moz.UnaryExpression(loc: loc(node), operator: '!')
                    ue.argument = ProcessIsNull(node)
                    Return ue
                    return

                n.operator = '!=='

            case BinaryOperatorType.Equality:
                n.operator = '=='
            case BinaryOperatorType.Inequality:
                n.operator = '!='

            case BinaryOperatorType.Member:
                n.operator = 'in'
            case BinaryOperatorType.Addition:
                n.operator = '+'
            case BinaryOperatorType.Subtraction:
                n.operator = '-'
            case BinaryOperatorType.Multiply:
                n.operator = '*'
            case BinaryOperatorType.Division:
                n.operator = '/'
            case BinaryOperatorType.Modulus:
                n.operator = '%'
            case BinaryOperatorType.BitwiseAnd:
                n.operator = '&'
            case BinaryOperatorType.BitwiseOr:
                n.operator = '|'
            case BinaryOperatorType.ExclusiveOr:
                n.operator = '^'
            case BinaryOperatorType.GreaterThan:
                n.operator = '>'
            case BinaryOperatorType.GreaterThanOrEqual:
                n.operator = '>='
            case BinaryOperatorType.LessThan:
                n.operator = '<'
            case BinaryOperatorType.LessThanOrEqual:
                n.operator = '<='
            case BinaryOperatorType.ShiftLeft:
                n.operator = '<<'
            case BinaryOperatorType.ShiftRight:
                n.operator = '>>'

            case BinaryOperatorType.Assign:
                n = Moz.AssignmentExpression(loc: loc(node), operator: '=')
            case BinaryOperatorType.InPlaceBitwiseAnd:
                n = Moz.AssignmentExpression(loc: loc(node), operator: '&=')
            case BinaryOperatorType.InPlaceBitwiseOr:
                n = Moz.AssignmentExpression(loc: loc(node), operator: '|=')
            case BinaryOperatorType.InPlaceExclusiveOr:
                n = Moz.AssignmentExpression(loc: loc(node), operator: '^=')

            case BinaryOperatorType.And:
                n = Moz.LogicalExpression(loc: loc(node), operator: '&&')
            case BinaryOperatorType.Or:
                n = Moz.LogicalExpression(loc: loc(node), operator: '||')

            otherwise:
                raise 'Operator not supported ' + node.Operator

        n.left = Apply(node.Left)
        n.right = Apply(node.Right)
        Return n

    def OnUnaryExpression(node as UnaryExpression):
        match node.Operator:
            case UnaryOperatorType.UnaryNegation:
                n = Moz.UnaryExpression(loc: loc(node), operator: '-')
            case UnaryOperatorType.LogicalNot:
                n = Moz.UnaryExpression(loc: loc(node), operator: '!')
            case UnaryOperatorType.OnesComplement:
                n = Moz.UnaryExpression(loc: loc(node), operator: '~')
            case UnaryOperatorType.Increment:
                n = Moz.UpdateExpression(loc: loc(node), operator: '++', prefix: true)
            case UnaryOperatorType.Decrement:
                n = Moz.UpdateExpression(loc: loc(node), operator: '--', prefix: true)
            case UnaryOperatorType.PostIncrement:
                n = Moz.UpdateExpression(loc: loc(node), operator: '++', prefix: false)
            case UnaryOperatorType.PostDecrement:
                n = Moz.UpdateExpression(loc: loc(node), operator: '--', prefix: false)
            otherwise:
                raise 'Operator not supported ' + node.Operator

        n.argument = Apply(node.Operand)
        Return n

    protected def ProcessIsNull(node as BinaryExpression) as Moz.IExpression:
        itm = (node.Right if node.Left.NodeType == NodeType.NullLiteralExpression else node.Left)
        call = Moz.CallExpression(loc: loc(node))
        call.callee = Moz.Identifier('Boo.isNull')
        call.arguments.Add(Apply(itm))
        return call

namespace BooJs.Compiler.Visitors

from Boo.Lang.Environments import My
from Boo.Lang.Compiler.Ast import *
from Boo.Lang.Compiler.TypeSystem import *
from Boo.Lang.PatternMatching import *

from BooJs.Compiler.Utils import *
from BooJs.Compiler import UniqueNameProvider, Mozilla as Moz
from BooJs.Compiler.TypeSystem import RuntimeMethodCache

class MozillaAstVisitor(FastDepthFirstVisitor):
"""
Transforms a Boo AST into a Mozilla AST
"""
    # Holds the latest conversion from a Boo node to a Moz node
    _return as Moz.Node

    protected def Return(node as Moz.Node):
        _return = node

    protected def Apply(node as Node) as Moz.Node:
        _return = null
        Visit node
        return _return

    protected def loc(node as Node):
        obj = Moz.SourceLocation()
        obj.source = node.LexicalInfo.FileName
        obj.start = Moz.SourceLocation.Position(line: node.LexicalInfo.Line, column: node.LexicalInfo.Column-1)

        # Compute an approximate end position if not available
        if node.EndSourceLocation and node.EndSourceLocation.Line >= 0:
            line, column = (node.EndSourceLocation.Line, node.EndSourceLocation.Column-1)
        else:
            parts = node.ToCodeString().Split(char('\n'))
            line, column = (node.LexicalInfo.Line, node.LexicalInfo.Column-1)
            if len(parts) > 1:
                line += len(parts) - 1
                column = 0
            column += len(parts[-1])

        obj.end = Moz.SourceLocation.Position(line: line, column: column)

        return obj

    def Run(node as Node) as Moz.Node:
        Visit node
        return _return

    def OnBoolLiteralExpression(node as BoolLiteralExpression):
        Return Moz.Literal(loc: loc(node), value: node.Value)

    def OnNullLiteralExpression(node as NullLiteralExpression):
        Return Moz.Literal(loc: loc(node), value: null)

    def OnIntegerLiteralExpression(node as IntegerLiteralExpression):
        n as Moz.IExpression
        if node.Value < 0:
            n = Moz.Literal(loc: loc(node), value: -node.Value)
            n = Moz.UnaryExpression(loc: loc(node), operator: '-', argument: n)
         else:
            n = Moz.Literal(loc: loc(node), value: node.Value)

        Return n

    def OnDoubleLiteralExpression(node as DoubleLiteralExpression):
        n as Moz.IExpression
        if node.Value < 0:
            n = Moz.Literal(loc: loc(node), value: -node.Value)
            n = Moz.UnaryExpression(loc: loc(node), operator: '-', argument: n)
         else:
            n = Moz.Literal(loc: loc(node), value: node.Value)

        Return n

    def OnStringLiteralExpression(node as StringLiteralExpression):
        Return Moz.Literal(loc: loc(node), value: node.Value)

    def OnRELiteralExpression(node as RELiteralExpression):
        Return Moz.Literal(loc: loc(node), value: node.Value, regexp: true)

    def OnSelfLiteralExpression(node as SelfLiteralExpression):
    """ If it's inside a constructor we keep the `self` identifier since they
        act as a factory. Otherwise we create a `this` keyword
    """
        if node.GetAncestor[of Constructor]():
            Return Moz.Identifier(loc: loc(node), 'self')
        else:
            Return Moz.ThisExpression(loc: loc(node))

    def OnListLiteralExpression(node as ListLiteralExpression):
        n = Moz.ArrayExpression(loc: loc(node))
        for itm in node.Items:
            exp = Apply(itm)
            n.elements.Add(exp)
        Return n

    def OnHashLiteralExpression(node as HashLiteralExpression):
        n = Moz.ObjectExpression(loc: loc(node))
        for pair in node.Items:
            prop = Moz.ObjectExpression.Prop(key: Apply(pair.First), value: Apply(pair.Second))
            n.properties.Add(prop)
        Return n

    def OnEnumDefinition(node as EnumDefinition):
        # Create an object with the enum values
        o = Moz.ObjectExpression(loc: loc(node))
        for member as EnumMember in node.Members:
            assert member.Initializer != null, 'Enum definition without an initializer value!'
            o.properties.Add(
                Moz.ObjectExpression.Prop(
                    key: Moz.Literal(value: member.Name),
                    value: Apply(member.Initializer)
                )
            )

        # Assign the object to a variable
        Return CreateVar(node, node.Name, o)

    protected def CreateVar(node as Node, name as string, value as Moz.IExpression):
        v = Moz.VariableDeclarator(loc: loc(node))
        v.id = Moz.Identifier(loc: loc(node), name)
        v.init = value
        n = Moz.VariableDeclaration(loc: loc(node), kind: 'var')
        n.declarations.Add(v)
        return n

    protected def CreateAssign(loc as Moz.SourceLocation, ident as string, value as Moz.IExpression):
        a = Moz.AssignmentExpression(loc: loc, operator:'=')
        a.left = Moz.Identifier(loc: loc, name: ident)
        a.right = value
        return a

    def OnModule(node as Module):
        n = Moz.Program(loc: loc(node))

        deps = List of Moz.IExpression() { Moz.Literal('Boo') }
        refs = List of Moz.IPattern() { Moz.Identifier('Boo') }

        # Get namespace mapping annotations
        mapping as Hash = node['nsmapping']
        asmrefs as Hash = node['nsasmrefs']

        for ns in mapping.Keys:
            if ns in asmrefs:
                context().TraceInfo('Namespace mapping {0} => {1}', ns, mapping[ns] + ':' + asmrefs[ns])
                deps.Add( Moz.Literal(ns + ':' + asmrefs[ns]) )
            else:
                context().TraceInfo('Namespace mapping {0} => {1}', ns, mapping[ns])
                deps.Add( Moz.Literal(ns) )
            refs.Add( Moz.Identifier(mapping[ns]) )

        # Use Boo.define to bootstrap the module contents
        call = Moz.CallExpression()
        call.callee = Moz.MemberExpression(
            object: Moz.Identifier(name: 'Boo'),
            property: Moz.Identifier(name: 'define')
        )
        call.arguments.Add(Moz.Literal(value: (node.Namespace.Name if node.Namespace else '')))
        call.arguments.Add(Moz.ArrayExpression(elements: deps))
        fn = Moz.FunctionExpression(params: refs, body: Moz.BlockStatement())
        call.arguments.Add(fn)
        n.body.Add(Moz.ExpressionStatement(call))

        st as Moz.IStatement
        for member in node.Members:
            st = Apply(member)
            if st is null:
                continue

            if st isa Moz.BlockStatement:
                fn.body.body += (st as Moz.BlockStatement).body
            else:
                fn.body.body.Add(st)

        # Export public symbols
        # js: exports.symbol = symbol
        members = TypeMemberCollection()
        for member in node.Members:
            if member.IsSynthetic and member.Name.EndsWith('Module'):
                members.AddRange((member as ClassDefinition).Members)
            else:
                members.Add(member)

        for member in members:
            continue unless member.IsPublic and not member.IsInternal

            expr = Moz.BinaryExpression(
                left: Moz.MemberExpression(
                    object: Moz.Identifier('exports'),
                    property: Moz.Identifier(member.Name)
                ),
                operator: '=',
                right: Moz.Identifier(member.Name)
            )

            st = Moz.ExpressionStatement(expr)
            fn.body.body.Add(st)

        if not node.Globals.IsEmpty:
            rcall = Moz.CallExpression()
            rcall.callee = Moz.MemberExpression(
                object: Moz.Identifier(name: 'Boo'),
                property: Moz.Identifier(name: 'require')
            )
            # Clone the deps list replacing 'exports' by the namespace
            deps = deps.GetRange(0, len(deps))
            #deps[0] = Moz.Literal((node.Namespace.Name if node.Namespace else ''))
            rcall.arguments.Add(Moz.ArrayExpression(elements: deps))
            fn = Moz.FunctionExpression(params: refs, body: Moz.BlockStatement())
            rcall.arguments.Add(fn)
            n.body.Add(Moz.ExpressionStatement(rcall))

            for global in node.Globals.Statements:
                st = Apply(global)
                fn.body.body.Add(st)

        Return n

    def OnClassDefinition(node as ClassDefinition):
        n = Moz.BlockStatement()

        # Detect module class and output its members directly
        if node.IsSynthetic and node.IsFinal and node.Name.EndsWith('Module'):
            members = [m for m in node.Members if m.NodeType != NodeType.Constructor]
            st as Moz.IStatement
            for member in members:
                match Apply(member):
                    case null:
                        continue
                    case bs=Moz.BlockStatement():
                        n.body += bs.body
                    case _:
                        n.body.Add(_)

            Return n
            return

        # TODO: Handle visibility (internals should not be exposed)

        # Bar = (function (_super_) {
        #     // Constructor
        #     function Bar(_init_) {
        #         // Call constructor (for calls from JS or duck code)
        #         // TODO: Use `this instanceof Bar` test instead?
        #         if (_init_ !== Boo.INIT)
        #             return Boo.overload(arguments, [ [], ['string', 'int'] ], [Foo.ctor$0, Foo.ctor$1]);
        #
        #         // Initialize fields (TODO: can this be moved to the prototype?)
        #         this.field = '';
        #
        #         // Bind public instance methods (NOTE: also inherited) (TODO: Support annotation to avoid binding)
        #         this.foo = Boo.bind(this.foo, this);
        #     }
        #
        #     // Inherit "static" members
        #     Boo.extend(Bar, _super_);
        #
        #     // Static constructor factories (called from typed code)
        #     Bar.ctor$0 = function Bar$ctor$0() {
        #         var self = this instanceof Foo ? this : new Foo(Boo.INIT);
        #         // Call parent constructor
        #         _super_.call(self);
        #         // Call constructor overload
        #         Bar.ctor$1.call(self, 'foo')
        #         // constructor logic
        #         ...
        #         return self;
        #     }
        #
        #     // Static members
        #     Bar.bars = function Bar$$bars() {};
        #
        #     // Setup inheritance (TODO: move this into Boo.extend?)
        #     Bar.prototype = Boo.create(_super_.prototype);
        #     Bar.prototype.constructor = Bar;
        #
        #     // Reflect metadata
        #     Bar.prototype.$boo$interfaces = [IEnumerator];
        #     Bar.prototype.$boo$super = _super_
        #
        #     // Alias static members to instance (for JavaScript access)
        #     // TODO: Shall we differ from Boo an only allow static access on type?
        #     Bar.prototype.bars = Bar.bars;
        #
        #     // Instance members
        #     Bar.prototype.foo = function Bar$foo() {
        #         // Call parent method
        #         _super_.prototype.foo.call(this);
        #     };
        #
        #     // Static constructor logic (in .Net this only runs when the type is first accessed)
        #     ...
        #
        #     return Bar;
        # })(Foo);

        # TODO: Support constructor overloading
        constructors = [c for c in node.Members if c.NodeType == NodeType.Constructor]
        if len(constructors) > 1:
            raise "Constructor overloading not supported yet"

        block = Moz.BlockStatement()

        # Constructor
        # js: function Bar(_init_) {}
        cons = Moz.FunctionDeclaration(loc: loc(node))
        cons.params = List[of Moz.IPattern]() { Moz.Identifier('_init_') }
        cons.body = Moz.BlockStatement()
        cons.id = Moz.Identifier(node.Name)
        block.Add(cons)

        # Call constructor factory
        # js: if (_init_ !== Boo.INIT) return Boo.overload(arguments, [ ... ], [ cons$0, cons$1 ])
        ifst = Moz.IfStatement(loc: loc(node))
        ifst.test = Moz.BinaryExpression(
            operator: '!==',
            left: Moz.Identifier('_init_'),
            right: Moz.Identifier('Boo.INIT')
        )
        # TODO: Build a proper overload call
        ifst.consequent = Moz.ReturnStatement(
            argument: Moz.CallExpression(
                callee: Moz.Identifier(node.Name + '.constructor.apply'),
                arguments: List[of Moz.IExpression]() {
                    Moz.Literal(value: null),
                    Moz.Identifier('arguments')
                }
            )
            # argument: Moz.CallExpression(
            #     callee: Moz.Identifier('Boo.overload'),
            #     arguments: List[of Moz.IExpression]() {
            #         Moz.Identifier('arguments'),
            #         Moz.Literal(value: []),
            #         Moz.Literal(value: [])
            #     }
            # )
        )
        cons.body.Add(ifst)

        # Bind instance methods
        # js: this.foo = Boo.bind(this.foo, this);
        for m in node.Members:
            continue unless m.NodeType == NodeType.Method and not m.IsStatic
            assign = CreateAssign(loc(m), "this.$(m.Name)", Moz.CallExpression(
                Moz.Identifier('Boo.bind'),
                Moz.MemberExpression(
                    object: Moz.ThisExpression(),
                    property: Moz.Identifier(m.Name)
                ),
                Moz.ThisExpression()
            ))
            cons.body.Add(assign)

        # Events
        # TODO: Do it properly (ie: static events?)
        for e as Event in [m for m in node.Members if m.NodeType == NodeType.Event]:
            assign = CreateAssign(loc(e), "this.$(e.Name)", null)
            assign.right = Moz.CallExpression(callee: Moz.Identifier('Boo.event'))
            cons.body.Add(assign)

        # Initialize fields
        # TODO: Shouldn't fields with primitive initializers be moved to the prototype?
        # TODO: Boo already moves field initialization to the constructors
        for f as Field in [m for m in node.Members if m.NodeType == NodeType.Field]:
            # TODO: Define fields without initializers too
            if f.Initializer:
                assign = CreateAssign(loc(f), "this.$(f.Name)", Apply(f.Initializer))
                cons.body.Add(assign)

        # Inherit "static" members from the parent type
        # js: Boo.extend(Bar, _super_)
        block.Add(Moz.CallExpression(
            Moz.Identifier('Boo.extend'),
            Moz.Identifier(node.Name),
            Moz.Identifier('_super_')
        ))

        # Include constructor factories as private scoped functions
        members = [m for m in node.Members if m.NodeType == NodeType.Constructor]
        for c as Constructor in members:
            continue if not c

            assign = CreateAssign(loc(node), "$(node.Name).$(c.Name)", null)
            fn = Apply(c) as Moz.FunctionDeclaration
            assign.right = Moz.FunctionExpression(
                loc: fn.loc,
                id: Moz.Identifier(node.FullName.Replace('.', '$') + '$$' + c.Name),
                params: fn.params,
                body: fn.body
            )
            block.Add(assign)

        # Static members
        # js: Bar.sbar = function Bar$$sbar (arg) { }
        # TODO: Handle static fields too!
        for m in node.Members:
            continue unless m.NodeType == NodeType.Method and m.IsStatic
            assign = CreateAssign(loc(m), "$(node.Name).$(m.Name)", null)
            fn = Apply(m) as Moz.FunctionDeclaration
            assign.right = Moz.FunctionExpression(
                loc: fn.loc,
                id: Moz.Identifier(node.FullName.Replace('.', '$') + '$$' + m.Name),
                params: fn.params,
                body: fn.body
            )
            block.Add(assign)

        # Setup inheritance
        # js: Bar.prototype = Boo.create(_super_.prototype)
        assign = CreateAssign(loc(node), "$(node.Name).prototype", Moz.CallExpression(
            Moz.Identifier('Boo.create'),
            Moz.Identifier('_super_.prototype')
        ))
        block.Add(assign)
        # js: Bar.prototype.constructor = Bar
        assign = CreateAssign(loc(node), "$(node.Name).prototype.constructor", Moz.Identifier(node.Name))
        block.Add(assign)

        # Metadata
        # js: Bar.prototype.$boo$interfaces = [ IEnumerable ]
        interfaces = Moz.ArrayExpression()
        for t in node.BaseTypes:
            continue if t is node.BaseTypes.First
            continue if typeSystem().IsSystemObject(t.Entity)
            interfaces.elements.Add(
                Moz.Identifier(t.Entity.FullName)
            )
        assign = CreateAssign(loc(node), node.Name + '.prototype.$boo$interfaces', interfaces)
        block.Add(assign)
        # js: Bar.prototype.$boo$super = _super_
        assign = CreateAssign(loc(node), node.Name + '.prototype.$boo$super', Moz.Identifier('_super_'))
        block.Add(assign)

        # Alias static members to instance (for JavaScript access)
        # js: Bar.prototype.bars = Bar.bars;
        # TODO: Shall we differ from Boo an only allow static access on type?
        for m in node.Members:
            continue unless m.NodeType == NodeType.Method and m.IsStatic
            assign = CreateAssign(loc(m), node.Name + '.prototype.' + m.Name, Moz.Identifier(node.Name + '.' + m.Name))
            block.Add(assign)

        # Instance members
        # js: Bar.prototype.bar = function Bar$bar (arg) { }
        members = [m for m in node.Members if m.NodeType == NodeType.Method and not m.IsStatic]
        for method as Method in members:
            assign = CreateAssign(loc(node), node.Name + '.prototype.' + method.Name, null)
            fn = Apply(method) as Moz.FunctionDeclaration
            assign.right = Moz.FunctionExpression(
                loc: fn.loc,
                id: Moz.Identifier(node.FullName.Replace('.', '$') + '$' + fn.id.name),
                params: fn.params,
                body: fn.body
            )
            block.Add(assign)

        # TODO: Include static constructor logic to run when creating the type

        # Wrap everything in a self calling function and assign to a variable
        # js: Bar = (function(_super_){ ... return Bar; })(Object)
        block.body.Add(
            Moz.ReturnStatement(argument: Moz.Identifier(node.Name))
        )

        func = Moz.FunctionExpression(body:block)
        func.params = List of Moz.IPattern() { Moz.Identifier('_super_') }
        call = Moz.CallExpression(callee:func)
        call.arguments.Add(
            Moz.Identifier(
                ('Object' if typeSystem().IsSystemObject(node.BaseTypes.First.Entity)
                    else node.BaseTypes.First.Entity.FullName)
            )
        )

        Return CreateVar(node, node.Name, call)

    def OnField(node as Field):
        pass

    def OnConstructor(node as Constructor):
        n = Moz.FunctionDeclaration(loc: loc(node))
        n.id = Moz.Identifier(loc: loc(node), name: node.FullName.Replace('.', '$'))
        for param in node.Parameters:
            p = Moz.Identifier(loc: loc(param), name: param.Name)
            n.params.Add(p)

        n.body = Apply(node.Body)

        // js: var self = this instanceof Foo ? this : new Foo(Boo.INIT);
        n.body.body.Insert(0, CreateVar(
            node,
            'self',
            Moz.ConditionalExpression(
                test: Moz.BinaryExpression(
                    operator: 'instanceof',
                    left: Moz.ThisExpression(),
                    right: Moz.Identifier(node.DeclaringType.Name)
                ),
                consequent: Moz.ThisExpression(),
                alternate: Moz.NewExpression(
                    _constructor: Moz.Identifier(node.DeclaringType.Name),
                    arguments: List[of Moz.IExpression]() {
                        Moz.Identifier('Boo.INIT')
                    }
                )
            )
        ))
        // js: return self
        n.body.Add(
            Moz.ReturnStatement(
                argument: Moz.Identifier('self')
            )
        )

        Return n

    def OnMethod(node as Method):
        n = Moz.FunctionDeclaration(loc: loc(node))
        n.id = Moz.Identifier(loc: loc(node), name: node.Name)
        for param in node.Parameters:
            p = Moz.Identifier(loc: loc(param), name: param.Name)
            n.params.Add(p)

        n.body = Apply(node.Body)
        Return n

    def OnDeclarationStatement(node as DeclarationStatement):
        # TODO: Generate type annotated comments
        Return CreateVar(node, node.Declaration.Name, Apply(node.Initializer))

    def OnBlock(node as Block):
        b = Moz.BlockStatement(loc: loc(node))
        for st in node.Statements:
            mst = Apply(st) as Moz.IStatement
            if mst:
                b.body.Add(mst)

        Return b

    def OnExpressionStatement(node as ExpressionStatement):
        n = Moz.ExpressionStatement(loc: loc(node))
        n.expression = Apply(node.Expression)
        Return n

    def OnReturnStatement(node as ReturnStatement):
        n = Moz.ReturnStatement(loc: loc(node))

        # Inside constructors always return `self`
        if node.GetAncestor[of Constructor]():
            n.argument = Moz.Identifier('self')
        else:
            n.argument = Apply(node.Expression)

        Return n

    def OnIfStatement(node as IfStatement):

        ifst = Moz.IfStatement(loc: loc(node))
        ifst.test = Apply(node.Condition)
        ifst.consequent = Apply(node.TrueBlock)
        if node.FalseBlock and not node.FalseBlock.IsEmpty:
            blk = node.FalseBlock
            if len(blk.Statements) == 1 and blk.FirstStatement isa IfStatement:
                ifst.alternate = Apply(blk.FirstStatement)
            else:
                ifst.alternate = Apply(blk)

        Return ifst

    def OnForStatement(node as ForStatement):
        # TODO: Optimize range(items.length) to cache the length value
        # TODO: Optimize range(f, t) and range(f, t, s)
        #       for (i=f; (s > 0 && i < t) || (s < 0 && i > t); i += s)

        # Only those for statements iterating over simple `range` should have survived
        mie = node.Iterator as MethodInvocationExpression
        if len(mie.Arguments) == 1:
            start = Moz.Literal(0)
            length = Apply(mie.Arguments[0])
        else:
            start = Apply(mie.Arguments[0])
            length = Apply(mie.Arguments[1])

        index = Moz.Identifier(node.Declarations[0].Name)

        fst = Moz.ForStatement(loc: loc(node))
        fst.init = CreateAssign(loc(node), index.name, start)
        fst.test = Moz.BinaryExpression(
            left: index,
            operator: '<',
            right: length
        )
        fst.update = Moz.UpdateExpression(
            argument: index,
            operator: '++',
            prefix: false
        )

        fst.body = Apply(node.Block)
        Return fst

    def OnWhileStatement(node as WhileStatement):
        wst = Moz.WhileStatement(loc: loc(node))
        wst.test = Apply(node.Condition)
        wst.body = Apply(node.Block)

        Return wst

    def OnTryStatement(node as TryStatement):
        n = Moz.TryStatement(loc: loc(node))
        n.block = Apply(node.ProtectedBlock)

        assert len(node.ExceptionHandlers) <= 1, 'Multiple exceptions handlers should be processed in previous steps'
        if len(node.ExceptionHandlers):
            hdl = node.ExceptionHandlers[0]
            h = Moz.CatchClause(loc: loc(hdl))
            h.param = Moz.Identifier(loc: loc(hdl.Declaration), name: hdl.Declaration.Name)
            h.body = Apply(hdl.Block)
            n.handlers.Add(h)

        if node.EnsureBlock:
            n.finalizer = Apply(node.EnsureBlock)

        Return n

    def OnBreakStatement(node as BreakStatement):
        Return Moz.BreakStatement(loc: loc(node))

    def OnContinueStatement(node as ContinueStatement):
        Return Moz.ContinueStatement(loc: loc(node))

    def OnLabelStatement(node as LabelStatement):
        n = Moz.LabeledStatement(loc: loc(node))
        n.label = Moz.Identifier(loc: loc(node), name: node.Name)

        # In Mozilla AST labels are always associated with a statement.
        stmts = (node.ParentNode as Block).Statements
        idx = stmts.IndexOf(node)
        st = stmts[idx + 1]
        stmts.RemoveAt(idx + 1)

        if st.NodeType not in (NodeType.ForStatement, NodeType.WhileStatement):
            raise 'Javascript only allows label on looping statements'
        n.body = Apply(st)

        Return n

    def OnGotoStatement(node as GotoStatement):
        n = Moz.ContinueStatement(loc: loc(node))
        n.label = Moz.Identifier(loc: loc(node.Label), name: node.Label.Name)
        Return n

    def OnRaiseStatement(node as RaiseStatement):
        # TODO: Move this to clean step
        /*
        if false and Context.Parameters.Debug:
            n = Moz.CallExpression(loc: loc(node))
            n.callee = Moz.MemberExpression(
                loc: loc(node),
                object: Moz.Identifier(name: 'Boo'),
                property: Moz.Identifier(name: 'raise')
            )
            # TODO: We should make sure it's a constructor
            if node.Exception isa MethodInvocationExpression:
                c = Moz.NewExpression(loc: loc(node.Exception))
                c._constructor = Apply(node.Exception)
                n.arguments.Add(c)
            else:
                n.arguments.Add(Apply(node.Exception))

            lex = node.Exception.LexicalInfo
            n.arguments.Add(Moz.Literal(value: lex.FileName))
            n.arguments.Add(Moz.Literal(value: lex.Line))

            Return Moz.ExpressionStatement(loc: loc(node), expression: n)
        else:
        */
        t = Moz.ThrowStatement(loc: loc(node))
        t.argument = Apply(node.Exception)
        # TODO: We should make sure it's a constructor
        /*
        if false and node.Exception isa MethodInvocationExpression:
            c = Moz.NewExpression(loc: loc(node.Exception))
            c._constructor = Apply(node.Exception)
            t.argument = c
        else:
        */

        Return t

    def OnReferenceExpression(node as ReferenceExpression):
        Return Moz.Identifier(loc: loc(node), name: node.Name)

    def OnMemberReferenceExpression(node as MemberReferenceExpression):
        n = Moz.MemberExpression(loc: loc(node), property: Moz.Identifier(loc: loc(node), name: node.Name))
        n.object = Apply(node.Target)
        Return n

    def OnConditionalExpression(node as ConditionalExpression):
    """ Convert to the ternary operator.
            (10 if true else 20)  -->  true ? 10 : 20
    """
        c = Moz.ConditionalExpression(loc: loc(node))
        c.test = Apply(node.Condition)
        c.consequent = Apply(node.TrueValue)
        c.alternate = Apply(node.FalseValue)
        Return c

    def OnSlicingExpression(node as SlicingExpression):
        n = Moz.MemberExpression(
            loc: loc(node),
            object: Apply(node.Target),
            property: Apply(node.Indices[0].Begin),
            computed: true
        )

        Return n

    def OnMacroStatement(node as MacroStatement):
        # Special handling for switch/case constructs (used in generators)
        if node.Name == 'switch':
            switch = Moz.SwitchStatement(loc: loc(node))
            switch.discriminant = Apply(node.Arguments[0])
            for casemacro as MacroStatement in node.Body.Statements:
                assert casemacro isa MacroStatement

                case = Moz.SwitchCase(loc: loc(casemacro))
                case.test = Apply(casemacro.Arguments[0])
                for st in casemacro.Body.Statements:
                    case.consequent.Add(Apply(st))

                switch.cases.Add(case)

            Return switch
        else:
            raise 'Macro statements should have been already resolved'

    def OnSuperLiteralExpression(node as SuperLiteralExpression):
        n = Moz.Identifier('_super_')
        Return n

    def OnMethodInvocationExpression(node as MethodInvocationExpression):

        # Detect constructors
        if node.ContainsAnnotation('constructor') or \
           node.Target.Entity and node.Target.Entity.EntityType == EntityType.Constructor and not isFactory(node.Target):
           #node.Target.Entity isa IConstructor and not isFactory(node.Target):
            # TODO: Check if we know for sure there is a constructor factory
            c = Moz.NewExpression(loc: loc(node))
            c._constructor = Apply(node.Target)
            for arg in node.Arguments:
                c.arguments.Add(Apply(arg))
            Return c
            # ce = Moz.CallExpression(loc: loc(node))
            # ce.callee = Moz.MemberExpression(
            #     object: Apply(node.Target),
            #     property: Moz.Identifier('constructor')
            # )
            # for arg in node.Arguments:
            #     ce.arguments.Add(Apply(arg))
            # Return ce
        # Detect sequences
        elif node.Target.ToString() == '@':
            s = Moz.SequenceExpression(loc: loc(node))
            for arg in node.Arguments:
                s.expressions.Add(Apply(arg))
            Return s
        # Detect super()
        elif node.Target.NodeType == NodeType.SuperLiteralExpression:
            method = node.GetAncestor[of Method]()
            n = Moz.CallExpression(loc: loc(node))
            n.callee = Moz.Identifier('_super_.prototype.' + method.Name + '.call')
            n.arguments.Add(Moz.ThisExpression())
            for arg in node.Arguments:
                n.arguments.Add(Apply(arg))
            Return n

        else:
            n = Moz.CallExpression(loc: loc(node))
            n.callee = Apply(node.Target)
            for arg in node.Arguments:
                n.arguments.Add(Apply(arg))

            # Detect calls to eval()
            cache = My[of RuntimeMethodCache].Instance
            if node.Target.Entity is cache.Eval:
                n.callee = Moz.Identifier('eval')
                # Check if we can use verbatim code for the node
                if node.ContainsAnnotation('verbatim') and \
                   arg = node.Arguments.First and arg.NodeType == NodeType.StringLiteralExpression:
                    n.verbatim = (arg as StringLiteralExpression).Value

            Return n

    def OnBlockExpression(node as BlockExpression):
        n = Moz.FunctionExpression(loc: loc(node))
        for param in node.Parameters:
            p = Moz.Identifier(loc: loc(param), name: param.Name)
            n.params.Add(p)

        n.body = Apply(node.Body)
        Return n

    def OnBinaryExpression(node as BinaryExpression):
        n = Moz.BinaryExpression(loc: loc(node))
        match node.Operator:
            case BinaryOperatorType.ReferenceEquality:
                if NodeType.NullLiteralExpression in (node.Left.NodeType, node.Right.NodeType):
                    Return(ProcessIsNull(node))
                    return

                n.operator = '==='
            case BinaryOperatorType.ReferenceInequality:
                if NodeType.NullLiteralExpression in (node.Left.NodeType, node.Right.NodeType):
                    ue = Moz.UnaryExpression(loc: loc(node), operator: '!')
                    ue.argument = ProcessIsNull(node)
                    Return ue
                    return

                n.operator = '!=='

            case BinaryOperatorType.Equality:
                n.operator = '=='
            case BinaryOperatorType.Inequality:
                n.operator = '!='

            case BinaryOperatorType.Member:
                n.operator = 'in'
            case BinaryOperatorType.Addition:
                n.operator = '+'
            case BinaryOperatorType.Subtraction:
                n.operator = '-'
            case BinaryOperatorType.Multiply:
                n.operator = '*'
            case BinaryOperatorType.Division:
                n.operator = '/'
            case BinaryOperatorType.Modulus:
                n.operator = '%'
            case BinaryOperatorType.BitwiseAnd:
                n.operator = '&'
            case BinaryOperatorType.BitwiseOr:
                n.operator = '|'
            case BinaryOperatorType.ExclusiveOr:
                n.operator = '^'
            case BinaryOperatorType.GreaterThan:
                n.operator = '>'
            case BinaryOperatorType.GreaterThanOrEqual:
                n.operator = '>='
            case BinaryOperatorType.LessThan:
                n.operator = '<'
            case BinaryOperatorType.LessThanOrEqual:
                n.operator = '<='
            case BinaryOperatorType.ShiftLeft:
                n.operator = '<<'
            case BinaryOperatorType.ShiftRight:
                n.operator = '>>'

            case BinaryOperatorType.Assign:
                n = Moz.AssignmentExpression(loc: loc(node), operator: '=')
            case BinaryOperatorType.InPlaceBitwiseAnd:
                n = Moz.AssignmentExpression(loc: loc(node), operator: '&=')
            case BinaryOperatorType.InPlaceBitwiseOr:
                n = Moz.AssignmentExpression(loc: loc(node), operator: '|=')
            case BinaryOperatorType.InPlaceExclusiveOr:
                n = Moz.AssignmentExpression(loc: loc(node), operator: '^=')

            case BinaryOperatorType.And:
                n = Moz.LogicalExpression(loc: loc(node), operator: '&&')
            case BinaryOperatorType.Or:
                n = Moz.LogicalExpression(loc: loc(node), operator: '||')

            otherwise:
                raise 'Operator not supported ' + node.Operator

        n.left = Apply(node.Left)
        n.right = Apply(node.Right)
        Return n

    def OnUnaryExpression(node as UnaryExpression):
        match node.Operator:
            case UnaryOperatorType.UnaryNegation:
                n = Moz.UnaryExpression(loc: loc(node), operator: '-')
            case UnaryOperatorType.LogicalNot:
                n = Moz.UnaryExpression(loc: loc(node), operator: '!')
            case UnaryOperatorType.OnesComplement:
                n = Moz.UnaryExpression(loc: loc(node), operator: '~')
            case UnaryOperatorType.Increment:
                n = Moz.UpdateExpression(loc: loc(node), operator: '++', prefix: true)
            case UnaryOperatorType.Decrement:
                n = Moz.UpdateExpression(loc: loc(node), operator: '--', prefix: true)
            case UnaryOperatorType.PostIncrement:
                n = Moz.UpdateExpression(loc: loc(node), operator: '++', prefix: false)
            case UnaryOperatorType.PostDecrement:
                n = Moz.UpdateExpression(loc: loc(node), operator: '--', prefix: false)
            otherwise:
                raise 'Operator not supported ' + node.Operator

        n.argument = Apply(node.Operand)
        Return n

    protected def ProcessIsNull(node as BinaryExpression) as Moz.IExpression:
        itm = (node.Right if node.Left.NodeType == NodeType.NullLiteralExpression else node.Left)
        call = Moz.CallExpression(loc: loc(node))
        call.callee = Moz.Identifier('Boo.isNull')
        call.arguments.Add(Apply(itm))
        return call


namespace Boo.OMeta.Parser

import Boo.OMeta
import Boo.PatternMatching

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

import System.Globalization

macro infix:

	l, op, r = infix.Arguments

	return ExpressionStatement([| $l = ((($l >> l, $op >> op, $r >> r) ^ newInfixExpression(op, l, r)) | $r) |])

macro infixr:

	l, op, r = infixr.Arguments

	return ExpressionStatement([| $l = ((($r >> l, $op >> op, $l >> r) ^ newInfixExpression(op, l, r)) | $r) |])

macro prefix:

	rule, op, next = prefix.Arguments

	return ExpressionStatement([| $rule = ($op >> op, $rule >> e) ^ newPrefixExpression(op, e) | $next |])

macro list_of:
"""
Generates rules for lists of the given expression.

	list_of expression

Expands to something that matches:

	expression (COMMA expression)+
"""

	rule, = list_of.Arguments

	block as Block = list_of.ParentNode

	listRuleName = ReferenceExpression(Name: "${rule}_list")
	listRule = [| $listRuleName = ((($rule >> first), ++((COMMA, $rule >> e) ^ e) >> rest) ^ prepend(first, rest)) | ($rule >> v ^ [v]) |]
	block.Add(listRule)

	optionalRuleName = ReferenceExpression(Name: "optional_${rule}_list")
	optionalListRule = [| $optionalRuleName = $listRuleName | ("" ^ []) |]
	block.Add(optionalListRule)

ometa BooParser < WhitespaceSensitiveTokenizer:

	tokens:
		equality = "=="
		inequality = "!="
		assign = "="
		assign_inplace = "+=" | "-=" | "*=" | "/=" | "%=" | "^=" | "&=" | "|="
		xor = "^"
		increment = "++"
		decrement = "--"
		plus = "+"
		minus = "-"
		exponentiation = "**"
		star = "*"
		division = "/"
		modulus = "%"
		ones_complement = "~"
		bitwise_shift_left = "<<"
		bitwise_shift_right = ">>"
		greater_than_eq = ">="
		greater_than = ">"
		less_than_eq = "<="
		less_than = "<"
		bitwise_and = "&"
		bitwise_or = "|"
		hexnum = ("0x", ++(hex_digit | digit) >> ds) ^ makeString(ds)
		num = ++digit
		colon = ":"
		dot = "."
		comma = ","
		lparen = "(", enterWhitespaceAgnosticRegion
		rparen = ")", leaveWhitespaceAgnosticRegion
		lbrack = "[", enterWhitespaceAgnosticRegion
		rbrack = "]", leaveWhitespaceAgnosticRegion
		kw = (keywords >> value, ~(letter | digit | '_')) ^ value
		tdqs = ('"""', ++(~'"""', _) >> s, '"""') ^ s
		sdqs = ('"', ++(~'"', _) >> s, '"') ^ s
		sqs = ("'", ++(~"'", _) >> s, "'") ^ s
		id = ((letter | '_') >> p, --(letter | digit | '_') >> s) ^ makeString(p, s)

	hex_digit = _ >> c as char and ((c >= char('a') and c <= char('f')) or (c >= char('A') and c <= char('Z')))

	keywords "class", "def", "import", "pass", "return", "true", \
		"false", "and", "or", "as", "not", "if", "is", "null", \
		"for", "in", "yield", "self", "super"

	keyword[expected] = ((KW >> t) and (expected is tokenValue(t))) ^ t

	module = (
		--EOL,
		(docstring >> s | ""),
		--import_declaration >> ids,
		--module_member >> members,
		--stmt >> stmts,
		--whitespace
	) ^ newModule(s, ids, members, stmts)

	docstring = (TDQS >> t, eol) ^ tokenValue(t)

	import_declaration = (IMPORT, qualified_name >> qn, eol) ^ newImport(qn)

	qualified_name = (ID >> qualifier, --((DOT, ID >> n) ^ n) >> suffix)^ buildQName(qualifier, suffix)

	module_member = class_def | method

	class_def = (
		CLASS, ID >> className, begin_block, class_body >> body, end_block
	) ^ newClass(className, body)

	begin_block = COLON, INDENT

	end_block = DEDENT

	class_body = ((PASS, eol) ^ null) | (++class_member >> members ^ members)

	class_member = method | class_def

	method = (
		DEF, ID >> name, LPAREN, optional_parameter_list >> parameters, RPAREN, block >> body
	) ^ newMethod(name, parameters, body)

	list_of parameter

	parameter = ((ID >> name, AS, type_reference >> type) | (ID >> name)) ^ ParameterDeclaration(Name: tokenValue(name), Type: type)

	block = empty_block | non_empty_block

	empty_block = (begin_block, (PASS, eol), end_block) ^ Block()

	non_empty_block = (begin_block, ++stmt >> stmts, end_block)  ^ newBlock(stmts)

	stmt = ((stmt_block >> s) | (stmt_line >> s, eol)) ^ s

	stmt_line = (~~(ID, AS), stmt_declaration) \
		| stmt_expression \
		| stmt_return \
		| stmt_yield

	stmt_yield = (YIELD, assignment >> e, stmt_modifier >> m) ^ YieldStatement(Expression: e, Modifier: m)

	stmt_modifier = ((IF, assignment >> e) ^ StatementModifier(Type: StatementModifierType.If, Condition: e)) | ""

	stmt_declaration = (declaration >> d, ASSIGN, expression >> e) ^ newDeclarationStatement(d, e)

	declaration = (ID >> name, ((AS, type_reference >> typeRef) | "")) ^ newDeclaration(name, typeRef)

	stmt_block = stmt_if | stmt_for

	stmt_for = (FOR, declaration_list >> dl, IN, expression >> e, block >> body) ^ newForStatement(dl, e, body)

	stmt_if = (IF, expression >> e, block >> trueBlock) ^ newIfStatement(e, trueBlock)

	stmt_return = (RETURN, optional_expression >> e, stmt_modifier >> m) ^ ReturnStatement(Expression: e, Modifier: m)

	optional_expression = expression | ""

	stmt_expression = (multi_assignment | assignment) >> e ^ ExpressionStatement(Expression: e)

	multi_assignment = (expression >> l, ASSIGN >> op, assignment_list >> items) ^ newInfixExpression(op, l, newRValue(items))

	list_of assignment

	infixr assignment, (ASSIGN | ASSIGN_INPLACE), expression

	expression = or_expression

	infix or_expression, OR, and_expression

	infix and_expression, AND, not_expression

	prefix not_expression, NOT, membership_expression

	infix membership_expression, (IN | ((NOT, IN) ^ makeToken("not in"))), identity_test_expression

	infix identity_test_expression, ((IS, NOT) ^ makeToken("is not")) | IS, comparison

	infix comparison, (EQUALITY | INEQUALITY | GREATER_THAN | GREATER_THAN_EQ | LESS_THAN | LESS_THAN_EQ), bitwise_or_expression

	infix bitwise_or_expression, BITWISE_OR, bitwise_xor_expression

	infix bitwise_xor_expression, XOR, bitwise_and_expression

	infix bitwise_and_expression, BITWISE_AND, bitwise_shift_expression

	infix bitwise_shift_expression, (BITWISE_SHIFT_LEFT | BITWISE_SHIFT_RIGHT), term

	infix term, (PLUS | MINUS), factor

	infix factor, (STAR | DIVISION | MODULUS), signalled_expression

	prefix signalled_expression, (MINUS | INCREMENT | DECREMENT), ones_complement_expression

	prefix ones_complement_expression, ONES_COMPLEMENT, exponentiation_expression

	infix exponentiation_expression, EXPONENTIATION, try_cast

	try_cast = ((try_cast >> e, AS, type_reference >> typeRef) ^ TryCastExpression(Target: e, Type: typeRef)) | member_reference

	member_reference = ((member_reference >> e, DOT, ID >> name) ^ newMemberReference(e, name)) | slicing

	slicing = ((member_reference >> e, LBRACK, slice_list >> indices, RBRACK) ^ newSlicing(e, indices)) | invocation

	slice = (
			(
				(COLON ^ OmittedExpression.Default) >> begin,
				(expression | ("" ^ OmittedExpression.Default)) >> end,
				(omitted_expression | "") >> step
			)
			|
			(
				expression >> begin,
				((omitted_expression >> end,
					((omitted_expression >> step) | ""))
				| "")
			)
		) ^ newSlice(begin, end, step)

	list_of slice

	omitted_expression = (COLON, expression) | (COLON ^ OmittedExpression.Default)

	invocation = ((member_reference >> target, LPAREN, optional_expression_list >> args, RPAREN) ^ newInvocation(target, args)) \
		| atom

	type_reference = type_reference_simple

	type_reference_simple = (qualified_name >> qname) ^ SimpleTypeReference(Name: qname)

	atom = integer | boolean | reference | array_literal | list_literal \
		| string_literal | null_literal | parenthesized_expression \
		| self_literal | super_literal

	parenthesized_expression = (LPAREN, expression >> e, RPAREN) ^ e

	null_literal = NULL ^ [| null |]

	super_literal = SUPER ^ [| super |]

	self_literal = SELF ^ [| self |]

	string_literal = ((SDQS | TDQS | SQS) >> s) ^ newStringLiteral(s)

	array_literal = (LPAREN,
		((COMMA ^ [])
		| ((expression >> e, ++(COMMA, expression) >> tail) ^ prepend(e, tail))
		| ((expression >> e, COMMA) ^ [e])) >> items
		,RPAREN) ^ newArrayLiteral(items)

	list_literal = (LBRACK, optional_expression_list >> items, RBRACK) ^ newListLiteral(items)

	list_of expression

	list_of declaration

	reference = ID >> r ^ newReference(r)

	integer = (NUM >> n ^ newInteger(n, NumberStyles.None)) \
		| (HEXNUM >> n ^ newInteger(n, NumberStyles.HexNumber))

	boolean = true_literal | false_literal

	true_literal = TRUE ^ [| true |]

	false_literal = FALSE ^ [| false |]

	eol = ++EOL | ~_

	def newSlicing(target as Expression, slices):
		node = SlicingExpression(Target: target)
		for slice in slices: node.Indices.Add(slice)
		return node

	def newSlice(begin as Expression, end as Expression, step as Expression):
		return Slice(begin, end, step)

	def newRValue(items as List):
		if len(items) > 1: return newArrayLiteral(items)
		return items[0]

	def newForStatement(declarations, e as Expression, body as Block):
		node = ForStatement(Iterator: e, Block: body)
		for d in declarations: node.Declarations.Add(d)
		return node

	def newDeclaration(name, type as TypeReference):
		return Declaration(Name: tokenValue(name), Type: type)

	def newDeclarationStatement(d as Declaration,  initializer as Expression):
		return DeclarationStatement(Declaration: d, Initializer: initializer)

	def newIfStatement(condition as Expression, trueBlock as Block):
		return IfStatement(Condition: condition, TrueBlock: trueBlock)

	def newModule(doc, imports, members, stmts):
		m = Module(Documentation: doc)
		for item in imports: m.Imports.Add(item)
		for member in members: m.Members.Add(member)
		for stmt as Statement in stmts: m.Globals.Add(stmt)
		return m

	def newImport(qname as string):
		return Import(Namespace: qname)

	def newInteger(t, style as NumberStyles):
		value = int.Parse(tokenValue(t), style)
		return IntegerLiteralExpression(Value: value)

	def newMethod(name, parameters, body as Block):
		node = Method(Name: tokenValue(name), Body: body)
		for p in parameters: node.Parameters.Add(p)
		return node

	def newClass(name, members):
		klass = ClassDefinition(Name: tokenValue(name))
		if members is not null:
			for member in members: klass.Members.Add(member)
		return klass

	def newInvocation(target as Expression, args as List):
		mie = MethodInvocationExpression(Target: target)
		for arg in args: mie.Arguments.Add(arg)
		return mie

	def newReference(t):
		return ReferenceExpression(Name: tokenValue(t))

	def newMemberReference(target as Expression, name):
		return MemberReferenceExpression(Target: target, Name: tokenValue(name))

	def newArrayLiteral(items):
		literal = ArrayLiteralExpression()
		for item in items:
			literal.Items.Add(item)
		return literal

	def newListLiteral(items):
		literal = ListLiteralExpression()
		for item in items: literal.Items.Add(item)
		return literal

	def newStringLiteral(s):
		return StringLiteralExpression(Value: tokenValue(s))

	def newInfixExpression(op, l as Expression, r as Expression):
		return BinaryExpression(Operator: binaryOperatorFor(op), Left: l, Right: r)

	def newPrefixExpression(op, e as Expression):
		return UnaryExpression(Operator: unaryOperatorFor(op), Operand: e)

	def unaryOperatorFor(op):
		match tokenValue(op):
			case "not": return UnaryOperatorType.LogicalNot
			case "-": return UnaryOperatorType.UnaryNegation
			case "~": return UnaryOperatorType.OnesComplement
			case "++": return UnaryOperatorType.Increment
			case "--": return UnaryOperatorType.Decrement

	def binaryOperatorFor(op):
		match tokenValue(op):
			case "is": return BinaryOperatorType.ReferenceEquality
			case "is not": return BinaryOperatorType.ReferenceInequality
			case "in": return BinaryOperatorType.Member
			case "not in": return BinaryOperatorType.NotMember
			case "and": return BinaryOperatorType.And
			case "or": return BinaryOperatorType.Or
			case "|": return BinaryOperatorType.BitwiseOr
			case "&": return BinaryOperatorType.BitwiseAnd
			case "^": return BinaryOperatorType.ExclusiveOr
			case "+": return BinaryOperatorType.Addition
			case "-": return BinaryOperatorType.Subtraction
			case "*": return BinaryOperatorType.Multiply
			case "**": return BinaryOperatorType.Exponentiation
			case "/": return BinaryOperatorType.Division
			case "%": return BinaryOperatorType.Modulus
			case "=": return BinaryOperatorType.Assign
			case "==": return BinaryOperatorType.Equality
			case "!=": return BinaryOperatorType.Inequality
			case "+=": return BinaryOperatorType.InPlaceAddition
			case "-=": return BinaryOperatorType.InPlaceSubtraction
			case "/=": return BinaryOperatorType.InPlaceDivision
			case "*=": return BinaryOperatorType.InPlaceMultiply
			case "^=": return BinaryOperatorType.InPlaceExclusiveOr
			case "&=": return BinaryOperatorType.InPlaceBitwiseAnd
			case "|=": return BinaryOperatorType.InPlaceBitwiseOr
			case ">>": return BinaryOperatorType.ShiftRight
			case "<<": return BinaryOperatorType.ShiftLeft
			case "<": return BinaryOperatorType.LessThan
			case "<=": return BinaryOperatorType.LessThanOrEqual
			case ">": return BinaryOperatorType.GreaterThan
			case ">=": return BinaryOperatorType.GreaterThanOrEqual

	def newAssignment(l as Expression, r as Expression):
		return [| $l = $r |]

	def newBlock(stmts):
		b = Block()
		for item in stmts:
			b.Statements.Add(item)
		return b

	def prepend(first, tail as List):
		if first is null: return tail
		return [first] + tail

	def buildQName(q, rest):
		return join(tokenValue(t) for t in prepend(q, rest), '.')

namespace Boo.OMeta.Parser

import Boo.OMeta
import Boo.PatternMatching

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

import System.Globalization

macro infix:

	l, op, r = infix.Arguments

	return ExpressionStatement([| $l = ((($l >> l, $op >> op, $r >> r) ^ newInfixExpression(op, l, r)) | $r) |])

macro infixr:

	l, op, r = infixr.Arguments

	return ExpressionStatement([| $l = ((($r >> l, $op >> op, $l >> r) ^ newInfixExpression(op, l, r)) | $r) |])

macro prefix:

	rule, op, next = prefix.Arguments

	return ExpressionStatement([| $rule = ($op >> op, $rule >> e) ^ newPrefixExpression(op, e) | $next |])

macro list_of:
"""
Generates rules for lists of the given expression.

	list_of expression

Expands to something that matches:

	expression (COMMA expression)+
"""

	rule, = list_of.Arguments

	block as Block = list_of.ParentNode

	listRuleName = ReferenceExpression(Name: "${rule}_list")
	listRule = [| $listRuleName = ((($rule >> first), ++((COMMA, $rule >> e) ^ e) >> rest) ^ prepend(first, rest)) | ($rule >> v ^ [v]) |]
	block.Add(listRule)

	optionalRuleName = ReferenceExpression(Name: "optional_${rule}_list")
	optionalListRule = [| $optionalRuleName = $listRuleName | ("" ^ []) |]
	block.Add(optionalListRule)

ometa BooParser < WhitespaceSensitiveTokenizer:

	tokens:
		equality = "=="
		inequality = "!="
		assign = "="
		assign_inplace = "+=" | "-=" | "*=" | "/=" | "%=" | "^="
		xor = "^"
		increment = "++"
		decrement = "--"
		plus = "+"
		minus = "-"
		exponentiation = "**"
		star = "*"
		division = "/"
		modulus = "%"
		ones_complement = "~"
		bitwise_shift_left = "<<"
		bitwise_shift_right = ">>"
		greater_than_eq = ">="
		greater_than = ">"
		less_than_eq = "<="
		less_than = "<"
		bitwise_and = "&"
		bitwise_or = "|"
		hexnum = ("0x", ++(hex_digit | digit) >> ds) ^ makeString(ds)
		num = ++digit
		colon = ":"
		dot = "."
		comma = ","
		lparen = "(", enterWhitespaceAgnosticRegion
		rparen = ")", leaveWhitespaceAgnosticRegion
		lbrack = "[", enterWhitespaceAgnosticRegion
		rbrack = "]", leaveWhitespaceAgnosticRegion
		kw = (keywords >> value, ~(letter | digit | '_')) ^ value
		tdqs = ('"""', ++(~'"""', _) >> s, '"""') ^ s
		sdqs = ('"', ++(~'"', _) >> s, '"') ^ s
		sqs = ("'", ++(~"'", _) >> s, "'") ^ s
		id = ((letter | '_') >> p, --(letter | digit | '_') >> s) ^ makeString(p, s)

	hex_digit = _ >> c as char and ((c >= char('a') and c <= char('f')) or (c >= char('A') and c <= char('Z')))

	keywords "class", "def", "import", "pass", "return", "true", \
		"false", "and", "or", "as", "not", "if", "is", "null", \
		"for", "in", "yield", "self", "super"

	keyword[expected] = ((KW >> t) and (expected is tokenValue(t))) ^ t

	module = (
		--EOL,
		(docstring >> s | ""),
		--import_declaration >> ids,
		--module_member >> members,
		--stmt >> stmts,
		--whitespace
	) ^ newModule(s, ids, members, stmts)

	docstring = (TDQS >> t, eol) ^ tokenValue(t)

	import_declaration = (IMPORT, qualified_name >> qn, eol) ^ newImport(qn)

	qualified_name = (ID >> qualifier, --((DOT, ID >> n) ^ n) >> suffix)^ buildQName(qualifier, suffix)

	module_member = class_def | method

	class_def = (
		CLASS, ID >> className, begin_block, class_body >> body, end_block
	) ^ newClass(className, body)

	begin_block = COLON, INDENT

	end_block = DEDENT

	class_body = ((PASS, eol) ^ null) | (++class_member >> members ^ members)

	class_member = method | class_def

	method = (
		DEF, ID >> name, LPAREN, optional_parameter_list >> parameters, RPAREN, block >> body
	) ^ newMethod(name, parameters, body)

	list_of parameter

	parameter = ((ID >> name, AS, type_reference >> type) | (ID >> name)) ^ ParameterDeclaration(Name: tokenValue(name), Type: type)

	block = empty_block | non_empty_block

	empty_block = (begin_block, (PASS, eol), end_block) ^ Block()

	non_empty_block = (begin_block, ++stmt >> stmts, end_block)  ^ newBlock(stmts)

	stmt = ((stmt_block >> s) | (stmt_line >> s, eol)) ^ s

	stmt_line = (~~(ID, AS), stmt_declaration) \
		| stmt_expression \
		| stmt_return \
		| stmt_yield

	stmt_yield = (YIELD, assignment >> e, stmt_modifier >> m) ^ YieldStatement(Expression: e, Modifier: m)

	stmt_modifier = ((IF, assignment >> e) ^ StatementModifier(Type: StatementModifierType.If, Condition: e)) | ""

	stmt_declaration = (declaration >> d, ASSIGN, expression >> e) ^ newDeclarationStatement(d, e)

	declaration = (ID >> name, ((AS, type_reference >> typeRef) | "")) ^ newDeclaration(name, typeRef)

	stmt_block = stmt_if | stmt_for

	stmt_for = (FOR, declaration_list >> dl, IN, expression >> e, block >> body) ^ newForStatement(dl, e, body)

	stmt_if = (IF, expression >> e, block >> trueBlock) ^ newIfStatement(e, trueBlock)

	stmt_return = (RETURN, optional_expression >> e, stmt_modifier >> m) ^ ReturnStatement(Expression: e, Modifier: m)

	optional_expression = expression | ""

	stmt_expression = (multi_assignment | assignment) >> e ^ ExpressionStatement(Expression: e)

	multi_assignment = (expression >> l, ASSIGN >> op, assignment_list >> items) ^ newInfixExpression(op, l, newRValue(items))

	list_of assignment

	infixr assignment, (ASSIGN | ASSIGN_INPLACE), expression

	expression = or_expression

	infix or_expression, OR, and_expression

	infix and_expression, AND, not_expression

	prefix not_expression, NOT, membership_expression

	infix membership_expression, (IN | ((NOT, IN) ^ makeToken("not in"))), identity_test_expression

	infix identity_test_expression, ((IS, NOT) ^ makeToken("is not")) | IS, comparison

	infix comparison, (EQUALITY | INEQUALITY | GREATER_THAN | GREATER_THAN_EQ | LESS_THAN | LESS_THAN_EQ), bitwise_or_expression

	infix bitwise_or_expression, BITWISE_OR, bitwise_xor_expression

	infix bitwise_xor_expression, XOR, bitwise_and_expression

	infix bitwise_and_expression, BITWISE_AND, bitwise_shift_expression

	infix bitwise_shift_expression, (BITWISE_SHIFT_LEFT | BITWISE_SHIFT_RIGHT), term

	infix term, (PLUS | MINUS), factor

	infix factor, (STAR | DIVISION | MODULUS), signalled_expression

	prefix signalled_expression, (MINUS | INCREMENT | DECREMENT), ones_complement_expression

	prefix ones_complement_expression, ONES_COMPLEMENT, exponentiation_expression

	infix exponentiation_expression, EXPONENTIATION, try_cast

	try_cast = ((try_cast >> e, AS, type_reference >> typeRef) ^ TryCastExpression(Target: e, Type: typeRef)) | member_reference

	member_reference = ((member_reference >> e, DOT, ID >> name) ^ newMemberReference(e, name)) | slicing

	slicing = ((member_reference >> e, LBRACK, slice_list >> indices, RBRACK) ^ newSlicing(e, indices)) | invocation

	slice = (
			(
				(COLON ^ OmittedExpression.Default) >> begin,
				(expression | ("" ^ OmittedExpression.Default)) >> end,
				(omitted_expression | "") >> step
			)
			|
			(
				expression >> begin,
				((omitted_expression >> end,
					((omitted_expression >> step) | ""))
				| "")
			)
		) ^ newSlice(begin, end, step)

	list_of slice

	omitted_expression = (COLON, expression) | (COLON ^ OmittedExpression.Default)

	invocation = ((member_reference >> target, LPAREN, optional_expression_list >> args, RPAREN) ^ newInvocation(target, args)) \
		| atom

	type_reference = type_reference_simple

	type_reference_simple = (qualified_name >> qname) ^ SimpleTypeReference(Name: qname)

	atom = integer | boolean | reference | array_literal | list_literal \
		| string_literal | null_literal | parenthesized_expression \
		| self_literal | super_literal

	parenthesized_expression = (LPAREN, expression >> e, RPAREN) ^ e

	null_literal = NULL ^ [| null |]

	super_literal = SUPER ^ [| super |]

	self_literal = SELF ^ [| self |]

	string_literal = ((SDQS | TDQS | SQS) >> s) ^ newStringLiteral(s)

	array_literal = (LPAREN,
		((COMMA ^ [])
		| ((expression >> e, ++(COMMA, expression) >> tail) ^ prepend(e, tail))
		| ((expression >> e, COMMA) ^ [e])) >> items
		,RPAREN) ^ newArrayLiteral(items)

	list_literal = (LBRACK, optional_expression_list >> items, RBRACK) ^ newListLiteral(items)

	list_of expression

	list_of declaration

	reference = ID >> r ^ newReference(r)

	integer = (NUM >> n ^ newInteger(n, NumberStyles.None)) \
		| (HEXNUM >> n ^ newInteger(n, NumberStyles.HexNumber))

	boolean = true_literal | false_literal

	true_literal = TRUE ^ [| true |]

	false_literal = FALSE ^ [| false |]

	eol = ++EOL | ~_

	def newSlicing(target as Expression, slices):
		node = SlicingExpression(Target: target)
		for slice in slices: node.Indices.Add(slice)
		return node

	def newSlice(begin as Expression, end as Expression, step as Expression):
		return Slice(begin, end, step)

	def newRValue(items as List):
		if len(items) > 1: return newArrayLiteral(items)
		return items[0]

	def newForStatement(declarations, e as Expression, body as Block):
		node = ForStatement(Iterator: e, Block: body)
		for d in declarations: node.Declarations.Add(d)
		return node

	def newDeclaration(name, type as TypeReference):
		return Declaration(Name: tokenValue(name), Type: type)

	def newDeclarationStatement(d as Declaration,  initializer as Expression):
		return DeclarationStatement(Declaration: d, Initializer: initializer)

	def newIfStatement(condition as Expression, trueBlock as Block):
		return IfStatement(Condition: condition, TrueBlock: trueBlock)

	def newModule(doc, imports, members, stmts):
		m = Module(Documentation: doc)
		for item in imports: m.Imports.Add(item)
		for member in members: m.Members.Add(member)
		for stmt as Statement in stmts: m.Globals.Add(stmt)
		return m

	def newImport(qname as string):
		return Import(Namespace: qname)

	def newInteger(t, style as NumberStyles):
		value = int.Parse(tokenValue(t), style)
		return IntegerLiteralExpression(Value: value)

	def newMethod(name, parameters, body as Block):
		node = Method(Name: tokenValue(name), Body: body)
		for p in parameters: node.Parameters.Add(p)
		return node

	def newClass(name, members):
		klass = ClassDefinition(Name: tokenValue(name))
		if members is not null:
			for member in members: klass.Members.Add(member)
		return klass

	def newInvocation(target as Expression, args as List):
		mie = MethodInvocationExpression(Target: target)
		for arg in args: mie.Arguments.Add(arg)
		return mie

	def newReference(t):
		return ReferenceExpression(Name: tokenValue(t))

	def newMemberReference(target as Expression, name):
		return MemberReferenceExpression(Target: target, Name: tokenValue(name))

	def newArrayLiteral(items):
		literal = ArrayLiteralExpression()
		for item in items:
			literal.Items.Add(item)
		return literal

	def newListLiteral(items):
		literal = ListLiteralExpression()
		for item in items: literal.Items.Add(item)
		return literal

	def newStringLiteral(s):
		return StringLiteralExpression(Value: tokenValue(s))

	def newInfixExpression(op, l as Expression, r as Expression):
		return BinaryExpression(Operator: binaryOperatorFor(op), Left: l, Right: r)

	def newPrefixExpression(op, e as Expression):
		return UnaryExpression(Operator: unaryOperatorFor(op), Operand: e)

	def unaryOperatorFor(op):
		match tokenValue(op):
			case "not": return UnaryOperatorType.LogicalNot
			case "-": return UnaryOperatorType.UnaryNegation
			case "~": return UnaryOperatorType.OnesComplement
			case "++": return UnaryOperatorType.Increment
			case "--": return UnaryOperatorType.Decrement

	def binaryOperatorFor(op):
		match tokenValue(op):
			case "is": return BinaryOperatorType.ReferenceEquality
			case "is not": return BinaryOperatorType.ReferenceInequality
			case "in": return BinaryOperatorType.Member
			case "not in": return BinaryOperatorType.NotMember
			case "and": return BinaryOperatorType.And
			case "or": return BinaryOperatorType.Or
			case "|": return BinaryOperatorType.BitwiseOr
			case "&": return BinaryOperatorType.BitwiseAnd
			case "^": return BinaryOperatorType.ExclusiveOr
			case "+": return BinaryOperatorType.Addition
			case "-": return BinaryOperatorType.Subtraction
			case "*": return BinaryOperatorType.Multiply
			case "**": return BinaryOperatorType.Exponentiation
			case "/": return BinaryOperatorType.Division
			case "%": return BinaryOperatorType.Modulus
			case "=": return BinaryOperatorType.Assign
			case "==": return BinaryOperatorType.Equality
			case "!=": return BinaryOperatorType.Inequality
			case "+=": return BinaryOperatorType.InPlaceAddition
			case "-=": return BinaryOperatorType.InPlaceSubtraction
			case "/=": return BinaryOperatorType.InPlaceDivision
			case "*=": return BinaryOperatorType.InPlaceMultiply
			case "^=": return BinaryOperatorType.InPlaceExclusiveOr
			case ">>": return BinaryOperatorType.ShiftRight
			case "<<": return BinaryOperatorType.ShiftLeft
			case "<": return BinaryOperatorType.LessThan
			case "<=": return BinaryOperatorType.LessThanOrEqual
			case ">": return BinaryOperatorType.GreaterThan
			case ">=": return BinaryOperatorType.GreaterThanOrEqual

	def newAssignment(l as Expression, r as Expression):
		return [| $l = $r |]

	def newBlock(stmts):
		b = Block()
		for item in stmts:
			b.Statements.Add(item)
		return b

	def prepend(first, tail as List):
		if first is null: return tail
		return [first] + tail

	def buildQName(q, rest):
		return join(tokenValue(t) for t in prepend(q, rest), '.')