single line comments

single line comments

namespace Boo.OMeta.Parser

import Boo.OMeta
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
		lbrace = "{", enterWhitespaceAgnosticRegion
		rbrace = "}", leaveWhitespaceAgnosticRegion

		kw = (keywords >> value, ~(letter | digit | '_')) ^ value
		tdq = '"""'
		dq = '"'
		sqs = ("'", ++(~"'", _) >> s, "'") ^ s
		id = ((letter | '_') >> p, --(letter | digit | '_') >> s) ^ makeString(p, s)

	space = line_comment | super

	line_comment = '#', --(~newline, _)

	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", "interface", "in", "yield", "self", "super", "of"

	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 = (TDQ, ++(~tdq, string_char) >> s, TDQ, eol) ^ makeString(s)

	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 | stmt_line

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

	stmt_macro = (ID >> name, assignment_list >> args, ((block >> b) | (stmt_modifier >> m))) ^ newMacro(name, args, b, m)

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

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

	stmt_declaration = (declaration >> d, ((ASSIGN, expression >> e) | ""), eol) ^ 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, rvalue >> e, block >> body) ^ newForStatement(dl, e, body)

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

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

	optional_assignment = assignment | ""

	stmt_expression = ((multi_assignment | assignment) >> e, stmt_modifier >> m) ^ ExpressionStatement(Expression: e, Modifier: m)

	multi_assignment = (expression >> l, ASSIGN >> op, rvalue >> r) ^ newInfixExpression(op, l, r)

	rvalue = assignment_list >> items ^ 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

	// need to fix memoization for rule with arguments
	infix identity_test_expression, (IS, ((NOT ^ makeToken("is not")) | ("" ^ makeToken("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_assignment_list >> args, RPAREN) ^ newInvocation(target, args)) \
		| atom

	type_reference = type_reference_simple | type_reference_array

	type_reference_array = (LPAREN, ranked_type_reference >> tr, RPAREN) ^ tr

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

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

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

	null_literal = NULL ^ [| null |]

	super_literal = SUPER ^ [| super |]

	self_literal = SELF ^ [| self |]

	string_literal = (SQS >> s) ^ newStringLiteral(s)

	string_interpolation = (
		DQ,
		++(
			((++(~('"' | '$'), string_char) >> s) ^ StringLiteralExpression(makeString(s)))
			| (('${', expression >> v, --whitespace, '}') ^ v)
			| ('$', atom)
			) >> items,
		DQ) ^ newStringInterpolation(items)

	string_char = ('\\', ('\\' | '$')) | (~'\\', _)

	array_literal = array_literal_empty | array_literal_single | array_literal_multi

	array_literal_empty = (LPAREN, array_literal_type >> type, COMMA, RPAREN) ^ newArrayLiteral(type, [])

	array_literal_single = (LPAREN, array_literal_type >> type, assignment >> e, COMMA, RPAREN) ^ newArrayLiteral(type, [e])

	array_literal_multi = (LPAREN, array_literal_type >> type, assignment >> e, ++(COMMA, assignment) >> tail, (COMMA | ""), RPAREN) ^ newArrayLiteral(type, prepend(e, tail))

	array_literal_type = ((OF, ranked_type_reference >> type, COLON) | "") ^ type

	ranked_type_reference = ((type_reference >> type), ((COMMA,  integer >> rank) | "")) ^ ArrayTypeReference(ElementType: type, Rank: rank)

	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 | ~_

namespace Boo.OMeta.Parser

import Boo.OMeta
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
		lbrace = "{", enterWhitespaceAgnosticRegion
		rbrace = "}", leaveWhitespaceAgnosticRegion

		kw = (keywords >> value, ~(letter | digit | '_')) ^ value
		tdq = '"""'
		dq = '"'
		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", "of"

	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 = (TDQ, ++(~tdq, string_char) >> s, TDQ, eol) ^ makeString(s)

	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 | stmt_line

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

	stmt_macro = (ID >> name, assignment_list >> args, ((block >> b) | (stmt_modifier >> m))) ^ newMacro(name, args, b, m)

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

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

	stmt_declaration = (declaration >> d, ((ASSIGN, expression >> e) | ""), eol) ^ 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, rvalue >> e, block >> body) ^ newForStatement(dl, e, body)

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

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

	optional_assignment = assignment | ""

	stmt_expression = ((multi_assignment | assignment) >> e, stmt_modifier >> m) ^ ExpressionStatement(Expression: e, Modifier: m)

	multi_assignment = (expression >> l, ASSIGN >> op, rvalue >> r) ^ newInfixExpression(op, l, r)

	rvalue = assignment_list >> items ^ 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

	// need to fix memoization for rule with arguments
	infix identity_test_expression, (IS, ((NOT ^ makeToken("is not")) | ("" ^ makeToken("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_assignment_list >> args, RPAREN) ^ newInvocation(target, args)) \
		| atom

	type_reference = type_reference_simple | type_reference_array

	type_reference_array = (LPAREN, ranked_type_reference >> tr, RPAREN) ^ tr

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

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

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

	null_literal = NULL ^ [| null |]

	super_literal = SUPER ^ [| super |]

	self_literal = SELF ^ [| self |]

	string_literal = (SQS >> s) ^ newStringLiteral(s)

	string_interpolation = (
		DQ,
		++(
			((++(~('"' | '$'), string_char) >> s) ^ StringLiteralExpression(makeString(s)))
			| (('${', expression >> v, --whitespace, '}') ^ v)
			| ('$', atom)
			) >> items,
		DQ) ^ newStringInterpolation(items)

	string_char = ('\\', ('\\' | '$')) | (~'\\', _)

	array_literal = array_literal_empty | array_literal_single | array_literal_multi

	array_literal_empty = (LPAREN, array_literal_type >> type, COMMA, RPAREN) ^ newArrayLiteral(type, [])

	array_literal_single = (LPAREN, array_literal_type >> type, assignment >> e, COMMA, RPAREN) ^ newArrayLiteral(type, [e])

	array_literal_multi = (LPAREN, array_literal_type >> type, assignment >> e, ++(COMMA, assignment) >> tail, (COMMA | ""), RPAREN) ^ newArrayLiteral(type, prepend(e, tail))

	array_literal_type = ((OF, ranked_type_reference >> type, COLON) | "") ^ type

	ranked_type_reference = ((type_reference >> type), ((COMMA,  integer >> rank) | "")) ^ ArrayTypeReference(ElementType: type, Rank: rank)

	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 | ~_


don't create output file for debugging when not running on mono

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

namespace booi

from System import AppDomain, Console, Environment, Guid, STAThreadAttribute
import System.Linq.Enumerable
from System.Diagnostics import Trace, TraceLevel, TextWriterTraceListener, Stopwatch
from System.IO import TextReader, StreamWriter, Directory, File, Path
from System.Reflection import Assembly
from Boo.Lang.Compiler import CompilerContext, CompilerOutputType
from Boo.Lang.Compiler.IO import StringInput, FileInput
from Boo.Lang.Compiler.Pipelines import CompileToMemory, CompileToFile
from Boo.Lang.Compiler.TypeSystem.Reflection import IAssemblyReference
from Boo.Lang.Useful.CommandLine import CommandLineException

class Program:

    public static final Version = 'Booi ' + BooVersion
    public static final DefaultErrorCode = 127
    public static final DefaultSuccessCode = 0

    _args as (string)
    _cmdline as CommandLine

    _assemblyResolver = AssemblyResolver()
    _params = CompilerParameters()

    def constructor(cmdline, args as (string)):
        _cmdline = cmdline
        _args = args

        # Register a custom resolver to provide compiler assemblies at runtime
        AppDomain.CurrentDomain.AssemblyResolve += _assemblyResolver.AssemblyResolve

    protected def LoadDlls(path as string):
        dlls = Directory.GetFiles(path, '*.dll')
        for dll in dlls:
            Trace.TraceInformation('Loading assembly {0}', dll)
            asm = _params.LoadAssembly(dll)
            _params.References.Add(asm)
            _assemblyResolver.AddAssembly(asm.Assembly)

    protected def LoadPackages(path as string):
        # Load all DLLs in the root directory
        LoadDlls(path)

        # Process NuGet style repositories by looking into package directories,
        # only accessing their latests versions if more than one is present.
        dirs = Directory.GetDirectories(path) \
              .GroupBy({ x | /\.\d.*$/.Replace(x, '') }) \
              .Select({ g | g.OrderBy({ f | f.ToLower() }).Last() }) \
              .ToList()

        # Get executing framework version to discard newer assemblies
        maxversion = typeof(System.Type).Assembly.GetName().Version
        for dir in dirs:
            dir = Path.Combine(dir, 'lib')
            continue unless Directory.Exists(dir)

            # Load any generic assemblies in the lib folder
            LoadDlls(dir)

            # Assume we always target the full framework (netXX)
            path = Directory.GetDirectories(dir, 'net*') \
                 .OrderBy({ f | f[-2:] }) \
                 .Where({ f |
                    maxversion >= System.Version(
                        join(f[-2:].ToCharArray(), '.')
                    )
                 }) \
                 .LastOrDefault()

            LoadDlls(path) if path

    def Run() as int:

        # TODO: Instead of always creating a temp directory to copy
        #       all the references, it should be possible to inject code
        #       in the generated assembly that registers to the AppDomain
        #       assembly load event and loads them based on the configuration
        #       used when compiling. This will allow to load NuGet references
        #       on demand too and should work when using nunit.

        _params.OutputType = CompilerOutputType.Auto
        _params.Debug = _cmdline.Debug
        _params.Ducky = _cmdline.Ducky
        _params.Strict = _cmdline.Strict
        _params.WhiteSpaceAgnostic = _cmdline.Wsa
        _params.Cache = _cmdline.Cache

        if _cmdline.Verbose:
            _params.TraceLevel = TraceLevel.Verbose
            Trace.Listeners.Add(TextWriterTraceListener(Console.Error))

        if (_params.Debug and IsMono()) or _cmdline.Runner or _cmdline.Output:
            # Save to disk to ensure we get symbols loaded on runtime errors
            _params.GenerateInMemory = false
            _params.Pipeline = CompileToFile()

            if not _cmdline.Output:
                # Find a temporary directory name for the output
                while true:
                    path = Path.Combine(Path.GetTempPath(), 'booi-' + Path.GetRandomFileName())
                    path = path.Replace('.', '-')
                    break unless Directory.Exists(path)
                Trace.TraceInformation("Creating temporary directory '{0}'", path)
                Directory.CreateDirectory(path)
                _params.OutputAssembly = Path.Combine(path, 'booi-main.exe')
            else:
                if Directory.Exists(_cmdline.Output):
                    path = _cmdline.Output
                    _params.OutputAssembly = Path.Combine(path, 'booi-main.exe')
                else:
                    path = Path.GetDirectoryName(_cmdline.Output)
                    _params.OutputAssembly = _cmdline.Output

        else:
            _params.GenerateInMemory = true
            _params.Pipeline = CompileToMemory()

        for d in _cmdline.Defines:
            _params.Defines.Add(d.Key, d.Value)

        for libpath in _cmdline.LibPaths:
            try:
                _params.LibPaths.AddUnique(Path.GetFullPath(libpath))
            except ex as System.ArgumentException:
                Trace.TraceError("LibPath '{0}' not found", libpath)

        sw = System.Diagnostics.Stopwatch()
        sw.Start()

        for reference in _cmdline.References:
            try:
                asmref = _params.LoadAssembly(reference)
                if asmref is null:
                    printerr string.Format(Boo.Lang.Resources.StringResources.BooC_UnableToLoadAssembly, reference)
                    return DefaultErrorCode

                _params.References.Add(asmref)
                _assemblyResolver.AddAssembly(asmref.Assembly)
            except ex:
                printerr "Error loading reference '{0}': {1}", reference, ex
                return DefaultErrorCode

        # Load packages
        for package in _cmdline.Packages:
            LoadPackages(package) if Directory.Exists(package)

        sw.Stop()
        Trace.TraceInformation("Loading references took {0}ms", sw.ElapsedMilliseconds)

        for file in _cmdline.Files:
            continue if file == '-'
            if Directory.Exists(file):
                for f in Directory.GetFiles(file, '*.boo'):
                    _params.Input.Add(FileInput(f))
            else:
                _params.Input.Add(FileInput(file))

        if not len(_cmdline.Files) or _cmdline.Files.Contains('-'):
            _params.Input.Add(StringInput('<stdin>', consume(Console.In)))

        # Set the MAIN define if we are compiling a single script
        if 1 == len(_params.Input) and not _params.Defines.ContainsKey('MAIN'):
            _params.Defines.Add('MAIN', '')

        sw.Restart()
        generated = Compile()
        return DefaultErrorCode if generated is null
        sw.Stop()
        Trace.TraceInformation('Compilation took {0}ms', sw.ElapsedMilliseconds)

        if _cmdline.Runner or _cmdline.Output:
            # Collect all dependencies in a temporary dir
            for asmref in _params.References:
                asm = asmref.Assembly
                # Skip dynamically generated assemblies
                continue if asm.IsDynamic

                try:
                    asmfile = Path.GetFileName(asm.Location)
                    if asmfile in ('System.dll', 'System.Core.dll', 'mscorlib.dll'):
                        continue

                    # HACK: The assembly containing the parser is imported dynamically by
                    #       the compiler, looking for it to be placed next to it.
                    #       Here we ensure that when the compiler is referenced the parser
                    #       is also copied next to it.
                    if asmfile == 'Boo.Lang.Compiler.dll':
                        File.Copy(
                            asm.Location.Replace('Boo.Lang.Compiler.dll', 'Boo.Lang.Parser.dll'),
                            Path.Combine(path, 'Boo.Lang.Parser.dll')
                        )

                    File.Copy(asm.Location, Path.Combine(path, asmfile))
                except ex:
                    Trace.TraceError("Error copying assembly '{0}' to '{1}'", asmref.Name, path)

            DumpSolutionFile()

        if _cmdline.Runner:
            # Replace place holders in script arguments
            asmpath = generated.Location
            args = List of string()
            if not len(_args):
                args.Add('"' + asmpath + '"')
            else:
                for arg in _args:
                    arg = arg.Replace('%filename%', Path.GetFileName(asmpath))
                    arg = arg.Replace('%pathname%', Path.GetDirectoryName(asmpath))
                    arg = arg.Replace('%assembly%', asmpath)
                    arg = arg.Replace('%solution%', asmpath[:-3] + 'sln')
                    if arg == '%':
                        arg = asmpath
                    args.Add('"' + arg + '"')

            # Try to execute the runner
            Trace.TraceInformation('Running command: {0} {1}', _cmdline.Runner, join(args, ' '))
            retval = runcommand(_cmdline.Runner, join(args, ' '))
        else:
            retval = Execute(generated, _args)

        # Clean up generated files
        if path and not _cmdline.Output:
            Trace.TraceInformation("Removing temporary directory '{0}'", path)
            Directory.Delete(path, true)

        return retval

    def Compile():
        context = CompilerContext(_params)
        _params.Pipeline.Run(context)

        if _cmdline.Warnings and len(context.Warnings):
            printerr(context.Warnings.ToString())

        if len(context.Errors):
            printerr(context.Errors.ToString(_params.TraceVerbose))
            return null

        if not context.GeneratedAssembly:
            return Assembly.LoadFrom(context.GeneratedAssemblyFileName)

        return context.GeneratedAssembly

    def Execute(asm as Assembly, argv as (string)):
        exitCode = DefaultSuccessCode
        try:
            _assemblyResolver.AddAssembly(asm)

            main = asm.EntryPoint
            if not main:
                printerr 'Error: Entry point not found'
                return DefaultErrorCode

            if len(main.GetParameters()) > 0:
                returnValue = main.Invoke(null, (argv,))
            else:
                returnValue = main.Invoke(null, null)

            exitCode = returnValue if returnValue is not null
        except x as TargetInvocationException:
            printerr x.InnerException
            exitCode = DefaultErrorCode
        ensure:
            Environment.ExitCode = exitCode

        return exitCode

    protected def DumpSolutionFile():
    """ Create a MonoDevelop solution file to ease debugging. MonoDevelop allows defining
        managed executables as projects.
    """
        asmpath = Path.GetDirectoryName(_params.OutputAssembly)
        asmfile = Path.GetFileName(_params.OutputAssembly)
        asmname = asmfile[:-4]

        # Get assemblies automatically compiled from sources
        assemblies = [asmfile]
        for asmref as IAssemblyReference in _params.References:
            asm = asmref.Assembly
            if asm.IsDynamic:
                assemblies.Add(asmref.Name + '.dll')
                continue

            attrs = asm.GetCustomAttributes(CompilationTimeAttribute, false)
            if len(attrs):
                assemblies.Add(asmref.Name + '.dll')

        fpath = Path.Combine(asmpath, asmname + '.sln')
        using fs = StreamWriter(fpath):
            fs.WriteLine()
            fs.WriteLine('Microsoft Visual Studio Solution File, Format Version 11.00')
            fs.WriteLine('# Visual Studio 2010')
            fs.WriteLine('Global')
            fs.WriteLine('\tGlobalSection(MonoDevelopProperties) = preSolution')
            fs.WriteLine('\t\tStartupItem = {0}', asmname)
            fs.WriteLine('\tEndGlobalSection')
            fs.WriteLine('EndGlobal')

            for asmfile in assemblies:
                # Generate a deterministic Guid based on the file name
                using md5 = System.Security.Cryptography.MD5.Create():
                    bytes = System.Text.Encoding.Default.GetBytes(asmfile)
                    guid = Guid(md5.ComputeHash(bytes))

                fs.WriteLine('Project("{{8BC9CEB9-8B4A-11D0-8D11-00A0C91BC942}}") = "{0}", "{1}", "{{{2}}}"', asmfile[:-4], asmfile, guid)
                fs.WriteLine('\tProjectSection(MonoDevelopProperties) = preProject')
                fs.WriteLine('\t\tConfigurations = $0')
                fs.WriteLine('\t\t$0.Configuration = $1')
                fs.WriteLine('\t\t$1.name = Debug')
                fs.WriteLine('\t\t$1.OutputPath = ./')
                fs.WriteLine('\t\t$1.ctype = ProjectConfiguration')
                fs.WriteLine('\tEndProjectSection')
                fs.WriteLine('EndProject')

            # Include an NUnit assembly test collection project
            fs.WriteLine('Project("{9344bdbb-3e7f-41fc-a0dd-8665d75ee146}") = "nunit", "nunit.mdproj", "{4B9B3417-6A64-4D19-912A-9A7C50828CEA}"')
            fs.WriteLine('EndProject')

        # Generate an mdproj file for the NUnit assembly test collection
        fpath = Path.Combine(asmpath, 'nunit.mdproj')
        using fs = StreamWriter(fpath):
            fs.WriteLine('<?xml version="1.0" encoding="utf-8"?>')
            fs.WriteLine('<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">')
            fs.WriteLine('<PropertyGroup>')
            fs.WriteLine(' <Configuration Condition=" \'$(Configuration)\' == \'\' ">Default</Configuration>')
            fs.WriteLine(' <Platform Condition=" \'$(Platform)\' == \'\' ">AnyCPU</Platform>')
            fs.WriteLine(' <ItemType>NUnitAssemblyGroupProject</ItemType>')
            fs.WriteLine(' <ProductVersion>10.0.0</ProductVersion>')
            fs.WriteLine(' <SchemaVersion>2.0</SchemaVersion>')
            fs.WriteLine(' <ProjectGuid>{{4B9B3417-6A64-4D19-912A-9A7C50828CEA}}</ProjectGuid>')
            fs.WriteLine('</PropertyGroup>')
            fs.WriteLine('<PropertyGroup Condition=" \'$(Configuration)|$(Platform)\' == \'Default|AnyCPU\' ">')
            fs.WriteLine(' <Assemblies>')
            fs.WriteLine('  <Assemblies>')
            for asmfile in assemblies:
                fs.WriteLine('   <Assembly Path="{0}" />', Path.GetFullPath(Path.Combine(asmpath, asmfile)))
            fs.WriteLine('  </Assemblies>')
            fs.WriteLine(' </Assemblies>')
            fs.WriteLine('</PropertyGroup>')
            fs.WriteLine('</Project>')

def IsMono():
    return System.Type.GetType("Mono.Runtime") is not null

def runcommand(filename, arguments):
    p = System.Diagnostics.Process()
    p.StartInfo.Arguments = arguments
    p.StartInfo.CreateNoWindow = true
    p.StartInfo.UseShellExecute = true
    p.StartInfo.FileName = filename
    p.Start()
    p.WaitForExit()
    return p.ExitCode

def consume(reader as TextReader):
    return join(line for line in reader, "\n")

def printerr(*args):
    for arg in args:
        Console.Error.Write(arg)
        Console.Error.Write(' ')
    Console.Error.WriteLine()

[STAThread]
def Main(argv as (string)) as int:
    # Find the number of arguments that apply to booi
    argc = System.Array.IndexOf(argv, '--')
    if argc >= 0:
        argv = argv[:argc] + argv[argc+1:]
    else:
        argc = 0
        for arg in argv:
            argc++
            break unless arg.StartsWith('-')

    try:
        cmdline = CommandLine(argv[:argc])
    except ex as CommandLineException:
        printerr 'Error:', ex.Message
        return Program.DefaultErrorCode

    if cmdline.DoVersion:
        print Program.Version
        return Program.DefaultSuccessCode

    if not cmdline.IsValid or cmdline.DoHelp:
        print "Usage: booi [options] <script|-> [-- [script options]]"
        print "Options:"
        cmdline.PrintOptions(Console.Out)
        return Program.DefaultErrorCode

    return Program(cmdline, argv[argc:]).Run()

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

namespace booi

from System import AppDomain, Console, Environment, Guid, STAThreadAttribute
import System.Linq.Enumerable
from System.Diagnostics import Trace, TraceLevel, TextWriterTraceListener, Stopwatch
from System.IO import TextReader, StreamWriter, Directory, File, Path
from System.Reflection import Assembly
from Boo.Lang.Compiler import CompilerContext, CompilerOutputType
from Boo.Lang.Compiler.IO import StringInput, FileInput
from Boo.Lang.Compiler.Pipelines import CompileToMemory, CompileToFile
from Boo.Lang.Compiler.TypeSystem.Reflection import IAssemblyReference
from Boo.Lang.Useful.CommandLine import CommandLineException

class Program:

    public static final Version = 'Booi ' + BooVersion
    public static final DefaultErrorCode = 127
    public static final DefaultSuccessCode = 0

    _args as (string)
    _cmdline as CommandLine

    _assemblyResolver = AssemblyResolver()
    _params = CompilerParameters()

    def constructor(cmdline, args as (string)):
        _cmdline = cmdline
        _args = args

        # Register a custom resolver to provide compiler assemblies at runtime
        AppDomain.CurrentDomain.AssemblyResolve += _assemblyResolver.AssemblyResolve

    protected def LoadDlls(path as string):
        dlls = Directory.GetFiles(path, '*.dll')
        for dll in dlls:
            Trace.TraceInformation('Loading assembly {0}', dll)
            asm = _params.LoadAssembly(dll)
            _params.References.Add(asm)
            _assemblyResolver.AddAssembly(asm.Assembly)

    protected def LoadPackages(path as string):
        # Load all DLLs in the root directory
        LoadDlls(path)

        # Process NuGet style repositories by looking into package directories,
        # only accessing their latests versions if more than one is present.
        dirs = Directory.GetDirectories(path) \
              .GroupBy({ x | /\.\d.*$/.Replace(x, '') }) \
              .Select({ g | g.OrderBy({ f | f.ToLower() }).Last() }) \
              .ToList()

        # Get executing framework version to discard newer assemblies
        maxversion = typeof(System.Type).Assembly.GetName().Version
        for dir in dirs:
            dir = Path.Combine(dir, 'lib')
            continue unless Directory.Exists(dir)

            # Load any generic assemblies in the lib folder
            LoadDlls(dir)

            # Assume we always target the full framework (netXX)
            path = Directory.GetDirectories(dir, 'net*') \
                 .OrderBy({ f | f[-2:] }) \
                 .Where({ f |
                    maxversion >= System.Version(
                        join(f[-2:].ToCharArray(), '.')
                    )
                 }) \
                 .LastOrDefault()

            LoadDlls(path) if path

    def Run() as int:

        # TODO: Instead of always creating a temp directory to copy
        #       all the references, it should be possible to inject code
        #       in the generated assembly that registers to the AppDomain
        #       assembly load event and loads them based on the configuration
        #       used when compiling. This will allow to load NuGet references
        #       on demand too and should work when using nunit.

        _params.OutputType = CompilerOutputType.Auto
        _params.Debug = _cmdline.Debug
        _params.Ducky = _cmdline.Ducky
        _params.Strict = _cmdline.Strict
        _params.WhiteSpaceAgnostic = _cmdline.Wsa
        _params.Cache = _cmdline.Cache

        if _cmdline.Verbose:
            _params.TraceLevel = TraceLevel.Verbose
            Trace.Listeners.Add(TextWriterTraceListener(Console.Error))

        if _params.Debug or _cmdline.Runner or _cmdline.Output:
            # Save to disk to ensure we get symbols loaded on runtime errors
            _params.GenerateInMemory = false
            _params.Pipeline = CompileToFile()

            if not _cmdline.Output:
                # Find a temporary directory name for the output
                while true:
                    path = Path.Combine(Path.GetTempPath(), 'booi-' + Path.GetRandomFileName())
                    path = path.Replace('.', '-')
                    break unless Directory.Exists(path)
                Trace.TraceInformation("Creating temporary directory '{0}'", path)
                Directory.CreateDirectory(path)
                _params.OutputAssembly = Path.Combine(path, 'booi-main.exe')
            else:
                if Directory.Exists(_cmdline.Output):
                    path = _cmdline.Output
                    _params.OutputAssembly = Path.Combine(path, 'booi-main.exe')
                else:
                    path = Path.GetDirectoryName(_cmdline.Output)
                    _params.OutputAssembly = _cmdline.Output

        else:
            _params.GenerateInMemory = true
            _params.Pipeline = CompileToMemory()

        for d in _cmdline.Defines:
            _params.Defines.Add(d.Key, d.Value)

        for libpath in _cmdline.LibPaths:
            try:
                _params.LibPaths.AddUnique(Path.GetFullPath(libpath))
            except ex as System.ArgumentException:
                Trace.TraceError("LibPath '{0}' not found", libpath)

        sw = System.Diagnostics.Stopwatch()
        sw.Start()

        for reference in _cmdline.References:
            try:
                asmref = _params.LoadAssembly(reference)
                if asmref is null:
                    printerr string.Format(Boo.Lang.Resources.StringResources.BooC_UnableToLoadAssembly, reference)
                    return DefaultErrorCode

                _params.References.Add(asmref)
                _assemblyResolver.AddAssembly(asmref.Assembly)
            except ex:
                printerr "Error loading reference '{0}': {1}", reference, ex
                return DefaultErrorCode

        # Load packages
        for package in _cmdline.Packages:
            LoadPackages(package) if Directory.Exists(package)

        sw.Stop()
        Trace.TraceInformation("Loading references took {0}ms", sw.ElapsedMilliseconds)

        for file in _cmdline.Files:
            continue if file == '-'
            if Directory.Exists(file):
                for f in Directory.GetFiles(file, '*.boo'):
                    _params.Input.Add(FileInput(f))
            else:
                _params.Input.Add(FileInput(file))

        if not len(_cmdline.Files) or _cmdline.Files.Contains('-'):
            _params.Input.Add(StringInput('<stdin>', consume(Console.In)))

        # Set the MAIN define if we are compiling a single script
        if 1 == len(_params.Input) and not _params.Defines.ContainsKey('MAIN'):
            _params.Defines.Add('MAIN', '')

        sw.Restart()
        generated = Compile()
        return DefaultErrorCode if generated is null
        sw.Stop()
        Trace.TraceInformation('Compilation took {0}ms', sw.ElapsedMilliseconds)

        if _cmdline.Runner or _cmdline.Output:
            # Collect all dependencies in a temporary dir
            for asmref in _params.References:
                asm = asmref.Assembly
                # Skip dynamically generated assemblies
                continue if asm.IsDynamic

                try:
                    asmfile = Path.GetFileName(asm.Location)
                    if asmfile in ('System.dll', 'System.Core.dll', 'mscorlib.dll'):
                        continue

                    # HACK: The assembly containing the parser is imported dynamically by
                    #       the compiler, looking for it to be placed next to it.
                    #       Here we ensure that when the compiler is referenced the parser
                    #       is also copied next to it.
                    if asmfile == 'Boo.Lang.Compiler.dll':
                        File.Copy(
                            asm.Location.Replace('Boo.Lang.Compiler.dll', 'Boo.Lang.Parser.dll'),
                            Path.Combine(path, 'Boo.Lang.Parser.dll')
                        )

                    File.Copy(asm.Location, Path.Combine(path, asmfile))
                except ex:
                    Trace.TraceError("Error copying assembly '{0}' to '{1}'", asmref.Name, path)

            DumpSolutionFile()

        if _cmdline.Runner:
            # Replace place holders in script arguments
            asmpath = generated.Location
            args = List of string()
            if not len(_args):
                args.Add('"' + asmpath + '"')
            else:
                for arg in _args:
                    arg = arg.Replace('%filename%', Path.GetFileName(asmpath))
                    arg = arg.Replace('%pathname%', Path.GetDirectoryName(asmpath))
                    arg = arg.Replace('%assembly%', asmpath)
                    arg = arg.Replace('%solution%', asmpath[:-3] + 'sln')
                    if arg == '%':
                        arg = asmpath
                    args.Add('"' + arg + '"')

            # Try to execute the runner
            Trace.TraceInformation('Running command: {0} {1}', _cmdline.Runner, join(args, ' '))
            retval = runcommand(_cmdline.Runner, join(args, ' '))
        else:
            retval = Execute(generated, _args)

        # Clean up generated files
        if not _cmdline.Output:
            Trace.TraceInformation("Removing temporary directory '{0}'", path)
            Directory.Delete(path, true)

        return retval

    def Compile():
        context = CompilerContext(_params)
        _params.Pipeline.Run(context)

        if _cmdline.Warnings and len(context.Warnings):
            printerr(context.Warnings.ToString())

        if len(context.Errors):
            printerr(context.Errors.ToString(_params.TraceVerbose))
            return null

        if not context.GeneratedAssembly:
            return Assembly.LoadFrom(context.GeneratedAssemblyFileName)

        return context.GeneratedAssembly

    def Execute(asm as Assembly, argv as (string)):
        exitCode = DefaultSuccessCode
        try:
            _assemblyResolver.AddAssembly(asm)

            main = asm.EntryPoint
            if not main:
                printerr 'Error: Entry point not found'
                return DefaultErrorCode

            if len(main.GetParameters()) > 0:
                returnValue = main.Invoke(null, (argv,))
            else:
                returnValue = main.Invoke(null, null)

            exitCode = returnValue if returnValue is not null
        except x as TargetInvocationException:
            printerr x.InnerException
            exitCode = DefaultErrorCode
        ensure:
            Environment.ExitCode = exitCode

        return exitCode

    protected def DumpSolutionFile():
    """ Create a MonoDevelop solution file to ease debugging. MonoDevelop allows defining
        managed executables as projects.
    """
        asmpath = Path.GetDirectoryName(_params.OutputAssembly)
        asmfile = Path.GetFileName(_params.OutputAssembly)
        asmname = asmfile[:-4]

        # Get assemblies automatically compiled from sources
        assemblies = [asmfile]
        for asmref as IAssemblyReference in _params.References:
            asm = asmref.Assembly
            if asm.IsDynamic:
                assemblies.Add(asmref.Name + '.dll')
                continue

            attrs = asm.GetCustomAttributes(CompilationTimeAttribute, false)
            if len(attrs):
                assemblies.Add(asmref.Name + '.dll')

        fpath = Path.Combine(asmpath, asmname + '.sln')
        using fs = StreamWriter(fpath):
            fs.WriteLine()
            fs.WriteLine('Microsoft Visual Studio Solution File, Format Version 11.00')
            fs.WriteLine('# Visual Studio 2010')
            fs.WriteLine('Global')
            fs.WriteLine('\tGlobalSection(MonoDevelopProperties) = preSolution')
            fs.WriteLine('\t\tStartupItem = {0}', asmname)
            fs.WriteLine('\tEndGlobalSection')
            fs.WriteLine('EndGlobal')

            for asmfile in assemblies:
                # Generate a deterministic Guid based on the file name
                using md5 = System.Security.Cryptography.MD5.Create():
                    bytes = System.Text.Encoding.Default.GetBytes(asmfile)
                    guid = Guid(md5.ComputeHash(bytes))

                fs.WriteLine('Project("{{8BC9CEB9-8B4A-11D0-8D11-00A0C91BC942}}") = "{0}", "{1}", "{{{2}}}"', asmfile[:-4], asmfile, guid)
                fs.WriteLine('\tProjectSection(MonoDevelopProperties) = preProject')
                fs.WriteLine('\t\tConfigurations = $0')
                fs.WriteLine('\t\t$0.Configuration = $1')
                fs.WriteLine('\t\t$1.name = Debug')
                fs.WriteLine('\t\t$1.OutputPath = ./')
                fs.WriteLine('\t\t$1.ctype = ProjectConfiguration')
                fs.WriteLine('\tEndProjectSection')
                fs.WriteLine('EndProject')

            # Include an NUnit assembly test collection project
            fs.WriteLine('Project("{9344bdbb-3e7f-41fc-a0dd-8665d75ee146}") = "nunit", "nunit.mdproj", "{4B9B3417-6A64-4D19-912A-9A7C50828CEA}"')
            fs.WriteLine('EndProject')

        # Generate an mdproj file for the NUnit assembly test collection
        fpath = Path.Combine(asmpath, 'nunit.mdproj')
        using fs = StreamWriter(fpath):
            fs.WriteLine('<?xml version="1.0" encoding="utf-8"?>')
            fs.WriteLine('<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">')
            fs.WriteLine('<PropertyGroup>')
            fs.WriteLine(' <Configuration Condition=" \'$(Configuration)\' == \'\' ">Default</Configuration>')
            fs.WriteLine(' <Platform Condition=" \'$(Platform)\' == \'\' ">AnyCPU</Platform>')
            fs.WriteLine(' <ItemType>NUnitAssemblyGroupProject</ItemType>')
            fs.WriteLine(' <ProductVersion>10.0.0</ProductVersion>')
            fs.WriteLine(' <SchemaVersion>2.0</SchemaVersion>')
            fs.WriteLine(' <ProjectGuid>{{4B9B3417-6A64-4D19-912A-9A7C50828CEA}}</ProjectGuid>')
            fs.WriteLine('</PropertyGroup>')
            fs.WriteLine('<PropertyGroup Condition=" \'$(Configuration)|$(Platform)\' == \'Default|AnyCPU\' ">')
            fs.WriteLine(' <Assemblies>')
            fs.WriteLine('  <Assemblies>')
            for asmfile in assemblies:
                fs.WriteLine('   <Assembly Path="{0}" />', Path.GetFullPath(Path.Combine(asmpath, asmfile)))
            fs.WriteLine('  </Assemblies>')
            fs.WriteLine(' </Assemblies>')
            fs.WriteLine('</PropertyGroup>')
            fs.WriteLine('</Project>')

def runcommand(filename, arguments):
    p = System.Diagnostics.Process()
    p.StartInfo.Arguments = arguments
    p.StartInfo.CreateNoWindow = true
    p.StartInfo.UseShellExecute = true
    p.StartInfo.FileName = filename
    p.Start()
    p.WaitForExit()
    return p.ExitCode

def consume(reader as TextReader):
    return join(line for line in reader, "\n")

def printerr(*args):
    for arg in args:
        Console.Error.Write(arg)
        Console.Error.Write(' ')
    Console.Error.WriteLine()

[STAThread]
def Main(argv as (string)) as int:
    # Find the number of arguments that apply to booi
    argc = System.Array.IndexOf(argv, '--')
    if argc >= 0:
        argv = argv[:argc] + argv[argc+1:]
    else:
        argc = 0
        for arg in argv:
            argc++
            break unless arg.StartsWith('-')

    try:
        cmdline = CommandLine(argv[:argc])
    except ex as CommandLineException:
        printerr 'Error:', ex.Message
        return Program.DefaultErrorCode

    if cmdline.DoVersion:
        print Program.Version
        return Program.DefaultSuccessCode

    if not cmdline.IsValid or cmdline.DoHelp:
        print "Usage: booi [options] <script|-> [-- [script options]]"
        print "Options:"
        cmdline.PrintOptions(Console.Out)
        return Program.DefaultErrorCode

    return Program(cmdline, argv[argc:]).Run()


preserve source location for better error messages

namespace Boo.Adt

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

macro data:
"""
Produces a complete data type hierarchy from a declaration.

All the types produced are immutable (unless instructed otherwise) and have
meaningful implementations for Equals, GetHashCode and ToString.

Usage:

	data Expression = Const(Value as int) | Add(Left as Expression, Right as Expression)

	data SingleValue(Value as int)

	data TypeWithMutableField(@MutableValue as int) // @ makes the field mutable
"""

	DataMacroExpansion(data)

class DataMacroExpansion:

	_module as TypeDefinition
	_baseType as TypeDefinition

	def constructor(node as MacroStatement):

		assert 1 == len(node.Arguments)
		_module = enclosingModule(node)

		match node.Arguments[0]:
			case [| $left = $right |]:
				_baseType = createBaseType(left)
				expandDataConstructors(right)

			case [| $(ctor=MethodInvocationExpression()) < $baseType |]:
				_baseType = [|
					class $baseType:
						pass
				|]
				expandDataConstructor(ctor)

			case ctor=MethodInvocationExpression():
				_baseType = [|
					class object:
						pass
				|]
				expandDataConstructor(ctor)

	def createBaseType(node as Expression):
		type = baseTypeForExpression(node)
		type.LexicalInfo = node.LexicalInfo
		registerType(type)
		return type

	def abstractType(name as string):
		type = [|
			partial abstract class $name:
				pass
		|]
		return type

	def baseTypeForExpression(node as Expression):
		match node:
			case ReferenceExpression(Name: name):
				return abstractType(name)

			case [| $(ReferenceExpression(Name: name)) < $(ReferenceExpression(Name: baseType)) |]:
				type = [|
					partial abstract class $name($baseType):
						pass
				|]
				return type

			case mie=MethodInvocationExpression(Target: ReferenceExpression(Name: name)):
				type = abstractType(name)
				fields = fieldsFrom(mie)
				type.Members.Extend(fields)
				type.Members.Add(constructorForFields(fields))
				return type

			case gre=SlicingExpression(
						Target: ReferenceExpression(Name: name)):
				type = [|
					partial abstract class $name[of T]:
						pass
				|]
				type.GenericParameters.Clear()
				for index in gre.Indices:
					match index:
						case Slice(
							Begin: ReferenceExpression(Name: name),
							End: null,
							Step: null):
							type.GenericParameters.Add(
								GenericParameterDeclaration(Name: name))
				return type

	def expandDataConstructors(node as Expression):
		match node:
			case [| $left | $right |]:
				expandDataConstructors(left)
				expandDataConstructors(right)
			case MethodInvocationExpression():
				expandDataConstructor(node)

	def expandDataConstructor(node as MethodInvocationExpression):
		type = dataConstructorTypeForExpression(node.Target)
		type.LexicalInfo = node.LexicalInfo
		type.Members.Extend(fields = fieldsFrom(node))
		type.Members.Add(toStringForType(type))
		type.Members.Add(equalsForType(type))

		ctor = constructorForFields(fields)
		if len(_baseType.Members):
			superInvocation = [| super() |]
			i = 0
			for field as Field in fieldsOf(_baseType):
				ctor.Parameters.Insert(i++,
					ParameterDeclaration(Name: field.Name, Type: field.Type))
				superInvocation.Arguments.Add(ReferenceExpression(field.Name))
			ctor.Body.Insert(0, superInvocation)
		type.Members.Add(ctor)

		registerType(type)

	def dataConstructorTypeForExpression(node as Expression):
		match node:
			case ReferenceExpression(Name: name):
				type = [|
					partial class $name($_baseType):
						pass
				|]
				for arg in _baseType.GenericParameters:
					type.GenericParameters.Add(
								GenericParameterDeclaration(Name: arg.Name))
				return type

	def equalsForType(type as TypeDefinition):

		allFields = List(fieldsIncludingBaseType(type))
		if len(allFields) == 0:
			method = [|
				override def Equals(o):
					if o is null: return false
					return self.GetType() is o.GetType()
			|]
			return method

		method = [|
			override def Equals(o):
				if o is null: return false
				if self.GetType() is not o.GetType(): return false
				other as $type = o
		|]

		for field as Field in allFields:
			comparison = [|
				if self.$(field.Name) != other.$(field.Name):
					return false
			|]
			method.Body.Add(comparison)

		method.Body.Add([| return true |])
		return method

	def toStringForType(type as TypeDefinition):
		expression = ExpressionInterpolationExpression()
		items = expression.Expressions
		items.Add([| $("${type.Name}(") |])

		comma = false
		for field in fieldsIncludingBaseType(type):
			if comma: items.Add([| ", " |])
			items.Extend(toStringForField(field))

			comma = true

		items.Add([| $(")") |])
		return [|
			override def ToString():
				return $expression
		|]

	def toStringForField(field as Field):
		match field.Type:
			case ArrayTypeReference():
				yield [| "[" |]
				yield [| join(self.$(field.Name), ', ') |]
				yield [| "]" |]
			otherwise:
				yield [| self.$(field.Name) |]

	def fieldsIncludingBaseType(type as TypeDefinition):
		return cat(fieldsOf(_baseType), fieldsOf(type))

	def fieldsOf(type as TypeDefinition):
		return type.Members.Select(NodeType.Field)

	def constructorForFields(fields as (Field)):
		ctor = [|
			def constructor():
				pass
		|]
		for f in fields:
			ctor.Parameters.Add(p=ParameterDeclaration(Name: f.Name, Type: f.Type))
			ctor.Body.Add([| self.$(f.Name) = $p |])
		ctor.Parameters.HasParamArray = len(fields) > 0 and fields[-1].ContainsAnnotation("*")
		return ctor

	def fieldsFrom(node as MethodInvocationExpression):
		return array(fieldFrom(expression) for expression in node.Arguments)

	def fieldFrom(node as Expression):
		match node:
			case [| *$(ReferenceExpression(Name: name)) as $(arrayType = ArrayTypeReference()) |]:
				return arrayField(name, arrayType)
			case [| *$(ReferenceExpression(Name: name)) |]:
				return arrayField(name, ArrayTypeReference(baseTypeRef()))
			case [| $(ReferenceExpression(Name: name)) as $type |]:
				return fieldWith(name, type)
			case ReferenceExpression(Name: name):
				return fieldWith(name, baseTypeRef())

	def arrayField(name as string, arrayType as ArrayTypeReference):
		field = fieldWith(name, arrayType)
		field.Annotate("*")
		return field

	def baseTypeRef():
		return TypeReference.Lift(_baseType)

	def fieldWith(name as string, type as TypeReference):
		if name.StartsWith("@"): // mutable field
			return [|
				public $(fieldName(name)) as $type
			|]
		return [|
			public final $name as $type
		|]

	def fieldName(name as string):
		return (name[1:] if name.StartsWith("@") else name)

	def registerType(type as TypeDefinition):
		_module.Members.Add(type)

def enclosingModule(node as Node) as Module:
	return node.GetAncestor(NodeType.Module)

namespace Boo.Adt

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

macro data:
"""
Produces a complete data type hierarchy from a declaration.

All the types produced are immutable (unless instructed otherwise) and have
meaningful implementations for Equals, GetHashCode and ToString.

Usage:

	data Expression = Const(Value as int) | Add(Left as Expression, Right as Expression)

	data SingleValue(Value as int)

	data TypeWithMutableField(@MutableValue as int) // @ makes the field mutable
"""

	DataMacroExpansion(data)

class DataMacroExpansion:

	_module as TypeDefinition
	_baseType as TypeDefinition

	def constructor(node as MacroStatement):

		assert 1 == len(node.Arguments)
		_module = enclosingModule(node)

		match node.Arguments[0]:
			case [| $left = $right |]:
				_baseType = createBaseType(left)
				expandDataConstructors(right)

			case [| $(ctor=MethodInvocationExpression()) < $baseType |]:
				_baseType = [|
					class $baseType:
						pass
				|]
				expandDataConstructor(ctor)

			case ctor=MethodInvocationExpression():
				_baseType = [|
					class object:
						pass
				|]
				expandDataConstructor(ctor)

	def createBaseType(node as Expression):
		type = baseTypeForExpression(node)
		registerType(type)
		return type

	def abstractType(name as string):
		type = [|
			partial abstract class $name:
				pass
		|]
		return type

	def baseTypeForExpression(node as Expression):
		match node:
			case ReferenceExpression(Name: name):
				return abstractType(name)

			case [| $(ReferenceExpression(Name: name)) < $(ReferenceExpression(Name: baseType)) |]:
				type = [|
					partial abstract class $name($baseType):
						pass
				|]
				return type

			case mie=MethodInvocationExpression(Target: ReferenceExpression(Name: name)):
				type = abstractType(name)
				fields = fieldsFrom(mie)
				type.Members.Extend(fields)
				type.Members.Add(constructorForFields(fields))
				return type

			case gre=SlicingExpression(
						Target: ReferenceExpression(Name: name)):
				type = [|
					partial abstract class $name[of T]:
						pass
				|]
				type.GenericParameters.Clear()
				for index in gre.Indices:
					match index:
						case Slice(
							Begin: ReferenceExpression(Name: name),
							End: null,
							Step: null):
							type.GenericParameters.Add(
								GenericParameterDeclaration(Name: name))
				return type

	def expandDataConstructors(node as Expression):
		match node:
			case [| $left | $right |]:
				expandDataConstructors(left)
				expandDataConstructors(right)
			case MethodInvocationExpression():
				expandDataConstructor(node)

	def expandDataConstructor(node as MethodInvocationExpression):
		type = dataConstructorTypeForExpression(node.Target)
		type.LexicalInfo = node.LexicalInfo
		type.Members.Extend(fields = fieldsFrom(node))
		type.Members.Add(toStringForType(type))
		type.Members.Add(equalsForType(type))

		ctor = constructorForFields(fields)
		if len(_baseType.Members):
			superInvocation = [| super() |]
			i = 0
			for field as Field in fieldsOf(_baseType):
				ctor.Parameters.Insert(i++,
					ParameterDeclaration(Name: field.Name, Type: field.Type))
				superInvocation.Arguments.Add(ReferenceExpression(field.Name))
			ctor.Body.Insert(0, superInvocation)
		type.Members.Add(ctor)

		registerType(type)

	def dataConstructorTypeForExpression(node as Expression):
		match node:
			case ReferenceExpression(Name: name):
				type = [|
					partial class $name($_baseType):
						pass
				|]
				for arg in _baseType.GenericParameters:
					type.GenericParameters.Add(
								GenericParameterDeclaration(Name: arg.Name))
				return type

	def equalsForType(type as TypeDefinition):

		allFields = List(fieldsIncludingBaseType(type))
		if len(allFields) == 0:
			method = [|
				override def Equals(o):
					if o is null: return false
					return self.GetType() is o.GetType()
			|]
			return method

		method = [|
			override def Equals(o):
				if o is null: return false
				if self.GetType() is not o.GetType(): return false
				other as $type = o
		|]

		for field as Field in allFields:
			comparison = [|
				if self.$(field.Name) != other.$(field.Name):
					return false
			|]
			method.Body.Add(comparison)

		method.Body.Add([| return true |])
		return method

	def toStringForType(type as TypeDefinition):
		expression = ExpressionInterpolationExpression()
		items = expression.Expressions
		items.Add([| $("${type.Name}(") |])

		comma = false
		for field in fieldsIncludingBaseType(type):
			if comma: items.Add([| ", " |])
			items.Extend(toStringForField(field))

			comma = true

		items.Add([| $(")") |])
		return [|
			override def ToString():
				return $expression
		|]

	def toStringForField(field as Field):
		match field.Type:
			case ArrayTypeReference():
				yield [| "[" |]
				yield [| join(self.$(field.Name), ', ') |]
				yield [| "]" |]
			otherwise:
				yield [| self.$(field.Name) |]

	def fieldsIncludingBaseType(type as TypeDefinition):
		return cat(fieldsOf(_baseType), fieldsOf(type))

	def fieldsOf(type as TypeDefinition):
		return type.Members.Select(NodeType.Field)

	def constructorForFields(fields as (Field)):
		ctor = [|
			def constructor():
				pass
		|]
		for f in fields:
			ctor.Parameters.Add(p=ParameterDeclaration(Name: f.Name, Type: f.Type))
			ctor.Body.Add([| self.$(f.Name) = $p |])
		ctor.Parameters.HasParamArray = len(fields) > 0 and fields[-1].ContainsAnnotation("*")
		return ctor

	def fieldsFrom(node as MethodInvocationExpression):
		return array(fieldFrom(expression) for expression in node.Arguments)

	def fieldFrom(node as Expression):
		match node:
			case [| *$(ReferenceExpression(Name: name)) as $(arrayType = ArrayTypeReference()) |]:
				return arrayField(name, arrayType)
			case [| *$(ReferenceExpression(Name: name)) |]:
				return arrayField(name, ArrayTypeReference(baseTypeRef()))
			case [| $(ReferenceExpression(Name: name)) as $type |]:
				return fieldWith(name, type)
			case ReferenceExpression(Name: name):
				return fieldWith(name, baseTypeRef())

	def arrayField(name as string, arrayType as ArrayTypeReference):
		field = fieldWith(name, arrayType)
		field.Annotate("*")
		return field

	def baseTypeRef():
		return TypeReference.Lift(_baseType)

	def fieldWith(name as string, type as TypeReference):
		if name.StartsWith("@"): // mutable field
			return [|
				public $(fieldName(name)) as $type
			|]
		return [|
			public final $name as $type
		|]

	def fieldName(name as string):
		return (name[1:] if name.StartsWith("@") else name)

	def registerType(type as TypeDefinition):
		_module.Members.Add(type)

def enclosingModule(node as Node) as Module:
	return node.GetAncestor(NodeType.Module)


Minor improvement by not needing a specific terminating step

namespace BooJs.Compiler.Steps

import System
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.Steps
import Boo.Lang.Compiler.TypeSystem.Internal

class ProcessGenerators(AbstractTransformerCompilerStep):
"""
    Specialized step to process generators replacing Boo's one.

    TODO: State to run just before this one that converts all loops to while ones
          if the method is a generator
"""
    class TransformGenerator(FastDepthFirstVisitor):
        [getter(States)]
        _states = StatementCollection()

        [property(State)]
        _state as int = 0

        Current as Block:
            get: return States[State]

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

        def constructor():
            CreateState()

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

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

        def OnYieldStatement(node as YieldStatement):
            # Create a re-entry state
            nextstate = CreateState()

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

            # Continue in the re-entry state
            State = nextstate

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

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

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

            # Process the loop body
            Visit node.Block

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

            # Continue in the exit step previously created
            State = exitstate

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

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

            Visit node.ProtectedBlock

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

            # Create a new state for the exception handler
            exceptstate = CreateState()

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

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

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

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

            # Continue in the after state
            State = afterstate

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

    def HasTryStatement(node as Block) as bool:
        for st in node.Statements:
            return true if st.NodeType == NodeType.TryStatement
            if wst = st as WhileStatement:
                return true if HasTryStatement(wst.Block)
            elif ist = st as IfStatement:
                return true if HasTryStatement(ist.TrueBlock) or HasTryStatement(ist.FalseBlock)
            elif bst = st as Block:
                return true if HasTryStatement(bst)

        return false

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

    override def LeaveMethod(node as Method):

        has_try = HasTryStatement(node.Body)

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

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

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

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

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

        statemachine as Statement
        statemachine = [|
            if __error:
                raise __error

            $switch

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

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

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

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

                    # Re-raise the exception
                    raise __e
            |]

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

namespace BooJs.Compiler.Steps

import System
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.Steps
import Boo.Lang.Compiler.TypeSystem.Internal

class ProcessGenerators(AbstractTransformerCompilerStep):
"""
    Specialized step to process generators to replace Boo's original one.

    TODO: State to run just before this one that converts all loops to while ones
          if the method is a generator
"""
    class TransformGenerator(FastDepthFirstVisitor):
        [getter(States)]
        _states = StatementCollection()

        [property(State)]
        _state as int = 0

        Current as Block:
            get: return States[State]

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

        def constructor():
            CreateState()

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

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

        def OnYieldStatement(node as YieldStatement):
            # Create a re-entry state
            nextstate = CreateState()

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

            # Continue in the re-entry state
            State = nextstate

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

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

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

            # Process the loop body
            Visit node.Block

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

            # Continue in the exit step previously created
            State = exitstate

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

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

            Visit node.ProtectedBlock

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

            # Create a new state for the exception handler
            exceptstate = CreateState()

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

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

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

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

            # Continue in the after state
            State = afterstate

        def Terminate():
            # Create an exit point
            exitstate = CreateState()

            # Direct the current state to the exit point
            Current.Add([| __state = $(exitstate) |])
            Current.Add(_gotostatemachine)

            # Define the exit strategy
            State = exitstate
            Current.Add( [| raise Boo.STOP |] )

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

    def HasTryStatement(node as Block) as bool:
        for st in node.Statements:
            return true if st.NodeType == NodeType.TryStatement
            if wst = st as WhileStatement:
                return true if HasTryStatement(wst.Block)
            elif ist = st as IfStatement:
                return true if HasTryStatement(ist.TrueBlock) or HasTryStatement(ist.FalseBlock)
            elif bst = st as Block:
                return true if HasTryStatement(bst)

        return false

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

    override def LeaveMethod(node as Method):

        has_try = HasTryStatement(node.Body)

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

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

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

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

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

        statemachine as Statement
        statemachine = [|
            if __error:
                raise __error

            $switch
        |]

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

                    # Position the generator to its last state
                    __state = $(len(transformer.States)-1)

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

                    # Re-raise the exception
                    raise __e
            |]

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


don't depend on System.Collections.CollectionBase for it has been removed from newer .net profiles

namespace UnityScript.Lang

import System
import System.Collections

class Array(IList, Boo.Lang.Runtime.ICoercible):

	InnerList = System.Collections.Generic.List of object()

	def constructor():
		pass

	def constructor([required(capacity >= 0)] capacity as int):
		EnsureCapacity(capacity)

	def constructor(collection as IEnumerable):
		if collection isa String:
			Add(collection)
		else:
			AddRange(collection)

	def constructor(*items):
		if len(items) == 1 and items[0] isa IEnumerable:
			AddRange(items[0])
		else:
			AddRange(items)

	def GetEnumerator():
		return InnerList.GetEnumerator()

	def CopyTo(target as System.Array, targetIndex as int):
		elementType = target.GetType().GetElementType()
		elementsToCopy = Math.Min(len(target) - targetIndex, Count)
		sourceIndex = 0
		while sourceIndex < elementsToCopy:
			rawArrayIndexing:
				target[targetIndex++] = Boo.Lang.Runtime.RuntimeServices.Coerce(InnerList[sourceIndex++], elementType)

	Count:
		get: return InnerList.Count

	ICollection.IsSynchronized:
		get: return false

	ICollection.SyncRoot:
		get: return self

	def Clear():
		InnerList.Clear()

	def Contains(o):
		return InnerList.Contains(o)

	def IndexOf(o):
		return InnerList.IndexOf(o)

	def Insert(index as int, element):
		InnerList.Insert(index, element)

	def RemoveAt(index as int):
		InnerList.RemoveAt(index)

	IList.IsReadOnly:
		get: return false

	IList.IsFixedSize:
		get: return false

	static def op_Implicit(e as IEnumerable) as Array:
		if e is null: return null
		return Array(e)

	static def op_Implicit(a as System.Array) as Array:
		if a is null: return null
		return Array((a as IEnumerable))

	length:
		get:
			return self.Count
		set:
			EnsureCapacity(value)

			if value < self.Count:
				self.InnerList.RemoveRange(value, InnerList.Count - value)

	def clear():
		Clear()

	def Coerce(toType as System.Type):
		if toType.IsArray: return ToBuiltin(toType.GetElementType())
		return self

	def ToBuiltin(type as System.Type):
		result = System.Array.CreateInstance(type, Count)
		CopyTo(result, 0)
		return result

	def Add(value as object, *items):
		self.AddImpl(value, items)

	def Add(value as object):
		self.InnerList.Add(value)

	def Push(value as object, *items):
		return self.AddImpl(value, items)

	def push(value as object, *items):
		return self.AddImpl(value, items)

	// workaround for mono reflection bug
	def push(value as object):
		self.InnerList.Add(value)
		return self.InnerList.Count

	def Push(value as object):
		return self.push(value)

	def Unshift(value as object, *items):
		return self.UnshiftImpl(value, items)

	def unshift(value as object, *items):
		return self.UnshiftImpl(value, items)

	def Splice (index as int, howmany as int, *items):
		SpliceImpl (index, howmany, items)

	def splice (index as int, howmany as int, *items):
		SpliceImpl (index, howmany, items)

	# How do i allow the vararg only for ICollections?
	def Concat(value as ICollection, *items):
		return ConcatImpl(value, items)

	# How do i allow the vararg only for ICollections?
	def concat(value as ICollection, *items):
		return ConcatImpl(value, items)

	# Pop last and return
	def Pop():
		last = self.InnerList.Count - 1
		popped = self.InnerList[last]
		self.InnerList.RemoveAt(last)
		return popped

	def pop():
		return self.Pop()

	# Remove first element
	def Shift():
		first = 0
		popped = self.InnerList[first]
		self.InnerList.RemoveAt(0)
		return popped

	def shift():
		return self.Shift()

	# Convert to string
	def ToString ():
		return self.Join(",")

	def toString ():
		return ToString()

	# Returns array with elements removed
	def Slice(start as int, end as int):
		normalStart = NormalizeIndex(start)
		normalEnd = NormalizeIndex(end)
		return Array(InnerList.GetRange(normalStart, normalEnd - normalStart))

	def Slice(start as int):
		return self.Slice(start, self.InnerList.Count)

	def slice(start as int, end as int):
		return self.Slice(start, end)

	def slice(start as int):
		return self.Slice(start)

	# Revert
	def Reverse() as Array:
		self.InnerList.Reverse()
		return self

	def reverse() as Array:
		self.Reverse()
		return self

	callable Comparison(lhs as object, rhs as object) as int

	def Sort(comparison as Comparison):
		self.InnerList.Sort({ x, y | comparison(x, y) })

	def sort(comparison as Comparison):
		Sort(comparison)

	# Sort
	def Sort() as Array:
		self.InnerList.Sort()
		return self

	def sort() as Array:
		return self.Sort()

	# Combine to a string
	def Join (seperator as String):
		return Builtins.join(self.InnerList, seperator)

	def join (seperator as String):
		return self.Join(seperator)

	# Remove
	def Remove (obj as Object):
		self.InnerList.Remove(obj)

	def remove (obj as Object):
		self.Remove(obj)

	self[index as int] as object:
		get:
			return self.InnerList[index]
		set:
			EnsureCapacity(index+1)
			self.InnerList[index] = value

	def AddRange([required] collection as IEnumerable):
		for item in collection:
			self.InnerList.Add(item)

	private def AddImpl(value as object, [required] items as IEnumerable):
		self.InnerList.Add(value)
		for item in items:
			self.InnerList.Add(item)
		return self.InnerList.Count

	private def UnshiftImpl(value as object, [required] items as IEnumerable):
		self.InnerList.InsertRange(0, items)
		self.InnerList.Insert(0, value)
		return self.InnerList.Count

	private def SpliceImpl (index as int, howmany as int, [required] items as IEnumerable):
		if howmany != 0:
			self.InnerList.RemoveRange(index, howmany)
		self.InnerList.InsertRange(index, items)

	private def ConcatImpl(value as ICollection, [required] items as IEnumerable):
		arr = Array (self.InnerList)
		arr.AddRange(value)
		for item as ICollection in items:
			arr.AddRange(item)
		return arr

	private def EnsureCapacity(capacity as int):
		return if capacity < Count
		for i in range(capacity-self.Count):
			InnerList.Add(null)

	private def NormalizeIndex(index as int):
		return index if index >= 0
		return index + InnerList.Count

namespace UnityScript.Lang

import System
import System.Collections

class Array(CollectionBase, Boo.Lang.Runtime.ICoercible):

	def constructor():
		pass

	def constructor([required(capacity >= 0)] capacity as int):
		EnsureCapacity(capacity)

	def constructor(collection as IEnumerable):
		if collection isa String:
			Add(collection)
		else:
			AddRange(collection)

	def constructor(*items):
		if len(items) == 1 and items[0] isa IEnumerable:
			AddRange(items[0])
		else:
			AddRange(items)

	static def op_Implicit(e as IEnumerable) as Array:
		if e is null: return null
		return Array(e)

	static def op_Implicit(a as System.Array) as Array:
		if a is null: return null
		return Array((a as IEnumerable))

	length:
		get:
			return self.Count
		set:
			EnsureCapacity(value)

			if value < self.Count:
				self.InnerList.RemoveRange(value, InnerList.Count - value)

	def clear():
		Clear()

	def Coerce(toType as System.Type):
		if toType.IsArray: return ToBuiltin(toType.GetElementType())
		return self

	def ToBuiltin(type as System.Type):
		return self.InnerList.ToArray(type)

	def Add(value as object, *items):
		self.AddImpl(value, items)

	def Add(value as object):
		self.InnerList.Add(value)

	def Push(value as object, *items):
		return self.AddImpl(value, items)

	def push(value as object, *items):
		return self.AddImpl(value, items)

	// workaround for mono reflection bug
	def push(value as object):
		self.InnerList.Add(value)
		return self.InnerList.Count

	def Push(value as object):
		return self.push(value)

	def Unshift(value as object, *items):
		return self.UnshiftImpl(value, items)

	def unshift(value as object, *items):
		return self.UnshiftImpl(value, items)

	def Splice (index as int, howmany as int, *items):
		SpliceImpl (index, howmany, items)

	def splice (index as int, howmany as int, *items):
		SpliceImpl (index, howmany, items)

	# How do i allow the vararg only for ICollections?
	def Concat(value as ICollection, *items):
		return ConcatImpl(value, items)

	# How do i allow the vararg only for ICollections?
	def concat(value as ICollection, *items):
		return ConcatImpl(value, items)

	# Pop last and return
	def Pop():
		last = self.InnerList.Count - 1
		popped = self.InnerList[last]
		self.InnerList.RemoveAt(last)
		return popped

	def pop():
		return self.Pop()

	# Remove first element
	def Shift():
		first = 0
		popped = self.InnerList[first]
		self.InnerList.RemoveAt(0)
		return popped

	def shift():
		return self.Shift()

	# Convert to string
	def ToString ():
		return self.Join(",")

	def toString ():
		return ToString()

	# Returns array with elements removed
	def Slice(start as int, end as int):
		normalStart = NormalizeIndex(start)
		normalEnd = NormalizeIndex(end)
		return Array(InnerList.GetRange(normalStart, normalEnd - normalStart))

	def Slice(start as int):
		return self.Slice(start, self.InnerList.Count)

	def slice(start as int, end as int):
		return self.Slice(start, end)

	def slice(start as int):
		return self.Slice(start)

	# Revert
	def Reverse() as Array:
		self.InnerList.Reverse()
		return self

	def reverse() as Array:
		self.Reverse()
		return self

	callable Comparison(lhs as object, rhs as object) as int

	private class ComparisonComparer(IComparer):
		def constructor(comparison as Comparison):
			_comparison = comparison
		def Compare(lhs, rhs):
			return _comparison(lhs, rhs)
		_comparison as Comparison

	def Sort(comparison as Comparison):
		self.InnerList.Sort(ComparisonComparer(comparison))

	def sort(comparison as Comparison):
		Sort(comparison)

	# Sort
	def Sort() as Array:
		self.InnerList.Sort()
		return self

	def sort() as Array:
		self.Sort()
		return self

	# Combine to a string
	def Join (seperator as String):
		return Builtins.join(self.InnerList, seperator)

	def join (seperator as String):
		return Builtins.join(self.InnerList, seperator)

	# Remove
	def Remove (obj as Object):
		self.InnerList.Remove(obj)

	def remove (obj as Object):
		self.Remove(obj)

	self[index as int] as object:
		get:
			return self.InnerList[index]
		set:
			EnsureCapacity(index+1)
			self.InnerList[index] = value

	def AddRange([required] collection as IEnumerable):
		for item in collection:
			self.InnerList.Add(item)

	override protected def OnValidate(newValue):
		pass // anything goes

	private def AddImpl(value as object, [required] items as IEnumerable):
		self.InnerList.Add(value)

		for item in items:
			self.InnerList.Add(item)
		return self.InnerList.Count

	private def UnshiftImpl(value as object, [required] items as IEnumerable):
		self.InnerList.InsertRange(0, items)
		self.InnerList.Insert(0, value)
		return self.InnerList.Count

	private def SpliceImpl (index as int, howmany as int, [required] items as IEnumerable):
		if howmany != 0:
			self.InnerList.RemoveRange(index, howmany)
		self.InnerList.InsertRange(index, items)

	private def ConcatImpl(value as ICollection, [required] items as IEnumerable):
		arr = Array (self.InnerList)
		arr.InnerList.AddRange(value)

		for item as ICollection in items:
			arr.InnerList.AddRange(item)

		return arr

	private def EnsureCapacity(capacity as int):
		return if capacity < Count
		for i in range(capacity-self.Count):
			InnerList.Add(null)

	private def NormalizeIndex(index as int):
		return index if index >= 0
		return index + InnerList.Count