fix for prefix operators; super and self

namespace Boo.OMeta.Parser

import Boo.OMeta
import Boo.PatternMatching

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

import System.Globalization

macro infix:

	l, op, r = infix.Arguments

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

macro infixr:

	l, op, r = infixr.Arguments

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

macro prefix:

	rule, op, next = prefix.Arguments

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

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

	list_of expression

Expands to something that matches:

	expression (COMMA expression)+
"""

	rule, = list_of.Arguments

	block as Block = list_of.ParentNode

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

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

ometa BooParser < WhitespaceSensitiveTokenizer:

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

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

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

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

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

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

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

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

	module_member = class_def | method

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

	begin_block = COLON, INDENT

	end_block = DEDENT

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

	class_member = method | class_def

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

	list_of parameter

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

	block = empty_block | non_empty_block

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

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

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

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

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

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

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

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

	stmt_block = stmt_if | stmt_for

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

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

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

	optional_expression = expression | ""

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

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

	list_of assignment

	infixr assignment, (ASSIGN | ASSIGN_INPLACE), expression

	expression = or_expression

	infix or_expression, OR, and_expression

	infix and_expression, AND, not_expression

	prefix not_expression, NOT, membership_expression

	infix membership_expression, (IN | (NOT, IN)), identity_test_expression

	infix identity_test_expression, ((IS, NOT) | IS), comparison

	infix comparison, (EQUALITY | INEQUALITY | IS), 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, 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 = (expression >> begin,
				((COLON, omitted_expression >> end,
					((COLON, omitted_expression >> step) | ""))
					| "")) ^ newSlice(begin, end, step)

	list_of slice

	omitted_expression = expression | ("" ^ OmittedExpression.Default)

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

	type_reference = type_reference_simple

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

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

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

	null_literal = NULL ^ [| null |]

	super_literal = SUPER ^ [| super |]

	self_literal = SELF ^ [| self |]

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

	array_literal = (LPAREN, (COMMA ^ (items=[])) | (expression_list >> items, (COMMA | "")), RPAREN) ^ newArrayLiteral(items)

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

	list_of expression

	list_of declaration

	reference = ID >> r ^ newReference(r)

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

	boolean = true_literal | false_literal

	true_literal = TRUE ^ [| true |]

	false_literal = FALSE ^ [| false |]

	eol = ++EOL | ~_

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	def binaryOperatorFor(op):
		match tokenValue(op):
			case "is": return BinaryOperatorType.ReferenceEquality
			case "and": return BinaryOperatorType.And
			case "or": return BinaryOperatorType.Or
			case "^": return BinaryOperatorType.ExclusiveOr
			case "+": return BinaryOperatorType.Addition
			case "-": return BinaryOperatorType.Subtraction
			case "*": return BinaryOperatorType.Multiply
			case "**": return BinaryOperatorType.Exponentiation
			case "/": return BinaryOperatorType.Division
			case "%": return BinaryOperatorType.Modulus
			case "=": return BinaryOperatorType.Assign
			case "==": return BinaryOperatorType.Equality
			case "!=": return BinaryOperatorType.Inequality
			case "+=": return BinaryOperatorType.InPlaceAddition
			case "-=": return BinaryOperatorType.InPlaceSubtraction
			case "/=": return BinaryOperatorType.InPlaceDivision
			case "*=": return BinaryOperatorType.InPlaceMultiply
			case "^=": return BinaryOperatorType.InPlaceExclusiveOr

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

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

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

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

namespace Boo.OMeta.Parser

import Boo.OMeta
import Boo.PatternMatching

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

import System.Globalization

macro infix:

	l, op, r = infix.Arguments

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

macro infixr:

	l, op, r = infixr.Arguments

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

macro prefix:

	rule, op, next = prefix.Arguments

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

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

	list_of expression

Expands to something that matches:

	expression (COMMA expression)+
"""

	rule, = list_of.Arguments

	block as Block = list_of.ParentNode

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

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

ometa BooParser < WhitespaceSensitiveTokenizer:

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

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

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

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

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

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

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

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

	module_member = class_def | method

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

	begin_block = COLON, INDENT

	end_block = DEDENT

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

	class_member = method | class_def

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

	list_of parameter

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

	block = empty_block | non_empty_block

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

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

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

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

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

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

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

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

	stmt_block = stmt_if | stmt_for

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

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

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

	optional_expression = expression | ""

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

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

	list_of assignment

	infixr assignment, (ASSIGN | ASSIGN_INPLACE), expression

	expression = or_expression

	infix or_expression, OR, and_expression

	infix and_expression, AND, not_expression

	not_expression = ((NOT >> op, expression >> e) ^ newPrefixExpression(op, e)) | membership_expression

	infix membership_expression, (IN | (NOT, IN)), identity_test_expression

	infix identity_test_expression, ((IS, NOT) | IS), comparison

	infix comparison, (EQUALITY | INEQUALITY | IS), 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_op, term

	bitwise_shift_op = (BITWISE_SHIFT_LEFT | BITWISE_SHIFT_RIGHT)

	infix term, (PLUS | MINUS), factor

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

	prefix signalled_expression, MINUS, bitwise_not_expression

	prefix bitwise_not_expression, BITWISE_NOT, 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 = ((expression >> e, DOT, ID >> name) ^ newMemberReference(e, name)) | slicing

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

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

	list_of slice

	omitted_expression = expression | ("" ^ OmittedExpression.Default)

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

	type_reference = type_reference_simple

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

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

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

	null_literal = NULL ^ [| null |]

	prefix = ((BITWISE_NOT | MINUS) >> op, expression >> e) ^ newPrefixExpression(op, e)

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

	array_literal = (LPAREN, (COMMA ^ (items=[])) | (expression_list >> items, (COMMA | "")), RPAREN) ^ newArrayLiteral(items)

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

	list_of expression

	list_of declaration

	reference = ID >> r ^ newReference(r)

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

	boolean = true_literal | false_literal

	true_literal = TRUE ^ [| true |]

	false_literal = FALSE ^ [| false |]

	eol = ++EOL | ~_

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	def binaryOperatorFor(op):
		match tokenValue(op):
			case "is": return BinaryOperatorType.ReferenceEquality
			case "and": return BinaryOperatorType.And
			case "or": return BinaryOperatorType.Or
			case "^": return BinaryOperatorType.ExclusiveOr
			case "+": return BinaryOperatorType.Addition
			case "-": return BinaryOperatorType.Subtraction
			case "*": return BinaryOperatorType.Multiply
			case "**": return BinaryOperatorType.Exponentiation
			case "/": return BinaryOperatorType.Division
			case "%": return BinaryOperatorType.Modulus
			case "=": return BinaryOperatorType.Assign
			case "==": return BinaryOperatorType.Equality
			case "!=": return BinaryOperatorType.Inequality
			case "+=": return BinaryOperatorType.InPlaceAddition
			case "-=": return BinaryOperatorType.InPlaceSubtraction
			case "/=": return BinaryOperatorType.InPlaceDivision
			case "*=": return BinaryOperatorType.InPlaceMultiply
			case "^=": return BinaryOperatorType.InPlaceExclusiveOr

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

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

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

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


using macros to generate ometa rules is a good idea indeed

import Boo.OMeta
import Boo.Adt
import Boo.PatternMatching
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast

data Exp = Const(value as int) | Infix(operator as string, left as Exp, right as Exp)

// using macros to generate more complex ometa rules
// is indeed a good idea
macro infix:

	l, op, r = infix.Arguments

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

ometa Parser:
	parse = sum
	infix sum, ('+' | '-'), fac
	infix fac, ('*' |  '/'), atom
	atom = num | parens
	parens = ('(', exp >> value, ')') ^ value
	num = ++digit >> value ^ Const(int.Parse(join(value, '')))

// See, Mom! No visitors!
ometa Evaluator:
	eval = const | infix
	const = Const(value) ^ value
	infix = sum | subtraction | mult | division

	// we could certainly use a macro here too
	sum = Infix(operator: "+", left: eval >> l, right: eval >> r) ^ add(l, r)
	subtraction = Infix(operator: "-", left: eval >> l, right: eval >> r) ^ subtract(l, r)
	mult = Infix(operator: "*", left: eval >> l, right: eval >> r) ^ multiply(l, r)
	division = Infix(operator: "/", left: eval >> l, right: eval >> r) ^ divide(l, r)

	def add(x as int, y as int):
		return x + y

	def subtract(x as int, y as int):
		return x - y

	def multiply(x as int, y as int):
		return x * y

	def divide(left as int, right as int):
		return left / right

while true:
	line = prompt("> ")
	if string.IsNullOrEmpty(line) or line == "/q": break
	match m=Parser().parse(OMetaInput.For(line.Trim())):
		case SuccessfulMatch(Value, Input: OMetaInput(IsEmpty: true)):
			print Evaluator().eval(OMetaInput.Singleton(Value))
		otherwise:
			print m

import Boo.OMeta
import Boo.Adt
import Boo.PatternMatching

data Exp = Const(value as int) | Infix(operator as string, left as Exp, right as Exp)

ometa E:
	num = ++digit >> value ^ Const(int.Parse(join(value, '')))
	exp = sum | fac
	sum = (fac >> x, '+', fac >> y) ^ Infix("+", x, y)
	fac = mult | atom
	mult = (atom >> x,'*', atom >> y) ^ Infix("*", x, y)
	atom = num | parens
	parens = ('(', exp >> value, ')') ^ value

ometa XE < E:
	fac = division | super
	division = (atom >> x, '/', atom >> y) ^ Infix("/", x, y)

// See, Mom! No visitors!
ometa Evaluator:
	eval = const | infix
	const = Const(value) ^ value
	infix = sum | mult
	sum = Infix(operator: "+", left: eval >> l, right: eval >> r) ^ add(l, r)
	mult = Infix(operator: "*", left: eval >> l, right: eval >> r) ^ multiply(l, r)
	def add(x as int, y as int):
		return x + y
	def multiply(x as int, y as int):
		return x * y

ometa XEvaluator < Evaluator:
	eval = division | super
	division = Infix(operator: "/", left: eval >> l, right: eval >> r) ^ divide(l, r)
	def divide(left as int, right as int):
		return left / right

while true:
	line = prompt("> ")
	if string.IsNullOrEmpty(line) or line == "/q": break
	match m=XE().Apply('exp', OMetaInput.For(line)):
		case SuccessfulMatch(Value):
			print XEvaluator().eval(OMetaInput.Singleton(Value))
		otherwise:
			print m


enable IQuackFu even when in strict mode (fixes tests/integration/quack-fu.js)

namespace UnityScript.Steps

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

import Boo.Lang.Environments

import UnityScript.Core
import UnityScript.Macros
import UnityScript.TypeSystem

class ProcessUnityScriptMethods(ProcessMethodBodiesWithDuckTyping):

	deferred IEnumerable_GetEnumerator = Types.IEnumerable.GetMethod("GetEnumerator");

	deferred IEnumerator_MoveNext = Types.IEnumerator.GetMethod("MoveNext");

	deferred IEnumerator_get_Current = Types.IEnumerator.GetProperty("Current").GetGetMethod();

	deferred _StartCoroutine = NameResolutionService.ResolveMethod(UnityScriptTypeSystem.ScriptBaseType, "StartCoroutine_Auto")

	deferred _UnityRuntimeServices_GetEnumerator = ResolveUnityRuntimeMethod("GetEnumerator")

	deferred _UnityRuntimeServices_Update = ResolveUnityRuntimeMethod("Update")

	deferred _UnityRuntimeServices_GetTypeOf = ResolveUnityRuntimeMethod("GetTypeOf")

	_implicit = false

	override def Initialize(context as CompilerContext):
		super(context)

		// don't transform
		//     foo == null
		// into:
		//     foo is null
		// but into:
		//     foo.Equals(null)
		self.OptimizeNullComparisons = false

	def ResolveUnityRuntimeMethod(name as string):
		return NameResolutionService.ResolveMethod(UnityRuntimeServicesType, name)

	def ResolveUnityRuntimeField(name as string):
		return NameResolutionService.ResolveField(UnityRuntimeServicesType, name)

	deferred UnityRuntimeServicesType = TypeSystemServices.Map(UnityScript.Lang.UnityRuntimeServices)

	UnityScriptTypeSystem as UnityScript.TypeSystem.UnityScriptTypeSystem:
		get: return self.TypeSystemServices

	UnityScriptParameters as UnityScript.UnityScriptCompilerParameters:
		get: return _context.Parameters

	override def GetGeneratorReturnType(generator as InternalMethod):
		return TypeSystemServices.IEnumeratorType

	override protected def MemberNotFound(node as MemberReferenceExpression, ns as INamespace):
		if Strict:
			super(node, ns)
		else:
			BindQuack(node)

	override protected def LocalToReuseFor(d as Declaration):
		if DeclarationAnnotations.ShouldForceNewVariableFor(d):
			AssertUniqueLocal(d)
			return null
		return super(d)

	override def OnModule(module as Module):
		preserving _activeModule, Parameters.Strict, _implicit, my(UnityDowncastPermissions).Enabled:
			EnterModuleContext(module)
			super(module)

	override def VisitMemberPreservingContext(node as TypeMember):

		module = node.EnclosingModule
		if module is _activeModule:
			super(node)
			return

		preserving _activeModule, Parameters.Strict, _implicit, my(UnityDowncastPermissions).Enabled:
			EnterModuleContext(module)
			super(node)

	private def EnterModuleContext(module as Module):
		_activeModule = module
		Parameters.Strict = Pragmas.IsEnabledOn(module, Pragmas.Strict)
		_implicit = Pragmas.IsEnabledOn(module, Pragmas.Implicit)
		my(UnityDowncastPermissions).Enabled = Pragmas.IsEnabledOn(module, Pragmas.Downcast)

	_activeModule as Module

	Strict:
		get: return Parameters.Strict

	override def OnMethod(node as Method):
		super(node)
		CheckForEmptyCoroutine(node)
		return if Parameters.OutputType == CompilerOutputType.Library
		CheckEntryPoint(node)

	def CheckForEmptyCoroutine(node as Method):
		if not IsEmptyCoroutine(node):
			return
		node.Body.Add([| return $(EmptyEnumeratorReference) |])

	def IsEmptyCoroutine(node as Method):
		entity as InternalMethod = GetEntity(node)
		return entity.ReturnType is GetGeneratorReturnType(entity) and HasNeitherReturnNorYield(node)

	deferred EmptyEnumeratorReference = CodeBuilder.CreateMemberReference(ResolveUnityRuntimeField("EmptyEnumerator"))

	def CheckEntryPoint(node as Method):
		if not node.IsStatic: return
		if not node.IsPublic: return
		if node.Name != "Main": return
		if GetType(node.ReturnType) is not TypeSystemServices.VoidType: return

		ContextAnnotations.SetEntryPoint(_context, node)

	override def ProcessAutoLocalDeclaration(node as BinaryExpression, reference as ReferenceExpression):
		if (Strict and not _implicit) and not IsCompilerGenerated(reference):
			EmitUnknownIdentifierError(reference, reference.Name)
		else:
			super(node, reference)

	def IsCompilerGenerated(reference as ReferenceExpression):
		return reference.Name.Contains('$')

	override protected def ProcessBuiltinInvocation(node as MethodInvocationExpression, function as BuiltinFunction):
		if function is UnityScriptTypeSystem.UnityScriptEval:
			EvalAnnotation.Mark(_currentMethod.Method)
			BindExpressionType(node, TypeSystemServices.ObjectType)
			return
		if function is UnityScriptTypeSystem.UnityScriptTypeof:
			ProcessTypeofBuiltin(node);
			return
		super(node, function)

	private def ProcessTypeofBuiltin(node as MethodInvocationExpression):
		if node.Arguments.Count != 1:
			Error(node, CompilerError("UCE0001", node.Target.LexicalInfo, "'typeof' takes a single argument.", null))
			return

		type = node.Arguments[0].Entity as IType
		if type is not null:
			node.ParentNode.Replace(node, CodeBuilder.CreateTypeofExpression(type))
			return

		node.Target = CodeBuilder.CreateReference(_UnityRuntimeServices_GetTypeOf)
		BindExpressionType(node, TypeSystemServices.TypeType)

	override protected def ProcessMethodInvocation(node as MethodInvocationExpression, method as IMethod):
	"""
	Automatically detects coroutine invocations in assignments and as standalone
	expressions and generates StartCoroutine invocations.
	"""
		super(node, method)

		if not IsPossibleStartCoroutineInvocation(node):
			return

		if method.IsStatic: return

		tss = self.UnityScriptTypeSystem
		if not tss.IsScriptType(method.DeclaringType): return
		if not tss.IsGenerator(method): return

		parentNode = node.ParentNode
		parentNode.Replace(
			node,
			CodeBuilder.CreateMethodInvocation(
				TargetForStartCoroutineInvocation(node, method),
				_StartCoroutine,
				node))

	def TargetForStartCoroutineInvocation(node as MethodInvocationExpression, method as IMethod):
		target = cast(MemberReferenceExpression, node.Target).Target
		if target isa SuperLiteralExpression: // super becomes self for coroutine invocation
			return CodeBuilder.CreateSelfReference(target.LexicalInfo, method.DeclaringType)
		return target.CloneNode()

	override def ProcessStaticallyTypedAssignment(node as BinaryExpression):
		TryToResolveAmbiguousAssignment(node)
		ApplyImplicitArrayConversion(node)
		if ValidateAssignment(node):
			BindExpressionType(node, GetExpressionType(node.Right))
		else:
			Error(node)

	def ApplyImplicitArrayConversion(node as BinaryExpression):
		left = GetExpressionType(node.Left)
		if not left.IsArray: return

		right = GetExpressionType(node.Right)
		if right is not TypeSystemServices.Map(UnityScript.Lang.Array): return

		node.Right = CodeBuilder.CreateCast(left,
						CodeBuilder.CreateMethodInvocation(
							node.Right,
							ResolveMethod(right, "ToBuiltin"),
							CodeBuilder.CreateTypeofExpression(left.ElementType)))

	override def OnForStatement(node as ForStatement):
		assert 1 == len(node.Declarations)
		Visit(node.Iterator)
		if NeedsUpdateableIteration(node):
			ProcessUpdateableIteration(node)
		else:
			ProcessNormalIteration(node)

	def ProcessNormalIteration(node as ForStatement):
		node.Iterator = ProcessIterator(node.Iterator, node.Declarations)
		VisitForStatementBlock(node)

	def ProcessUpdateableIteration(node as ForStatement):
		newIterator = CodeBuilder.CreateMethodInvocation(_UnityRuntimeServices_GetEnumerator, node.Iterator)
		newIterator.LexicalInfo = LexicalInfo(node.Iterator.LexicalInfo)
		node.Iterator = newIterator
		ProcessDeclarationForIterator(node.Declarations[0], TypeSystemServices.ObjectType)
		VisitForStatementBlock(node)
		TransformIteration(node)

	def TransformIteration(node as ForStatement):
		iterator = CodeBuilder.DeclareLocal(
						_currentMethod.Method,
						_context.GetUniqueName("iterator"),
						TypeSystemServices.IEnumeratorType)
		iterator.IsUsed = true
		body = Block(node.LexicalInfo)
		body.Add(
			CodeBuilder.CreateAssignment(
				node.LexicalInfo,
				CodeBuilder.CreateReference(iterator),
				node.Iterator))

		// while __iterator.MoveNext():
		ws = WhileStatement(node.LexicalInfo)
		ws.Condition = CodeBuilder.CreateMethodInvocation(
						CodeBuilder.CreateReference(iterator),
						IEnumerator_MoveNext)

		current = CodeBuilder.CreateMethodInvocation(
							CodeBuilder.CreateReference(iterator),
							IEnumerator_get_Current)

		//	item = __iterator.Current
		loopVariable as InternalLocal = TypeSystemServices.GetEntity(node.Declarations[0])
		ws.Block.Add(
				CodeBuilder.CreateAssignment(
					node.LexicalInfo,
					CodeBuilder.CreateReference(loopVariable),
					current))
		ws.Block.Add(node.Block)

		LoopVariableUpdater(self, _context, iterator, loopVariable).Visit(node)

		body.Add(ws)
		node.ParentNode.Replace(node, body)

	def NeedsUpdateableIteration(node as ForStatement):
		iteratorType = GetExpressionType(node.Iterator)
		if iteratorType.IsArray: return false
		return true

	class LoopVariableUpdater(DepthFirstVisitor):

		_parent as ProcessUnityScriptMethods
		_context as CompilerContext
		_iteratorVariable as IEntity
		_loopVariable as IEntity
		_found as bool

		def constructor(parent as ProcessUnityScriptMethods, context as CompilerContext, iteratorVariable as IEntity, loopVariable as IEntity):
			_parent = parent
			_context = context
			_iteratorVariable = iteratorVariable
			_loopVariable = loopVariable

		override def OnExpressionStatement(node as ExpressionStatement):
			_found = false
			Visit(node.Expression)
			if not _found: return

			parentNode = node.ParentNode

			codeBuilder = _context.CodeBuilder
			block = Block(node.LexicalInfo)
			block.Add(node)
			block.Add(
				codeBuilder.CreateMethodInvocation(
					_parent._UnityRuntimeServices_Update,
					codeBuilder.CreateReference(_iteratorVariable),
					codeBuilder.CreateReference(_loopVariable)))

			parentNode.Replace(node, block)

		override def OnReferenceExpression(node as ReferenceExpression):
			if _found: return

			referent = node.Entity
			_found = referent is _loopVariable

namespace UnityScript.Steps

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

import Boo.Lang.Environments

import UnityScript.Core
import UnityScript.Macros
import UnityScript.TypeSystem

class ProcessUnityScriptMethods(ProcessMethodBodiesWithDuckTyping):

	deferred IEnumerable_GetEnumerator = Types.IEnumerable.GetMethod("GetEnumerator");

	deferred IEnumerator_MoveNext = Types.IEnumerator.GetMethod("MoveNext");

	deferred IEnumerator_get_Current = Types.IEnumerator.GetProperty("Current").GetGetMethod();

	deferred _StartCoroutine = NameResolutionService.ResolveMethod(UnityScriptTypeSystem.ScriptBaseType, "StartCoroutine_Auto")

	deferred _UnityRuntimeServices_GetEnumerator = ResolveUnityRuntimeMethod("GetEnumerator")

	deferred _UnityRuntimeServices_Update = ResolveUnityRuntimeMethod("Update")

	deferred _UnityRuntimeServices_GetTypeOf = ResolveUnityRuntimeMethod("GetTypeOf")

	_implicit = false

	override def Initialize(context as CompilerContext):
		super(context)

		// don't transform
		//     foo == null
		// into:
		//     foo is null
		// but into:
		//     foo.Equals(null)
		self.OptimizeNullComparisons = false

	def ResolveUnityRuntimeMethod(name as string):
		return NameResolutionService.ResolveMethod(UnityRuntimeServicesType, name)

	def ResolveUnityRuntimeField(name as string):
		return NameResolutionService.ResolveField(UnityRuntimeServicesType, name)

	deferred UnityRuntimeServicesType = TypeSystemServices.Map(UnityScript.Lang.UnityRuntimeServices)

	UnityScriptTypeSystem as UnityScript.TypeSystem.UnityScriptTypeSystem:
		get: return self.TypeSystemServices

	UnityScriptParameters as UnityScript.UnityScriptCompilerParameters:
		get: return _context.Parameters

	override def GetGeneratorReturnType(generator as InternalMethod):
		return TypeSystemServices.IEnumeratorType

	override def IsDuckTyped(e as Expression):
		if Strict: return false
		return super(e)

	override def IsDuckTyped(member as IMember):
		if Strict: return false
		return super(member)

	override protected def MemberNotFound(node as MemberReferenceExpression, ns as INamespace):
		if Strict:
			super(node, ns)
		else:
			BindQuack(node)

	override protected def LocalToReuseFor(d as Declaration):
		if DeclarationAnnotations.ShouldForceNewVariableFor(d):
			AssertUniqueLocal(d)
			return null
		return super(d)

	override def OnModule(module as Module):
		preserving _activeModule, Parameters.Strict, _implicit, my(UnityDowncastPermissions).Enabled:
			EnterModuleContext(module)
			super(module)

	override def VisitMemberPreservingContext(node as TypeMember):

		module = node.EnclosingModule
		if module is _activeModule:
			super(node)
			return

		preserving _activeModule, Parameters.Strict, _implicit, my(UnityDowncastPermissions).Enabled:
			EnterModuleContext(module)
			super(node)

	private def EnterModuleContext(module as Module):
		_activeModule = module
		Parameters.Strict = Pragmas.IsEnabledOn(module, Pragmas.Strict)
		_implicit = Pragmas.IsEnabledOn(module, Pragmas.Implicit)
		my(UnityDowncastPermissions).Enabled = Pragmas.IsEnabledOn(module, Pragmas.Downcast)

	_activeModule as Module

	Strict:
		get: return Parameters.Strict

	override def OnMethod(node as Method):
		super(node)
		CheckForEmptyCoroutine(node)
		return if Parameters.OutputType == CompilerOutputType.Library
		CheckEntryPoint(node)

	def CheckForEmptyCoroutine(node as Method):
		if not IsEmptyCoroutine(node):
			return
		node.Body.Add([| return $(EmptyEnumeratorReference) |])

	def IsEmptyCoroutine(node as Method):
		entity as InternalMethod = GetEntity(node)
		return entity.ReturnType is GetGeneratorReturnType(entity) and HasNeitherReturnNorYield(node)

	deferred EmptyEnumeratorReference = CodeBuilder.CreateMemberReference(ResolveUnityRuntimeField("EmptyEnumerator"))

	def CheckEntryPoint(node as Method):
		if not node.IsStatic: return
		if not node.IsPublic: return
		if node.Name != "Main": return
		if GetType(node.ReturnType) is not TypeSystemServices.VoidType: return

		ContextAnnotations.SetEntryPoint(_context, node)

	override def ProcessAutoLocalDeclaration(node as BinaryExpression, reference as ReferenceExpression):
		if (Strict and not _implicit) and not IsCompilerGenerated(reference):
			EmitUnknownIdentifierError(reference, reference.Name)
		else:
			super(node, reference)

	def IsCompilerGenerated(reference as ReferenceExpression):
		return reference.Name.Contains('$')

	override protected def ProcessBuiltinInvocation(node as MethodInvocationExpression, function as BuiltinFunction):
		if function is UnityScriptTypeSystem.UnityScriptEval:
			EvalAnnotation.Mark(_currentMethod.Method)
			BindExpressionType(node, TypeSystemServices.ObjectType)
			return
		if function is UnityScriptTypeSystem.UnityScriptTypeof:
			ProcessTypeofBuiltin(node);
			return
		super(node, function)

	private def ProcessTypeofBuiltin(node as MethodInvocationExpression):
		if node.Arguments.Count != 1:
			Error(node, CompilerError("UCE0001", node.Target.LexicalInfo, "'typeof' takes a single argument.", null))
			return

		type = node.Arguments[0].Entity as IType
		if type is not null:
			node.ParentNode.Replace(node, CodeBuilder.CreateTypeofExpression(type))
			return

		node.Target = CodeBuilder.CreateReference(_UnityRuntimeServices_GetTypeOf)
		BindExpressionType(node, TypeSystemServices.TypeType)

	override protected def ProcessMethodInvocation(node as MethodInvocationExpression, method as IMethod):
	"""
	Automatically detects coroutine invocations in assignments and as standalone
	expressions and generates StartCoroutine invocations.
	"""
		super(node, method)

		if not IsPossibleStartCoroutineInvocation(node):
			return

		if method.IsStatic: return

		tss = self.UnityScriptTypeSystem
		if not tss.IsScriptType(method.DeclaringType): return
		if not tss.IsGenerator(method): return

		parentNode = node.ParentNode
		parentNode.Replace(
			node,
			CodeBuilder.CreateMethodInvocation(
				TargetForStartCoroutineInvocation(node, method),
				_StartCoroutine,
				node))

	def TargetForStartCoroutineInvocation(node as MethodInvocationExpression, method as IMethod):
		target = cast(MemberReferenceExpression, node.Target).Target
		if target isa SuperLiteralExpression: // super becomes self for coroutine invocation
			return CodeBuilder.CreateSelfReference(target.LexicalInfo, method.DeclaringType)
		return target.CloneNode()

	override def ProcessStaticallyTypedAssignment(node as BinaryExpression):
		TryToResolveAmbiguousAssignment(node)
		ApplyImplicitArrayConversion(node)
		if ValidateAssignment(node):
			BindExpressionType(node, GetExpressionType(node.Right))
		else:
			Error(node)

	def ApplyImplicitArrayConversion(node as BinaryExpression):
		left = GetExpressionType(node.Left)
		if not left.IsArray: return

		right = GetExpressionType(node.Right)
		if right is not TypeSystemServices.Map(UnityScript.Lang.Array): return

		node.Right = CodeBuilder.CreateCast(left,
						CodeBuilder.CreateMethodInvocation(
							node.Right,
							ResolveMethod(right, "ToBuiltin"),
							CodeBuilder.CreateTypeofExpression(left.ElementType)))

	override def OnForStatement(node as ForStatement):
		assert 1 == len(node.Declarations)
		Visit(node.Iterator)
		if NeedsUpdateableIteration(node):
			ProcessUpdateableIteration(node)
		else:
			ProcessNormalIteration(node)

	def ProcessNormalIteration(node as ForStatement):
		node.Iterator = ProcessIterator(node.Iterator, node.Declarations)
		VisitForStatementBlock(node)

	def ProcessUpdateableIteration(node as ForStatement):
		newIterator = CodeBuilder.CreateMethodInvocation(_UnityRuntimeServices_GetEnumerator, node.Iterator)
		newIterator.LexicalInfo = LexicalInfo(node.Iterator.LexicalInfo)
		node.Iterator = newIterator
		ProcessDeclarationForIterator(node.Declarations[0], TypeSystemServices.ObjectType)
		VisitForStatementBlock(node)
		TransformIteration(node)

	def TransformIteration(node as ForStatement):
		iterator = CodeBuilder.DeclareLocal(
						_currentMethod.Method,
						_context.GetUniqueName("iterator"),
						TypeSystemServices.IEnumeratorType)
		iterator.IsUsed = true
		body = Block(node.LexicalInfo)
		body.Add(
			CodeBuilder.CreateAssignment(
				node.LexicalInfo,
				CodeBuilder.CreateReference(iterator),
				node.Iterator))

		// while __iterator.MoveNext():
		ws = WhileStatement(node.LexicalInfo)
		ws.Condition = CodeBuilder.CreateMethodInvocation(
						CodeBuilder.CreateReference(iterator),
						IEnumerator_MoveNext)

		current = CodeBuilder.CreateMethodInvocation(
							CodeBuilder.CreateReference(iterator),
							IEnumerator_get_Current)

		//	item = __iterator.Current
		loopVariable as InternalLocal = TypeSystemServices.GetEntity(node.Declarations[0])
		ws.Block.Add(
				CodeBuilder.CreateAssignment(
					node.LexicalInfo,
					CodeBuilder.CreateReference(loopVariable),
					current))
		ws.Block.Add(node.Block)

		LoopVariableUpdater(self, _context, iterator, loopVariable).Visit(node)

		body.Add(ws)
		node.ParentNode.Replace(node, body)

	def NeedsUpdateableIteration(node as ForStatement):
		iteratorType = GetExpressionType(node.Iterator)
		if iteratorType.IsArray: return false
		return true

	class LoopVariableUpdater(DepthFirstVisitor):

		_parent as ProcessUnityScriptMethods
		_context as CompilerContext
		_iteratorVariable as IEntity
		_loopVariable as IEntity
		_found as bool

		def constructor(parent as ProcessUnityScriptMethods, context as CompilerContext, iteratorVariable as IEntity, loopVariable as IEntity):
			_parent = parent
			_context = context
			_iteratorVariable = iteratorVariable
			_loopVariable = loopVariable

		override def OnExpressionStatement(node as ExpressionStatement):
			_found = false
			Visit(node.Expression)
			if not _found: return

			parentNode = node.ParentNode

			codeBuilder = _context.CodeBuilder
			block = Block(node.LexicalInfo)
			block.Add(node)
			block.Add(
				codeBuilder.CreateMethodInvocation(
					_parent._UnityRuntimeServices_Update,
					codeBuilder.CreateReference(_iteratorVariable),
					codeBuilder.CreateReference(_loopVariable)))

			parentNode.Replace(node, block)

		override def OnReferenceExpression(node as ReferenceExpression):
			if _found: return

			referent = node.Entity
			_found = referent is _loopVariable


vertical scrollbar

vertical scrollbar

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

#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

import System
import booish from booish
import System.Windows.Forms from System.Windows.Forms
import System.Drawing from System.Drawing

class PromptBox(TextBox):

	static Enter = chr(13)

	_interpreter = InteractiveInterpreter(RememberLastValue: true, Print: print)

	def constructor():
		self.Multiline = true
		self.ScrollBars = ScrollBars.Vertical
		_interpreter.References.Add(typeof(TextBox).Assembly)
		_interpreter.References.Add(typeof(Font).Assembly)

		prompt()

	override def OnKeyPress(args as KeyPressEventArgs):
		if Enter == args.KeyChar:
			try:
				code = Lines[-1][4:]
				print("")
				_interpreter.LoopEval(code)
			except x:
				print(x)
			prompt()
			args.Handled = true
		super(args)

	def print(msg):
		AppendText("${msg}\r\n")

	def prompt():
		AppendText(">>> ")

	static def chr(value as int):
		return cast(IConvertible, value).ToChar(null)

class MainForm(Form):

	def constructor():
		self.Text = "booish"
		Controls.Add(PromptBox(Dock: DockStyle.Fill,
							Font: System.Drawing.Font("Lucida Console", 12)))

if Application.MessageLoop:
	MainForm().ShowDialog()
else:
	Application.Run(MainForm())

#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

import System
import booish from booish
import System.Windows.Forms from System.Windows.Forms
import System.Drawing from System.Drawing

class PromptBox(TextBox):

	static Enter = chr(13)

	_interpreter = InteractiveInterpreter(RememberLastValue: true, Print: print)

	def constructor():
		self.Multiline = true

		_interpreter.References.Add(typeof(TextBox).Assembly)
		_interpreter.References.Add(typeof(Font).Assembly)

		prompt()

	override def OnKeyPress(args as KeyPressEventArgs):
		if Enter == args.KeyChar:
			try:
				code = Lines[-1][4:]
				print("")
				_interpreter.LoopEval(code)
			except x:
				print(x)
			prompt()
			args.Handled = true

	def print(msg):
		AppendText("${msg}\r\n")

	def prompt():
		AppendText(">>> ")

	static def chr(value as int):
		return cast(IConvertible, value).ToChar(null)

class MainForm(Form):

	def constructor():
		self.Text = "booish"
		Controls.Add(PromptBox(Dock: DockStyle.Fill,
							Font: System.Drawing.Font("Lucida Console", 12)))

if Application.MessageLoop:
	MainForm().ShowDialog()
else:
	Application.Run(MainForm())


usage information

usage information

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

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

namespace Boo.Lang.Useful.Attributes

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

class OnceAttribute(AbstractAstAttribute):
"""
Caches the return value of a method.

Usage
	[once]
	def foo():
		return Math.Sin(Math.PI*3)

@author Sorin Ionescu (sorin.ionescu@gmail.com)
"""
	private class ReturnValueStorageVisitor(DepthFirstVisitor):
	"""
	Replaces returns statements with binary expressions that
	store the values of the return statements.
	"""
		_returnValue as ReferenceExpression

		def constructor(returnValue as ReferenceExpression):
			_returnValue = returnValue

		override def OnReturnStatement(returnStatement as ReturnStatement):
		"""
		Replaces returns statements with binary expressions that
		store the values of the return statements.
		"""
			returnValueAssignment = BinaryExpression(
				LexicalInfo: returnStatement.LexicalInfo,
				Operator: BinaryOperatorType.Assign,
				Left: _returnValue,
				Right: returnStatement.Expression)

			parentNode = returnStatement.ParentNode
			parentNode.Replace(
				returnStatement,
				ExpressionStatement(returnValueAssignment))

	_method as Method
	_methodLock as Field
	_cached as Field
	_returnValue as Field

	override def Dispose():
	"""
	Dont get rid of the context because we'll need it later.
	"""
		pass

	override def Apply(node as Node):
	"""
	Applies the <OnceAttribute> to [node].

	Parameters:
		node
			The node to apply the <OnceAttribute> to.
	"""
		assert node isa Method

		_method = node

		CreateCachedField()
		CreateMethodLockField()
		PrepareMethodBody()

		Context.Parameters.Pipeline.AfterStep += def(
			sender,
			e as CompilerStepEventArgs):

			return if not e.Step isa ProcessMethodBodies

			# Void methods cannot be cached.
			assert _method.ReturnType.Entity is not \
				self.TypeSystemServices.VoidType, "once attribute cannot be applied to void methods"

			CreateReturnValueField()
			PostProcessMethod()

	def CreateReturnValueField():
	"""
	Creates the field that stores the return value of the cached method.
	"""
		template = self.CodeBuilder.CreateField('field', _method.ReturnType.Entity)
		_returnValue = AddField(template, "___${_method.Name}_returnValue")

	def CreateCachedField():
	"""
	Creates the cached flag.

	Remarks:
		The flag is used to check whether the method has been cached.
	"""
		template = ast:
			private field as bool
		_cached = AddField(template, "___${_method.Name}_cached")

	def CreateMethodLockField():
	"""
	Creates the lock field.

	Remarks:
		The field is used to lock on when the operatation is thread safe.
	"""
		template = ast:
			private field as object = object()
		_methodLock = AddField(template, "___${_method.Name}_lock")

	def AddField(template as Field, name as string):
		template.LexicalInfo = self.LexicalInfo
		template.Name = name
		template.Modifiers = TypeMemberModifiers.Private
		template.Modifiers |= TypeMemberModifiers.Static if IsStaticMethod()
		_method.DeclaringType.Members.Add(template)
		return template

	def IsStaticMethod():
		return _method.IsStatic or _method.ParentNode isa Module

	def PrepareMethodBody():
		newMethodBodyTemplate = ast:
			if not cached:
				System.Threading.Monitor.Enter(methodLock)
				try:
					if not cached:
						oldMethodBody
						cached = true
				ensure:
					System.Threading.Monitor.Exit(methodLock)

		ReplaceReferences(newMethodBodyTemplate, 'cached', _cached.Name)
		ReplaceReferences(newMethodBodyTemplate, 'methodLock', _methodLock.Name)
		newMethodBodyTemplate.ReplaceNodes(
			MacroStatement(Name: 'oldMethodBody'),
			_method.Body)

		_method.Body = Block()
		_method.Body.Add(newMethodBodyTemplate)

	def ReplaceReferences(node as Node, what as string, value as string):
		node.ReplaceNodes(
			ReferenceExpression(what),
			ReferenceExpression(value))

	def PostProcessMethod():
	"""
	Visit the node and replace return statements
	with binary expressions that store the return
	statement values.
	Add a single return statement at the end
	to return the cached value.
	"""
		_method.Accept(
				ReturnValueStorageVisitor(
					self.CodeBuilder.CreateReference(_returnValue)))
		_method.Body.Add(
			ReturnStatement(
				LexicalInfo: LexicalInfo,
				Expression: self.CodeBuilder.CreateReference(_returnValue)))

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

namespace Boo.Lang.Useful.Attributes

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

class OnceAttribute(AbstractAstAttribute):
"""
Caches the return value of a method.

@author Sorin Ionescu (sorin.ionescu@gmail.com)
"""
	private class ReturnValueStorageVisitor(DepthFirstVisitor):
	"""
	Replaces returns statements with binary expressions that
	store the values of the return statements.
	"""
		_returnValue as ReferenceExpression

		def constructor(returnValue as ReferenceExpression):
			_returnValue = returnValue

		override def OnReturnStatement(returnStatement as ReturnStatement):
		"""
		Replaces returns statements with binary expressions that
		store the values of the return statements.
		"""
			returnValueAssignment = BinaryExpression(
				LexicalInfo: returnStatement.LexicalInfo,
				Operator: BinaryOperatorType.Assign,
				Left: _returnValue,
				Right: returnStatement.Expression)

			parentNode = returnStatement.ParentNode
			parentNode.Replace(
				returnStatement,
				ExpressionStatement(returnValueAssignment))

	_method as Method
	_methodLock as Field
	_cached as Field
	_returnValue as Field

	override def Dispose():
	"""
	Dont get rid of the context because we'll need it later.
	"""
		pass

	override def Apply(node as Node):
	"""
	Applies the <OnceAttribute> to [node].

	Parameters:
		node
			The node to apply the <OnceAttribute> to.
	"""
		assert node isa Method

		_method = node

		CreateCachedField()
		CreateMethodLockField()
		PrepareMethodBody()

		Context.Parameters.Pipeline.AfterStep += def(
			sender,
			e as CompilerStepEventArgs):

			return if not e.Step isa ProcessMethodBodies

			# Void methods cannot be cached.
			assert _method.ReturnType.Entity is not \
				self.TypeSystemServices.VoidType, "once attribute cannot be applied to void methods"

			CreateReturnValueField()
			PostProcessMethod()

	def CreateReturnValueField():
	"""
	Creates the field that stores the return value of the cached method.
	"""
		template = self.CodeBuilder.CreateField('field', _method.ReturnType.Entity)
		_returnValue = AddField(template, "___${_method.Name}_returnValue")

	def CreateCachedField():
	"""
	Creates the cached flag.

	Remarks:
		The flag is used to check whether the method has been cached.
	"""
		template = ast:
			private field as bool
		_cached = AddField(template, "___${_method.Name}_cached")

	def CreateMethodLockField():
	"""
	Creates the lock field.

	Remarks:
		The field is used to lock on when the operatation is thread safe.
	"""
		template = ast:
			private field as object = object()
		_methodLock = AddField(template, "___${_method.Name}_lock")

	def AddField(template as Field, name as string):
		template.LexicalInfo = self.LexicalInfo
		template.Name = name
		template.Modifiers = TypeMemberModifiers.Private
		template.Modifiers |= TypeMemberModifiers.Static if IsStaticMethod()
		_method.DeclaringType.Members.Add(template)
		return template

	def IsStaticMethod():
		return _method.IsStatic or _method.ParentNode isa Module

	def PrepareMethodBody():
		newMethodBodyTemplate = ast:
			if not cached:
				System.Threading.Monitor.Enter(methodLock)
				try:
					if not cached:
						oldMethodBody
						cached = true
				ensure:
					System.Threading.Monitor.Exit(methodLock)

		ReplaceReferences(newMethodBodyTemplate, 'cached', _cached.Name)
		ReplaceReferences(newMethodBodyTemplate, 'methodLock', _methodLock.Name)
		newMethodBodyTemplate.ReplaceNodes(
			MacroStatement(Name: 'oldMethodBody'),
			_method.Body)

		_method.Body = Block()
		_method.Body.Add(newMethodBodyTemplate)

	def ReplaceReferences(node as Node, what as string, value as string):
		node.ReplaceNodes(
			ReferenceExpression(what),
			ReferenceExpression(value))

	def PostProcessMethod():
	"""
	Visit the node and replace return statements
	with binary expressions that store the return
	statement values.
	Add a single return statement at the end
	to return the cached value.
	"""
		_method.Accept(
				ReturnValueStorageVisitor(
					self.CodeBuilder.CreateReference(_returnValue)))
		_method.Body.Add(
			ReturnStatement(
				LexicalInfo: LexicalInfo,
				Expression: self.CodeBuilder.CreateReference(_returnValue)))


Include an NUnit assemblies project with the MonoDevelop solution

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

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

        slnpath = Path.Combine(asmpath, asmname + '.sln')
        using fs = StreamWriter(slnpath):
            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')

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