support for custom separators with list_of macro

namespace Boo.OMeta.Parser

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

import System.Globalization

macro infix:

	l, op, r = infix.Arguments

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

macro infixr:

	l, op, r = infixr.Arguments

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

macro prefix:

	rule, op, next = prefix.Arguments

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

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

	list_of expression

Expands to something that matches:

	expression (COMMA expression)+
"""

	if len(list_of.Arguments) == 2:
		rule, separator = list_of.Arguments
	else:
		rule, separator = list_of.Arguments[0], [| COMMA |]

	block as Block = list_of.ParentNode

	listRuleName = ReferenceExpression(Name: "${rule}_list")
	listRule = [| $listRuleName = ((($rule >> first), ++(($separator, $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:
		qq_begin = "[|"
		qq_end = "|]"
		equality = "=="
		inequality = "!="
		assign = "="
		assign_inplace = "+=" | "-=" | "*=" | "/=" | "%=" | "^=" | "&=" | "|="
		xor = "^"
		increment = "++"
		decrement = "--"
		plus = "+"
		minus = "-"
		exponentiation = "**"
		star = "*"
		division = "/"
		modulus = "%"
		ones_complement = "~"
		bitwise_shift_left = "<<"
		bitwise_shift_right = ">>"
		greater_than_eq = ">="
		greater_than = ">"
		less_than_eq = "<="
		less_than = "<"
		bitwise_and = "&"
		bitwise_or = "|"
		hexnum = ("0x", ++(hex_digit | digit) >> ds) ^ makeString(ds)
		num = ++digit
		colon = ":"
		dot = "."
		comma = ","
		lparen = "(", enterWhitespaceAgnosticRegion
		rparen = ")", leaveWhitespaceAgnosticRegion
		lbrack = "[", enterWhitespaceAgnosticRegion
		rbrack = "]", leaveWhitespaceAgnosticRegion
		lbrace = "{", enterWhitespaceAgnosticRegion
		rbrace = "}", leaveWhitespaceAgnosticRegion

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

	space = line_continuation | multi_line_comment | line_comment | super

	line_continuation = "\\", newline

	multi_line_comment = "/*", --(~"*/", (multi_line_comment | _)), "*/"

	line_comment = ('#' | "//"), --(~newline, _)

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

	keywords "class", "def", "import", "pass", "return", "true", \
		"false", "and", "or", "as", "not", "if", "is", "null", \
		"for", "interface", "internal", "in", "yield", "self", "super", "of", \
		"event", "private", "protected", "public", "enum", \
		"callable", "unless", "static", "final", "virtual", "override", "abstract", \
		"transient"

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

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

	docstring = (TDQ, ++(~tdq, string_char) >> s, TDQ, eol) ^ makeString(s)

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

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

	module_member = assembly_attribute | type_def | method

	type_def = class_def | interface_def | enum_def | callable_def

	callable_def = (CALLABLE, ID >> name, method_parameters >> parameters, optional_type >> type, eol) ^ newCallable(name, parameters, type)

	class_def = (
		attributes >> attrs,
		member_modifiers >> mod,
		CLASS, ID >> className, super_types >> superTypes, begin_block, class_body >> body, end_block
	) ^ newClass(attrs, mod, className, superTypes, body)

	interface_def = (
		attributes >> attrs,
		member_modifiers >> mod,
		INTERFACE, ID >> name, super_types >> superTypes, begin_block, interface_body >> body, end_block
	) ^ newInterface(attrs, mod, name, superTypes, body)

	enum_def = (
		attributes >> attrs,
		member_modifiers >> mod,
		ENUM, ID >> name, begin_block, enum_body >> body, end_block
	) ^ newEnum(attrs, mod, name, body)

	enum_body = ++enum_field >> fields ^ fields

	enum_field = (attributes >> attrs, ID >> name, ((ASSIGN, expression >> e) | ""), eol) ^ newEnumField(attrs, name, e)

	super_types = ((LPAREN, optional_type_reference_list >> types, RPAREN) ^ types) | ""

	begin_block = COLON, INDENT

	end_block = DEDENT

	class_body = no_member | (++class_member >> members ^ members)

	interface_body = no_member

	no_member = (PASS, eol) ^ null

	class_member = type_def | method | property_def | field | event_def

	event_def = (
		attributes >> attrs,
		member_modifiers >> mod,
		EVENT, ID >> name, optional_type >> type, eol
	) ^ newEvent(attrs, mod, name, type)

	property_def = (
		attributes >> attrs,
		member_modifiers >> mod,
		ID >> name, property_parameters >> parameters, optional_type >> type,
		begin_block,
		(
			(property_getter >> pg, property_setter >> ps)
			| (property_setter >> ps, property_getter >> pg)
			| (property_setter >> ps)
			| (property_getter >> pg)
		),
		end_block
	) ^ newProperty(attrs, mod, name, parameters, type, pg, ps)

	property_parameters = ((LBRACK, parameter_list >> parameters, RBRACK) | "") ^ parameters

	property_getter = accessor["get"]

	property_setter = accessor["set"]

	accessor[key] = (
		attributes >> attrs,
		member_modifiers >> mod,
		(ID >> name and (tokenValue(name) == key)),
		block >> body
	) ^ newMethod(attrs, mod, name, null, null, null, body)

	field = (
		attributes >> attrs,
		member_modifiers >> mod,
		ID >> name, optional_type >> type, field_initializer >> initializer, eol
	) ^ newField(attrs, mod, name, type, initializer)

	field_initializer = (ASSIGN, rvalue) | ""

	member_modifiers = --(
		(PRIVATE ^ TypeMemberModifiers.Private)
		| (PUBLIC ^ TypeMemberModifiers.Public)
		| (INTERNAL ^ TypeMemberModifiers.Internal)
		| (PROTECTED ^ TypeMemberModifiers.Protected)
		| (FINAL ^ TypeMemberModifiers.Final)
		| (STATIC ^ TypeMemberModifiers.Static)
		| (VIRTUAL ^ TypeMemberModifiers.Virtual)
		| (OVERRIDE ^ TypeMemberModifiers.Override)
		| (TRANSIENT ^ TypeMemberModifiers.Transient)
		| (ABSTRACT ^ TypeMemberModifiers.Abstract)
	) >> all ^ all

	method = (
		attributes >> attrs,
		member_modifiers >> mod, DEF, ID >> name, method_parameters >> parameters,
			attributes >> returnTypeAttributes, optional_type >> type,
			block >> body
	) ^ newMethod(attrs, mod, name, parameters, returnTypeAttributes, type, body)

	method_parameters = (LPAREN, optional_parameter_list >> parameters, RPAREN) ^ parameters

	list_of parameter

	assembly_attribute = (
		LBRACK, (ID >> name and (tokenValue(name) == "assembly")), COLON,
		attribute_list >> value,
		RBRACK, eol) ^ value

	attributes = --((LBRACK, attribute_list >> value, RBRACK, --EOL) ^ value) >> all ^ all

	attribute = (ID >> name, attribute_arguments >> args) ^ newAttribute(name, args)

	attribute_arguments = invocation_arguments | ""

	list_of attribute

	parameter = (attributes >> attrs, ID >> name, optional_type >> type) ^ newParameterDeclaration(attrs, name, type)

	optional_type = (AS, type_reference) | ""

	block = empty_block | non_empty_block

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

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

	stmt = stmt_block | stmt_line

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

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

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

	stmt_modifier = ((stmt_modifier_node >> value, eol) ^ value) | (eol ^ null)

	stmt_modifier_node = (
		stmt_modifier_type >> t,
		assignment >> e
	) ^ newStatementModifier(t, e)

	stmt_modifier_type = (IF ^ StatementModifierType.If) | (UNLESS ^ StatementModifierType.Unless)

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

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

	stmt_block = stmt_if | stmt_for

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

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

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

	optional_assignment = assignment | ""

	stmt_expression_block = (expression >> l, (ASSIGN | ASSIGN_INPLACE) >> op, block_expression >> r) ^ ExpressionStatement(newInfixExpression(op, l, r))

	block_expression = (DEF, optional_parameters >> parameters, block >> body) ^ newBlockExpression(parameters, body)

	optional_parameters = method_parameters | ("" ^ [])

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

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

	rvalue = assignment_list >> items ^ newRValue(items)

	list_of assignment

	infixr assignment, (ASSIGN | ASSIGN_INPLACE), expression

	expression = generator_expression | or_expression

	generator_expression = (
		or_expression >> projection,
		++generator_expression_body >> body
	) ^ newGeneratorExpression(projection, body)

	generator_expression_body = (FOR, declaration_list >> dl, IN, or_expression >> e, optional_filter >> f) ^ newGeneratorExpressionBody(dl, e, f)

	optional_filter = ((stmt_modifier_type >> t, or_expression >> e) ^ newStatementModifier(t, e)) | ""

	infix or_expression, OR, and_expression

	infix and_expression, AND, not_expression

	prefix not_expression, NOT, membership_expression

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

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

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

	infix bitwise_or_expression, BITWISE_OR, bitwise_xor_expression

	infix bitwise_xor_expression, XOR, bitwise_and_expression

	infix bitwise_and_expression, BITWISE_AND, bitwise_shift_expression

	infix bitwise_shift_expression, (BITWISE_SHIFT_LEFT | BITWISE_SHIFT_RIGHT), term

	infix term, (PLUS | MINUS), factor

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

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

	prefix ones_complement_expression, ONES_COMPLEMENT, exponentiation_expression

	infix exponentiation_expression, EXPONENTIATION, try_cast

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

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

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

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

	list_of expression

	list_of declaration

	list_of type_reference

	list_of slice

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

	invocation = ((member_reference >> target, invocation_arguments >> args) ^ newInvocation(target, args)) \
		| atom

	invocation_arguments = (LPAREN, optional_invocation_argument_list >> args, RPAREN) ^ args

	invocation_argument = named_argument | assignment

	list_of invocation_argument

	named_argument = (ID >> name, COLON, assignment >> value) ^ newNamedArgument(name, value)

	type_reference = type_reference_simple | type_reference_array | type_reference_callable

	type_reference_callable = (
		CALLABLE, LPAREN, optional_type_reference_list >> params, RPAREN, optional_type >> type
	) ^ newCallableTypeReference(params, type)

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

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

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

	quasi_quote = quasi_quote_member | quasi_quote_module | quasi_quote_expression | quasi_quote_stmt

	quasi_quote_module = (QQ_BEGIN, INDENT, module >> m, DEDENT, QQ_END) ^ newQuasiquoteBlock(m)

	quasi_quote_member = (QQ_BEGIN, INDENT, class_member >> m, DEDENT, QQ_END) ^ newQuasiquoteBlock(m)

	quasi_quote_expression = (QQ_BEGIN, rvalue >> s, QQ_END) ^ newQuasiquoteExpression(s)

	quasi_quote_stmt = (QQ_BEGIN, (qq_return | qq_macro) >> s, QQ_END) ^ newQuasiquoteExpression(s)

	qq_return = (RETURN, optional_assignment >> e) ^ ReturnStatement(Expression: e)

	qq_macro = (ID >> name, assignment_list >> args) ^ newMacro(name, args, null, null)

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

	null_literal = NULL ^ [| null |]

	super_literal = SUPER ^ [| super |]

	self_literal = SELF ^ [| self |]

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

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

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

	array_literal = array_literal_empty | array_literal_single | array_literal_multi

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

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

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

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

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

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

	hash_literal = (LBRACE, optional_expression_pair_list >> items, optional_comma, RBRACE) ^ newHashLiteral(items)

	optional_comma = COMMA | ""

	expression_pair = (assignment >> first, COLON, assignment >> second) ^ ExpressionPair(First: first, Second: second)

	list_of expression_pair

	reference = ID >> r ^ newReference(r)

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

	boolean = true_literal | false_literal

	true_literal = TRUE ^ [| true |]

	false_literal = FALSE ^ [| false |]

	eol = ++EOL | ~_

namespace Boo.OMeta.Parser

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

import System.Globalization

macro infix:

	l, op, r = infix.Arguments

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

macro infixr:

	l, op, r = infixr.Arguments

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

macro prefix:

	rule, op, next = prefix.Arguments

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

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

	list_of expression

Expands to something that matches:

	expression (COMMA expression)+
"""

	rule, = list_of.Arguments

	block as Block = list_of.ParentNode

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

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

ometa BooParser < WhitespaceSensitiveTokenizer:

	tokens:
		qq_begin = "[|"
		qq_end = "|]"
		equality = "=="
		inequality = "!="
		assign = "="
		assign_inplace = "+=" | "-=" | "*=" | "/=" | "%=" | "^=" | "&=" | "|="
		xor = "^"
		increment = "++"
		decrement = "--"
		plus = "+"
		minus = "-"
		exponentiation = "**"
		star = "*"
		division = "/"
		modulus = "%"
		ones_complement = "~"
		bitwise_shift_left = "<<"
		bitwise_shift_right = ">>"
		greater_than_eq = ">="
		greater_than = ">"
		less_than_eq = "<="
		less_than = "<"
		bitwise_and = "&"
		bitwise_or = "|"
		hexnum = ("0x", ++(hex_digit | digit) >> ds) ^ makeString(ds)
		num = ++digit
		colon = ":"
		dot = "."
		comma = ","
		lparen = "(", enterWhitespaceAgnosticRegion
		rparen = ")", leaveWhitespaceAgnosticRegion
		lbrack = "[", enterWhitespaceAgnosticRegion
		rbrack = "]", leaveWhitespaceAgnosticRegion
		lbrace = "{", enterWhitespaceAgnosticRegion
		rbrace = "}", leaveWhitespaceAgnosticRegion

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

	space = line_continuation | multi_line_comment | line_comment | super

	line_continuation = "\\", newline

	multi_line_comment = "/*", --(~"*/", (multi_line_comment | _)), "*/"

	line_comment = ('#' | "//"), --(~newline, _)

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

	keywords "class", "def", "import", "pass", "return", "true", \
		"false", "and", "or", "as", "not", "if", "is", "null", \
		"for", "interface", "internal", "in", "yield", "self", "super", "of", \
		"event", "private", "protected", "public", "enum", \
		"callable", "unless", "static", "final", "virtual", "override", "abstract", \
		"transient"

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

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

	docstring = (TDQ, ++(~tdq, string_char) >> s, TDQ, eol) ^ makeString(s)

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

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

	module_member = assembly_attribute | type_def | method

	type_def = class_def | interface_def | enum_def | callable_def

	callable_def = (CALLABLE, ID >> name, method_parameters >> parameters, optional_type >> type, eol) ^ newCallable(name, parameters, type)

	class_def = (
		attributes >> attrs,
		member_modifiers >> mod,
		CLASS, ID >> className, super_types >> superTypes, begin_block, class_body >> body, end_block
	) ^ newClass(attrs, mod, className, superTypes, body)

	interface_def = (
		attributes >> attrs,
		member_modifiers >> mod,
		INTERFACE, ID >> name, super_types >> superTypes, begin_block, interface_body >> body, end_block
	) ^ newInterface(attrs, mod, name, superTypes, body)

	enum_def = (
		attributes >> attrs,
		member_modifiers >> mod,
		ENUM, ID >> name, begin_block, enum_body >> body, end_block
	) ^ newEnum(attrs, mod, name, body)

	enum_body = ++enum_field >> fields ^ fields

	enum_field = (attributes >> attrs, ID >> name, ((ASSIGN, expression >> e) | ""), eol) ^ newEnumField(attrs, name, e)

	super_types = ((LPAREN, optional_type_reference_list >> types, RPAREN) ^ types) | ""

	begin_block = COLON, INDENT

	end_block = DEDENT

	class_body = no_member | (++class_member >> members ^ members)

	interface_body = no_member

	no_member = (PASS, eol) ^ null

	class_member = type_def | method | property_def | field | event_def

	event_def = (
		attributes >> attrs,
		member_modifiers >> mod,
		EVENT, ID >> name, optional_type >> type, eol
	) ^ newEvent(attrs, mod, name, type)

	property_def = (
		attributes >> attrs,
		member_modifiers >> mod,
		ID >> name, property_parameters >> parameters, optional_type >> type,
		begin_block,
		(
			(property_getter >> pg, property_setter >> ps)
			| (property_setter >> ps, property_getter >> pg)
			| (property_setter >> ps)
			| (property_getter >> pg)
		),
		end_block
	) ^ newProperty(attrs, mod, name, parameters, type, pg, ps)

	property_parameters = ((LBRACK, parameter_list >> parameters, RBRACK) | "") ^ parameters

	property_getter = accessor["get"]

	property_setter = accessor["set"]

	accessor[key] = (
		attributes >> attrs,
		member_modifiers >> mod,
		(ID >> name and (tokenValue(name) == key)),
		block >> body
	) ^ newMethod(attrs, mod, name, null, null, null, body)

	field = (
		attributes >> attrs,
		member_modifiers >> mod,
		ID >> name, optional_type >> type, field_initializer >> initializer, eol
	) ^ newField(attrs, mod, name, type, initializer)

	field_initializer = (ASSIGN, rvalue) | ""

	member_modifiers = --(
		(PRIVATE ^ TypeMemberModifiers.Private)
		| (PUBLIC ^ TypeMemberModifiers.Public)
		| (INTERNAL ^ TypeMemberModifiers.Internal)
		| (PROTECTED ^ TypeMemberModifiers.Protected)
		| (FINAL ^ TypeMemberModifiers.Final)
		| (STATIC ^ TypeMemberModifiers.Static)
		| (VIRTUAL ^ TypeMemberModifiers.Virtual)
		| (OVERRIDE ^ TypeMemberModifiers.Override)
		| (TRANSIENT ^ TypeMemberModifiers.Transient)
		| (ABSTRACT ^ TypeMemberModifiers.Abstract)
	) >> all ^ all

	method = (
		attributes >> attrs,
		member_modifiers >> mod, DEF, ID >> name, method_parameters >> parameters,
			attributes >> returnTypeAttributes, optional_type >> type,
			block >> body
	) ^ newMethod(attrs, mod, name, parameters, returnTypeAttributes, type, body)

	method_parameters = (LPAREN, optional_parameter_list >> parameters, RPAREN) ^ parameters

	list_of parameter

	assembly_attribute = (
		LBRACK, (ID >> name and (tokenValue(name) == "assembly")), COLON,
		attribute_list >> value,
		RBRACK, eol) ^ value

	attributes = --((LBRACK, attribute_list >> value, RBRACK, --EOL) ^ value) >> all ^ all

	attribute = (ID >> name, attribute_arguments >> args) ^ newAttribute(name, args)

	attribute_arguments = invocation_arguments | ""

	list_of attribute

	parameter = (attributes >> attrs, ID >> name, optional_type >> type) ^ newParameterDeclaration(attrs, name, type)

	optional_type = (AS, type_reference) | ""

	block = empty_block | non_empty_block

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

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

	stmt = stmt_block | stmt_line

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

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

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

	stmt_modifier = ((stmt_modifier_node >> value, eol) ^ value) | (eol ^ null)

	stmt_modifier_node = (
		stmt_modifier_type >> t,
		assignment >> e
	) ^ newStatementModifier(t, e)

	stmt_modifier_type = (IF ^ StatementModifierType.If) | (UNLESS ^ StatementModifierType.Unless)

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

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

	stmt_block = stmt_if | stmt_for

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

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

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

	optional_assignment = assignment | ""

	stmt_expression_block = (expression >> l, (ASSIGN | ASSIGN_INPLACE) >> op, block_expression >> r) ^ ExpressionStatement(newInfixExpression(op, l, r))

	block_expression = (DEF, optional_parameters >> parameters, block >> body) ^ newBlockExpression(parameters, body)

	optional_parameters = method_parameters | ("" ^ [])

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

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

	rvalue = assignment_list >> items ^ newRValue(items)

	list_of assignment

	infixr assignment, (ASSIGN | ASSIGN_INPLACE), expression

	expression = generator_expression | or_expression

	generator_expression = (
		or_expression >> projection,
		++generator_expression_body >> body
	) ^ newGeneratorExpression(projection, body)

	generator_expression_body = (FOR, declaration_list >> dl, IN, or_expression >> e, optional_filter >> f) ^ newGeneratorExpressionBody(dl, e, f)

	optional_filter = ((stmt_modifier_type >> t, or_expression >> e) ^ newStatementModifier(t, e)) | ""

	infix or_expression, OR, and_expression

	infix and_expression, AND, not_expression

	prefix not_expression, NOT, membership_expression

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

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

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

	infix bitwise_or_expression, BITWISE_OR, bitwise_xor_expression

	infix bitwise_xor_expression, XOR, bitwise_and_expression

	infix bitwise_and_expression, BITWISE_AND, bitwise_shift_expression

	infix bitwise_shift_expression, (BITWISE_SHIFT_LEFT | BITWISE_SHIFT_RIGHT), term

	infix term, (PLUS | MINUS), factor

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

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

	prefix ones_complement_expression, ONES_COMPLEMENT, exponentiation_expression

	infix exponentiation_expression, EXPONENTIATION, try_cast

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

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

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

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

	list_of expression

	list_of declaration

	list_of type_reference

	list_of slice

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

	invocation = ((member_reference >> target, invocation_arguments >> args) ^ newInvocation(target, args)) \
		| atom

	invocation_arguments = (LPAREN, optional_invocation_argument_list >> args, RPAREN) ^ args

	invocation_argument = named_argument | assignment

	list_of invocation_argument

	named_argument = (ID >> name, COLON, assignment >> value) ^ newNamedArgument(name, value)

	type_reference = type_reference_simple | type_reference_array | type_reference_callable

	type_reference_callable = (
		CALLABLE, LPAREN, optional_type_reference_list >> params, RPAREN, optional_type >> type
	) ^ newCallableTypeReference(params, type)

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

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

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

	quasi_quote = quasi_quote_member | quasi_quote_module | quasi_quote_expression | quasi_quote_stmt

	quasi_quote_module = (QQ_BEGIN, INDENT, module >> m, DEDENT, QQ_END) ^ newQuasiquoteBlock(m)

	quasi_quote_member = (QQ_BEGIN, INDENT, class_member >> m, DEDENT, QQ_END) ^ newQuasiquoteBlock(m)

	quasi_quote_expression = (QQ_BEGIN, rvalue >> s, QQ_END) ^ newQuasiquoteExpression(s)

	quasi_quote_stmt = (QQ_BEGIN, (qq_return | qq_macro) >> s, QQ_END) ^ newQuasiquoteExpression(s)

	qq_return = (RETURN, optional_assignment >> e) ^ ReturnStatement(Expression: e)

	qq_macro = (ID >> name, assignment_list >> args) ^ newMacro(name, args, null, null)

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

	null_literal = NULL ^ [| null |]

	super_literal = SUPER ^ [| super |]

	self_literal = SELF ^ [| self |]

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

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

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

	array_literal = array_literal_empty | array_literal_single | array_literal_multi

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

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

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

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

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

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

	hash_literal = (LBRACE, optional_expression_pair_list >> items, optional_comma, RBRACE) ^ newHashLiteral(items)

	optional_comma = COMMA | ""

	expression_pair = (assignment >> first, COLON, assignment >> second) ^ ExpressionPair(First: first, Second: second)

	list_of expression_pair

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


Support for sequences

Support for sequences

namespace BooJs.Compiler.Visitors

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

import BooJs.Compiler.Mozilla as Moz

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

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

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

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

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

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

        return obj

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

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

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

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

        Return n

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

        Return n

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

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

    def OnSelfLiteralExpression(node as SelfLiteralExpression):
        Return Moz.ThisExpression(loc: loc(node))

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

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

    def OnEnumDefinition(node as EnumDefinition):
        o = Moz.ObjectExpression(loc: loc(node))
        for member as EnumMember in node.Members:
            assert member.Initializer != null, 'Enum definition without an initializer value!'
            prop = Moz.ObjectExpressionProp(key: Moz.Literal(value: member.Name), value: Apply(member.Initializer))
            o.properties.Add(prop)

        # Assign the object to a variable
        v = Moz.VariableDeclarator(loc: loc(node))
        v.id = Moz.Identifier(loc: loc(node), name: node.FullName)
        v.init = o
        n = Moz.VariableDeclaration(loc: loc(node), kind: 'var')
        n.declarations.Add(v)

        Return n

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

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

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

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

        n['module'] = node

        Return n

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

        st as Moz.IStatement
        for member in node.Members:
            st = Apply(member)
            if st is null:
                continue
            elif st isa Moz.BlockStatement:
                n.body += (st as Moz.BlockStatement).body
            else:
                n.body.Add(st)

        Return n

    def OnField(node as Field):
        pass

    def OnConstructor(node as Constructor):
        pass

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

        b = Moz.BlockStatement()
        for local in node.Locals:
            st = Apply(local)
            b.body.Add(st) if st

        b.body += (Apply(node.Body) as Moz.BlockStatement).body
        n.body = b
        Return n

    def OnDeclarationStatement(node as DeclarationStatement):
        # TODO: Generate type annotated comments
        v = Moz.VariableDeclarator(loc: loc(node))
        v.id = Moz.Identifier(name: node.Declaration.Name)
        v.init = Apply(node.Initializer)

        vd = Moz.VariableDeclaration(loc: loc(node), kind: 'var')
        vd.declarations.Add(v)
        Return vd

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

        Return b

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

    def OnReturnStatement(node as ReturnStatement):
        n = Moz.ReturnStatement(loc: loc(node))
        n.argument = Apply(node.Expression)
        Return n

    def OnIfStatement(node as IfStatement):

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

        Return ifst

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

        Return wst

    def OnTryStatement(node as TryStatement):

        assert 1 == len(node.ExceptionHandlers), 'Multiple exceptions handlers should be processed in previous steps'

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

        hdl = node.ExceptionHandlers[0]
        h = Moz.CatchClause(loc: loc(hdl))
        h.param = Moz.Identifier(loc: loc(hdl.Declaration), name: hdl.Declaration.Name)
        h.body = Apply(hdl.Block)

        n.handlers.Add(h)

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

        Return n

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

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

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

        # In Mozilla AST the label statements are associated with an statement.
        stmts = (node.ParentNode as Block).Statements
        idx = stmts.IndexOf(node)
        st = stmts[idx + 1]
        stmts.RemoveAt(idx + 1)

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

        Return n

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

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

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

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

        Return t

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

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

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

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

        Return n

    def OnMethodInvocationExpression(node as MethodInvocationExpression):
        # Detect constructors
        if node.Target.Entity isa IConstructor:
            c = Moz.NewExpression(loc: loc(node))
            c._constructor = Apply(node.Target)
            for arg in node.Arguments:
                c.arguments.Add(Apply(arg))
            Return c
        # Detect sequences
        elif node.Target.ToString() == '@':
            s = Moz.SequenceExpression(loc: loc(node))
            for arg in node.Arguments:
                s.expressions.Add(Apply(arg))
            Return s
        else:
            n = Moz.CallExpression(loc: loc(node))
            n.callee = Apply(node.Target)
            for arg in node.Arguments:
                n.arguments.Add(Apply(arg))
            Return n

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

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

    def OnBinaryExpression(node as BinaryExpression):
        n = Moz.BinaryExpression(loc: loc(node))
        match node.Operator:
            case BinaryOperatorType.Equality:
                n.operator = '=='
            case BinaryOperatorType.Inequality:
                n.operator = '!='
            case BinaryOperatorType.ReferenceEquality:
                n.operator = '==='
            case BinaryOperatorType.ReferenceInequality:
                n.operator = '!=='
            case BinaryOperatorType.Member:
                n.operator = 'in'
            case BinaryOperatorType.Addition:
                n.operator = '+'
            case BinaryOperatorType.Subtraction:
                n.operator = '-'
            case BinaryOperatorType.Multiply:
                n.operator = '*'
            case BinaryOperatorType.Division:
                n.operator = '/'
            case BinaryOperatorType.Modulus:
                n.operator = '%'
            case BinaryOperatorType.BitwiseAnd:
                n.operator = '&'
            case BinaryOperatorType.BitwiseOr:
                n.operator = '|'
            case BinaryOperatorType.ExclusiveOr:
                n.operator = '^'
            case BinaryOperatorType.GreaterThan:
                n.operator = '>'
            case BinaryOperatorType.GreaterThanOrEqual:
                n.operator = '>='
            case BinaryOperatorType.LessThan:
                n.operator = '<'
            case BinaryOperatorType.LessThanOrEqual:
                n.operator = '<='
            case BinaryOperatorType.ShiftLeft:
                n.operator = '<<'
            case BinaryOperatorType.ShiftRight:
                n.operator = '>>'

            case BinaryOperatorType.Assign:
                n = Moz.AssignmentExpression(loc: loc(node), operator: '=')

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

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

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

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

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

namespace BooJs.Compiler.Visitors

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

import BooJs.Compiler.Mozilla as Moz

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

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

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

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

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

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

        return obj

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

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

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

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

        Return n

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

        Return n

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

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

    def OnSelfLiteralExpression(node as SelfLiteralExpression):
        Return Moz.ThisExpression(loc: loc(node))

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

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

    def OnEnumDefinition(node as EnumDefinition):
        o = Moz.ObjectExpression(loc: loc(node))
        for member as EnumMember in node.Members:
            assert member.Initializer != null, 'Enum definition without an initializer value!'
            prop = Moz.ObjectExpressionProp(key: Moz.Literal(value: member.Name), value: Apply(member.Initializer))
            o.properties.Add(prop)

        # Assign the object to a variable
        v = Moz.VariableDeclarator(loc: loc(node))
        v.id = Moz.Identifier(loc: loc(node), name: node.FullName)
        v.init = o
        n = Moz.VariableDeclaration(loc: loc(node), kind: 'var')
        n.declarations.Add(v)

        Return n

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

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

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

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

        n['module'] = node

        Return n

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

        st as Moz.IStatement
        for member in node.Members:
            st = Apply(member)
            if st is null:
                continue
            elif st isa Moz.BlockStatement:
                n.body += (st as Moz.BlockStatement).body
            else:
                n.body.Add(st)

        Return n

    def OnField(node as Field):
        pass

    def OnConstructor(node as Constructor):
        pass

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

        b = Moz.BlockStatement()
        for local in node.Locals:
            st = Apply(local)
            b.body.Add(st) if st

        b.body += (Apply(node.Body) as Moz.BlockStatement).body
        n.body = b
        Return n

    def OnDeclarationStatement(node as DeclarationStatement):
        # TODO: Generate type annotated comments
        v = Moz.VariableDeclarator(loc: loc(node))
        v.id = Moz.Identifier(name: node.Declaration.Name)
        v.init = Apply(node.Initializer)

        vd = Moz.VariableDeclaration(loc: loc(node), kind: 'var')
        vd.declarations.Add(v)
        Return vd

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

        Return b

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

    def OnReturnStatement(node as ReturnStatement):
        n = Moz.ReturnStatement(loc: loc(node))
        n.argument = Apply(node.Expression)
        Return n

    def OnIfStatement(node as IfStatement):

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

        Return ifst

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

        Return wst

    def OnTryStatement(node as TryStatement):

        assert 1 == len(node.ExceptionHandlers), 'Multiple exceptions handlers should be processed in previous steps'

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

        hdl = node.ExceptionHandlers[0]
        h = Moz.CatchClause(loc: loc(hdl))
        h.param = Moz.Identifier(loc: loc(hdl.Declaration), name: hdl.Declaration.Name)
        h.body = Apply(hdl.Block)

        n.handlers.Add(h)

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

        Return n

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

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

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

        # In Mozilla AST the label statements are associated with an statement.
        stmts = (node.ParentNode as Block).Statements
        idx = stmts.IndexOf(node)
        st = stmts[idx + 1]
        stmts.RemoveAt(idx + 1)

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

        Return n

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

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

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

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

        Return t

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

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

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

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

        Return n

    def OnMethodInvocationExpression(node as MethodInvocationExpression):
        # Detect constructors
        if node.Target.Entity isa IConstructor:
            c = Moz.NewExpression(loc: loc(node))
            c._constructor = Apply(node.Target)
            for arg in node.Arguments:
                c.arguments.Add(Apply(arg))
            Return c
        else:
            n = Moz.CallExpression(loc: loc(node))
            n.callee = Apply(node.Target)
            for arg in node.Arguments:
                n.arguments.Add(Apply(arg))
            Return n

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

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

    def OnBinaryExpression(node as BinaryExpression):
        n = Moz.BinaryExpression(loc: loc(node))
        match node.Operator:
            case BinaryOperatorType.Equality:
                n.operator = '=='
            case BinaryOperatorType.Inequality:
                n.operator = '!='
            case BinaryOperatorType.ReferenceEquality:
                n.operator = '==='
            case BinaryOperatorType.ReferenceInequality:
                n.operator = '!=='
            case BinaryOperatorType.Member:
                n.operator = 'in'
            case BinaryOperatorType.Addition:
                n.operator = '+'
            case BinaryOperatorType.Subtraction:
                n.operator = '-'
            case BinaryOperatorType.Multiply:
                n.operator = '*'
            case BinaryOperatorType.Division:
                n.operator = '/'
            case BinaryOperatorType.Modulus:
                n.operator = '%'
            case BinaryOperatorType.BitwiseAnd:
                n.operator = '&'
            case BinaryOperatorType.BitwiseOr:
                n.operator = '|'
            case BinaryOperatorType.ExclusiveOr:
                n.operator = '^'
            case BinaryOperatorType.GreaterThan:
                n.operator = '>'
            case BinaryOperatorType.GreaterThanOrEqual:
                n.operator = '>='
            case BinaryOperatorType.LessThan:
                n.operator = '<'
            case BinaryOperatorType.LessThanOrEqual:
                n.operator = '<='
            case BinaryOperatorType.ShiftLeft:
                n.operator = '<<'
            case BinaryOperatorType.ShiftRight:
                n.operator = '>>'

            case BinaryOperatorType.Assign:
                n = Moz.AssignmentExpression(loc: loc(node), operator: '=')

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

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

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

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

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


Unit tests

Unit tests

namespace Boo.Lang.PatternMatching.Tests

import NUnit.Framework(TestFixtureAttribute, TestAttribute, Assert)
import Boo.Lang.PatternMatching

class Container[of T]:
	public value as T

class Collection:
	public Items as List

[TestFixture]
class MatchMacroTest:

	[Test]
	def MemberReferenceTarget():
		expected = 42
		actual = singleItemOfContainedList(Container of Collection(value: Collection(Items: [expected])))
		Assert.AreEqual(expected, actual)

	def singleItemOfContainedList(container):
		match container:
			case Container of Collection(value.Items: (item,)):
				return item

	[Test]
	def TestEmptyArrayMatch():
		a = Collection(Items: [])
		match a:
			case Collection(Items: (,))

	[Test]
	def DoubleMatch():
		Assert.AreEqual("int int", doubleMatch(1, 2))
		Assert.AreEqual("int string", doubleMatch(1, '2'))
		Assert.AreEqual("_ string", doubleMatch(1.0, '2'))

	[Test]
	def DoubleMatchErrorIncludesBothArguments():
		try:
			doubleMatch('2', 42)
			Assert.Fail("MatchError expected")
		except e as MatchError:
			Assert.AreEqual("`a` failed to match `2` or `b` failed to match `42`", e.Message)

	def doubleMatch(a, b):
		match a, b:
			case int(), int():
				return "int int"
			case int(), string():
				return "int string"
			case _, string():
				return "_ string"

	[Test]
	def TestFixedSizeCollection():
		Assert.AreEqual(1, lastItem(Collection(Items: [1])))
		Assert.AreEqual(2, lastItem(Collection(Items: [1, 2])))

	[Test]
	def TestNestedFixedSize():
		c = Collection(Items: [Collection(Items: [1, 2])])
		Assert.AreEqual(2, nestedLastItem(c))

	[Test]
	def TestFixedSizeCollectionTyped():
		Assert.AreEqual(1, lastIntItem(Collection(Items: [1])))
		Assert.AreEqual(2, lastIntItem(Collection(Items: [1, 2])))

	[Test]
	def TestFixedSizeCollectionLastItemBeforeCatchAll():
		Assert.AreEqual(1, lastIntItemBeforeCatchAll(Collection(Items: [1, "_", "__"])))
		Assert.AreEqual(2, lastIntItemBeforeCatchAll(Collection(Items: [1, 2, "_", "__"])))

	[Test]
	def TestMatchErrorOnFixedSize():
		expectingMatchError:
			lastItem(Collection(Items: [1, 2, 3]))

	[Test]
	def TestGenericMatch():
		Assert.AreEqual(42, genericMatch(Container of int(value: 21)))
		Assert.AreEqual("BOO", genericMatch(Container of string(value: 'boo')))

	[Test]
	def TestPropertyPattern():
		Assert.AreEqual("item foo", itemByName(Item(Name: "foo")))
		Assert.AreEqual("not foo", itemByName(Item(Name: "not foo")))

	[Test]
	def TestNestedPropertyPattern():
		Assert.AreEqual("foo:bar", nestedByName(
								Item(Name: "foo",
									Child: Item(Name: "bar"))))

	[Test]
	def TestStringValue():
		for s in ("foo", "bar"):
			Assert.AreEqual("*${s}*", matchStringValue(s))

	[Test]
	def TestQualifiedReference():
		Assert.AreEqual("default item", itemByQualifiedReference(Item.Default))
		Assert.AreEqual("foo", itemByQualifiedReference(Item(Name: "foo")))

	[Test]
	def TestImplicitPropertyPattern():
		Assert.AreEqual("FOO", itemByImplicitNameReference(Item(Name: "foo")))

	[Test]
	def TestMatchErrorOnPropertyPattern():
		expectingMatchError:
			itemByName(42)

	[Test]
	def TestMatchErrorMessageIncludesValue():
		try:
			itemByName(42)
		except e as MatchError:
			Assert.AreEqual("`o` failed to match `42`", e.Message)

	[Test]
	def TestMatchErrorOnNestedPropertyPattern():
		expectingMatchError:
			nestedByName(42)

	[Test]
	def TestCaseCapture():
		Assert.AreEqual("foo", caseCapture(Item(Name: "foo")))
		Assert.AreEqual(42, caseCapture(21))

	[Test]
	def TestOtherwise():
		Assert.AreEqual("int", matchIntOtherwise(42))
		Assert.AreEqual("otherwise", matchIntOtherwise("42"))

	[Test]
	def TestEitherPattern():
		Assert.AreEqual("yes", either(42))
		Assert.AreEqual("yes", either(-1))
		Assert.AreEqual("no", either(0))

	def either(value as int):
		match value:
			case 42 | -1:
				return "yes"
			otherwise:
				return "no"

	[Test]
	def TestConstrainedOrPattern():
		Assert.AreEqual("yes", constrainedOr(42, 2))
		Assert.AreEqual("yes", constrainedOr(-1, 2))
		Assert.AreEqual("yes", constrainedOr(42, 42))
		Assert.AreEqual("no", constrainedOr(-1, 42))

	def constrainedOr(value1 as int, value2 as int):
		match value1:
			case 42 or value2 < 23:
				return "yes"
			otherwise:
				return "no"

	[Test]
	def TestConstrainedAndPattern():
		Assert.AreEqual("yes", constrainedAnd(42, 2))
		Assert.AreEqual("no", constrainedAnd(-1, 2))
		Assert.AreEqual("no", constrainedAnd(42, 42))
		Assert.AreEqual("no", constrainedAnd(-1, 42))

	def constrainedAnd(value1 as int, value2 as int):
		match value1:
			case 42 and value2 < 23:
				return "yes"
			otherwise:
				return "no"

	[Test]
	def TestBothPattern():
		Assert.AreEqual("yes", both(Item(Name: "foo")))
		Assert.AreEqual("yes", both(Item(Name: "foo", Child: null)))
		Assert.AreEqual("no", both(Item(Name: "foo", Child: Item(Name: "bar"))))
		Assert.AreEqual("no", both(Item(Name: "bar", Child: null)))

	def both(value as Item):
		match value:
			case Item(Child: null) & Item(Name: "foo"):
				return "yes"
			otherwise:
				return "no"

	enum Foo:
		None
		Bar
		Baz

	[Test]
	def TestMemberReferenceIsTreatedAsValueComparison():
		Assert.AreEqual("Bar", matchMemberRef(Foo.Bar))
		Assert.AreEqual("Baz", matchMemberRef(Foo.Baz))

	[Test]
	def TestMatchErrorOnMemberReferencePattern():
		expectingMatchError:
			matchMemberRef(Foo.None)

	[Test]
	def TestMatchArrayInvocation():
		Assert.AreEqual(3, MatchArray(1,10,3))
		Assert.AreEqual(true, MatchEmptyArray())
		Assert.AreEqual('bar', MatchStarArray('foo', 'bar', 'baz'))

	def MatchArray(*value):
		match value:
			case array(_,_,a):
				return a

	def MatchEmptyArray(*value):
		match value:
			case array():
				return true

	def MatchStarArray(*value):
		match value:
			case array('foo', a, *_):
				return a

	def itemByName(o):
		match o:
			case Item(Name: "foo"):
				return "item foo"
			case Item(Name: name):
				return name

	def nestedByName(o):
		match o:
			case Item(Name: outer, Child: Item(Name: inner)):
				return "${outer}:${inner}"

	def itemByQualifiedReference(o):
		match o:
			case Item(Name: Item.Default.Name):
				return "default item"
			case Item(Name: name):
				return name

	def itemByImplicitNameReference(o):
		match o:
			case Item(Name):
				return Name.ToUpper()

	def caseCapture(o):
		match o:
			case i = int():
				return i*2
			case item = Item():
				return item.Name

	def matchMemberRef(o as Foo):
		match o:
			case Foo.Bar:
				return "Bar"
			case Foo.Baz:
				return "Baz"

	def matchIntOtherwise(o):
		match o:
			case int():
				return "int"
			otherwise:
				return "otherwise"

	def matchStringValue(o):
		match o:
			case "foo":
				return "*foo*"
			case "bar":
				return "*bar*"

	def genericMatch(o):
		match o:
			case Container[of int](value: i):
				return i*2
			case Container[of string](value: s):
				return s.ToUpper()

	def lastItem(o):
		match o:
			case Collection(Items: (last,)):
				return last
			case Collection(Items: (_, last)):
				return last

	def nestedLastItem(o):
		match o:
			case Collection(Items: (Collection(Items: (_, last)),)):
				return last

	def lastIntItem(o):
		match o:
			case Collection(Items: (last = int(),)):
				return last
			case Collection(Items: (_ = int(), last = int(),)):
				return last

	def lastIntItemBeforeCatchAll(o):
		match o:
			case Collection(Items: (_ = int(), last = int(), *_)):
				return last
			case Collection(Items: (last = int(), *_)):
				return last
		return -1

namespace Boo.Lang.PatternMatching.Tests

import NUnit.Framework(TestFixtureAttribute, TestAttribute, Assert)
import Boo.Lang.PatternMatching

class Container[of T]:
	public value as T

class Collection:
	public Items as List

[TestFixture]
class MatchMacroTest:

	[Test]
	def MemberReferenceTarget():
		expected = 42
		actual = singleItemOfContainedList(Container of Collection(value: Collection(Items: [expected])))
		Assert.AreEqual(expected, actual)

	def singleItemOfContainedList(container):
		match container:
			case Container of Collection(value.Items: (item,)):
				return item

	[Test]
	def TestEmptyArrayMatch():
		a = Collection(Items: [])
		match a:
			case Collection(Items: (,))

	[Test]
	def DoubleMatch():
		Assert.AreEqual("int int", doubleMatch(1, 2))
		Assert.AreEqual("int string", doubleMatch(1, '2'))
		Assert.AreEqual("_ string", doubleMatch(1.0, '2'))

	[Test]
	def DoubleMatchErrorIncludesBothArguments():
		try:
			doubleMatch('2', 42)
			Assert.Fail("MatchError expected")
		except e as MatchError:
			Assert.AreEqual("`a` failed to match `2` or `b` failed to match `42`", e.Message)

	def doubleMatch(a, b):
		match a, b:
			case int(), int():
				return "int int"
			case int(), string():
				return "int string"
			case _, string():
				return "_ string"

	[Test]
	def TestFixedSizeCollection():
		Assert.AreEqual(1, lastItem(Collection(Items: [1])))
		Assert.AreEqual(2, lastItem(Collection(Items: [1, 2])))

	[Test]
	def TestNestedFixedSize():
		c = Collection(Items: [Collection(Items: [1, 2])])
		Assert.AreEqual(2, nestedLastItem(c))

	[Test]
	def TestFixedSizeCollectionTyped():
		Assert.AreEqual(1, lastIntItem(Collection(Items: [1])))
		Assert.AreEqual(2, lastIntItem(Collection(Items: [1, 2])))

	[Test]
	def TestFixedSizeCollectionLastItemBeforeCatchAll():
		Assert.AreEqual(1, lastIntItemBeforeCatchAll(Collection(Items: [1, "_", "__"])))
		Assert.AreEqual(2, lastIntItemBeforeCatchAll(Collection(Items: [1, 2, "_", "__"])))

	[Test]
	def TestMatchErrorOnFixedSize():
		expectingMatchError:
			lastItem(Collection(Items: [1, 2, 3]))

	[Test]
	def TestGenericMatch():
		Assert.AreEqual(42, genericMatch(Container of int(value: 21)))
		Assert.AreEqual("BOO", genericMatch(Container of string(value: 'boo')))

	[Test]
	def TestPropertyPattern():
		Assert.AreEqual("item foo", itemByName(Item(Name: "foo")))
		Assert.AreEqual("not foo", itemByName(Item(Name: "not foo")))

	[Test]
	def TestNestedPropertyPattern():
		Assert.AreEqual("foo:bar", nestedByName(
								Item(Name: "foo",
									Child: Item(Name: "bar"))))

	[Test]
	def TestStringValue():
		for s in ("foo", "bar"):
			Assert.AreEqual("*${s}*", matchStringValue(s))

	[Test]
	def TestQualifiedReference():
		Assert.AreEqual("default item", itemByQualifiedReference(Item.Default))
		Assert.AreEqual("foo", itemByQualifiedReference(Item(Name: "foo")))

	[Test]
	def TestImplicitPropertyPattern():
		Assert.AreEqual("FOO", itemByImplicitNameReference(Item(Name: "foo")))

	[Test]
	def TestMatchErrorOnPropertyPattern():
		expectingMatchError:
			itemByName(42)

	[Test]
	def TestMatchErrorMessageIncludesValue():
		try:
			itemByName(42)
		except e as MatchError:
			Assert.AreEqual("`o` failed to match `42`", e.Message)

	[Test]
	def TestMatchErrorOnNestedPropertyPattern():
		expectingMatchError:
			nestedByName(42)

	[Test]
	def TestCaseCapture():
		Assert.AreEqual("foo", caseCapture(Item(Name: "foo")))
		Assert.AreEqual(42, caseCapture(21))

	[Test]
	def TestOtherwise():
		Assert.AreEqual("int", matchIntOtherwise(42))
		Assert.AreEqual("otherwise", matchIntOtherwise("42"))

	[Test]
	def TestEitherPattern():
		Assert.AreEqual("yes", either(42))
		Assert.AreEqual("yes", either(-1))
		Assert.AreEqual("no", either(0))

	def either(value as int):
		match value:
			case 42 | -1:
				return "yes"
			otherwise:
				return "no"

	[Test]
	def TestConstrainedOrPattern():
		Assert.AreEqual("yes", constrainedOr(42, 2))
		Assert.AreEqual("yes", constrainedOr(-1, 2))
		Assert.AreEqual("yes", constrainedOr(42, 42))
		Assert.AreEqual("no", constrainedOr(-1, 42))

	def constrainedOr(value1 as int, value2 as int):
		match value1:
			case 42 or value2 < 23:
				return "yes"
			otherwise:
				return "no"

	[Test]
	def TestConstrainedAndPattern():
		Assert.AreEqual("yes", constrainedAnd(42, 2))
		Assert.AreEqual("no", constrainedAnd(-1, 2))
		Assert.AreEqual("no", constrainedAnd(42, 42))
		Assert.AreEqual("no", constrainedAnd(-1, 42))

	def constrainedAnd(value1 as int, value2 as int):
		match value1:
			case 42 and value2 < 23:
				return "yes"
			otherwise:
				return "no"

	[Test]
	def TestBothPattern():
		Assert.AreEqual("yes", both(Item(Name: "foo")))
		Assert.AreEqual("yes", both(Item(Name: "foo", Child: null)))
		Assert.AreEqual("no", both(Item(Name: "foo", Child: Item(Name: "bar"))))
		Assert.AreEqual("no", both(Item(Name: "bar", Child: null)))

	def both(value as Item):
		match value:
			case Item(Child: null) & Item(Name: "foo"):
				return "yes"
			otherwise:
				return "no"

	enum Foo:
		None
		Bar
		Baz

	[Test]
	def TestMemberReferenceIsTreatedAsValueComparison():
		Assert.AreEqual("Bar", matchMemberRef(Foo.Bar))
		Assert.AreEqual("Baz", matchMemberRef(Foo.Baz))

	[Test]
	def TestMatchErrorOnMemberReferencePattern():
		expectingMatchError:
			matchMemberRef(Foo.None)

	def itemByName(o):
		match o:
			case Item(Name: "foo"):
				return "item foo"
			case Item(Name: name):
				return name

	def nestedByName(o):
		match o:
			case Item(Name: outer, Child: Item(Name: inner)):
				return "${outer}:${inner}"

	def itemByQualifiedReference(o):
		match o:
			case Item(Name: Item.Default.Name):
				return "default item"
			case Item(Name: name):
				return name

	def itemByImplicitNameReference(o):
		match o:
			case Item(Name):
				return Name.ToUpper()

	def caseCapture(o):
		match o:
			case i = int():
				return i*2
			case item = Item():
				return item.Name

	def matchMemberRef(o as Foo):
		match o:
			case Foo.Bar:
				return "Bar"
			case Foo.Baz:
				return "Baz"

	def matchIntOtherwise(o):
		match o:
			case int():
				return "int"
			otherwise:
				return "otherwise"

	def matchStringValue(o):
		match o:
			case "foo":
				return "*foo*"
			case "bar":
				return "*bar*"

	def genericMatch(o):
		match o:
			case Container[of int](value: i):
				return i*2
			case Container[of string](value: s):
				return s.ToUpper()

	def lastItem(o):
		match o:
			case Collection(Items: (last,)):
				return last
			case Collection(Items: (_, last)):
				return last

	def nestedLastItem(o):
		match o:
			case Collection(Items: (Collection(Items: (_, last)),)):
				return last

	def lastIntItem(o):
		match o:
			case Collection(Items: (last = int(),)):
				return last
			case Collection(Items: (_ = int(), last = int(),)):
				return last

	def lastIntItemBeforeCatchAll(o):
		match o:
			case Collection(Items: (_ = int(), last = int(), *_)):
				return last
			case Collection(Items: (last = int(), *_)):
				return last
		return -1


Made the fix more "booish" :-)

Made the fix more "booish" :-)

namespace UnityScript.Tests

import System
import NUnit.Framework
import Boo.Lang.Useful

[TestFixture]
class UsTestFixture:

	[Test]
	def NoWarn():
		argv = (
			"-r:${GetAssemblyLocation()}",
			"-b:UnityScript.Tests.MonoBehaviour",
			"-m:Awake",
			"-nowarn:BCW0016",
			ResolvePath("warnings-1.js"), )
		AssertSuccessfulCompilation(argv)

	[Test]
	def ExeTarget():
		argv = (
			"-r:${GetAssemblyLocation()}",
			"-b:UnityScript.Tests.MonoBehaviour",
			"-m:Awake",
			"-target:exe",
			ResolvePath("target-exe.js"), )
		AssertSuccessfulCompilation(argv)

	[Test]
	def Pragmas():
		argv = (
			"-r:${GetAssemblyLocation()}",
			"-b:UnityScript.Tests.MonoBehaviour",
			"-m:Awake",
			"-pragmas:strict,implicit",
			ResolvePath("pragmas-1.js"), )
		AssertSuccessfulCompilation(argv)

	[Test]
	def WarningsAndErrorsReportCorrectFileNames():
		argv = (
			"-r:${GetAssemblyLocation()}",
			"-b:UnityScript.Tests.MonoBehaviour",
			"-m:Awake",
			ResolvePath("errors-1.js"), )
		AssertUnknownIdentifierErrorMessageAndExitCode(argv)

	[Test]
	def AcceptsMultipleReferences():
		argv = (
			"-r:${GetAssemblyLocation()}",
			"-r:${GetAssemblyLocation()}",
			"-b:UnityScript.Tests.MonoBehaviour",
			"-m:Awake",
			ResolvePath("errors-1.js"), )
		AssertUnknownIdentifierErrorMessageAndExitCode(argv)

	[Test]
	def EvalCanBeDisabled():
		message = "eval is not available on this platform"
		argv = (
			"-r:${GetAssemblyLocation()}",
			"-b:UnityScript.Tests.MonoBehaviour",
			"-m:Awake",
			'"-disable-eval:' + message + '"',
			path = ResolvePath("eval-can-be-disabled.js"))

		expected = "$path(2,2): UCE0008: $message"
		AssertErrorMessageAndExitCode(expected, argv)

	def ResolvePath(fname as string):
		return "${AbstractCompilerTestFixture.BasePath}/tests/us/${fname}"

	def AssertUnknownIdentifierErrorMessageAndExitCode(argv as (string)):
		expected = """
		${ResolvePath('errors-1.js')}(2,4): BCE0005: Unknown identifier: 'undefinedNoPathNameError'.
		"""
		AssertErrorMessageAndExitCode(expected, argv)

	def AssertErrorMessageAndExitCode(expected as string, argv as (string)):
		exitCode, output as string = ExecuteUs(argv)
		Assert.AreEqual(255, exitCode)
		Assert.AreEqual(expected.Trim(), output.Trim())

	def AssertSuccessfulCompilation(argv as (string)):
		exitCode, output as string = ExecuteUs(argv)
		Assert.AreEqual(0, exitCode, output)
		assert output.Trim().StartsWith("Successfully compiled")

	def ExecuteUs(argv as (string)):
		if PlatformInformation.IsMono:
			monoexe = System.Environment.GetEnvironmentVariable ("MONO_EXECUTABLE") or "mono"
			p = shellp(monoexe, UsExePath + " ${join(argv)}")
		else:
			p = shellp(UsExePath, join(argv))

		output = p.StandardOutput.ReadToEnd()
		p.WaitForExit()
		return p.ExitCode, output

	static def shellp(filename as string, arguments as string):
		p = System.Diagnostics.Process()
		p.StartInfo.Arguments = arguments
		p.StartInfo.CreateNoWindow = true
		p.StartInfo.UseShellExecute = false
		p.StartInfo.RedirectStandardOutput = true
		p.StartInfo.RedirectStandardInput = true
		p.StartInfo.RedirectStandardError = true
		p.StartInfo.FileName = filename
		p.Start()
		return p

	UsExePath:
		get: return typeof(us.CommandLineOptions).Module.FullyQualifiedName

namespace UnityScript.Tests

import System
import NUnit.Framework
import Boo.Lang.Useful

[TestFixture]
class UsTestFixture:

	[Test]
	def NoWarn():
		argv = (
			"-r:${GetAssemblyLocation()}",
			"-b:UnityScript.Tests.MonoBehaviour",
			"-m:Awake",
			"-nowarn:BCW0016",
			ResolvePath("warnings-1.js"), )
		AssertSuccessfulCompilation(argv)

	[Test]
	def ExeTarget():
		argv = (
			"-r:${GetAssemblyLocation()}",
			"-b:UnityScript.Tests.MonoBehaviour",
			"-m:Awake",
			"-target:exe",
			ResolvePath("target-exe.js"), )
		AssertSuccessfulCompilation(argv)

	[Test]
	def Pragmas():
		argv = (
			"-r:${GetAssemblyLocation()}",
			"-b:UnityScript.Tests.MonoBehaviour",
			"-m:Awake",
			"-pragmas:strict,implicit",
			ResolvePath("pragmas-1.js"), )
		AssertSuccessfulCompilation(argv)

	[Test]
	def WarningsAndErrorsReportCorrectFileNames():
		argv = (
			"-r:${GetAssemblyLocation()}",
			"-b:UnityScript.Tests.MonoBehaviour",
			"-m:Awake",
			ResolvePath("errors-1.js"), )
		AssertUnknownIdentifierErrorMessageAndExitCode(argv)

	[Test]
	def AcceptsMultipleReferences():
		argv = (
			"-r:${GetAssemblyLocation()}",
			"-r:${GetAssemblyLocation()}",
			"-b:UnityScript.Tests.MonoBehaviour",
			"-m:Awake",
			ResolvePath("errors-1.js"), )
		AssertUnknownIdentifierErrorMessageAndExitCode(argv)

	[Test]
	def EvalCanBeDisabled():
		message = "eval is not available on this platform"
		argv = (
			"-r:${GetAssemblyLocation()}",
			"-b:UnityScript.Tests.MonoBehaviour",
			"-m:Awake",
			'"-disable-eval:' + message + '"',
			path = ResolvePath("eval-can-be-disabled.js"))

		expected = "$path(2,2): UCE0008: $message"
		AssertErrorMessageAndExitCode(expected, argv)

	def ResolvePath(fname as string):
		return "${AbstractCompilerTestFixture.BasePath}/tests/us/${fname}"

	def AssertUnknownIdentifierErrorMessageAndExitCode(argv as (string)):
		expected = """
		${ResolvePath('errors-1.js')}(2,4): BCE0005: Unknown identifier: 'undefinedNoPathNameError'.
		"""
		AssertErrorMessageAndExitCode(expected, argv)

	def AssertErrorMessageAndExitCode(expected as string, argv as (string)):
		exitCode, output as string = ExecuteUs(argv)
		Assert.AreEqual(255, exitCode)
		Assert.AreEqual(expected.Trim(), output.Trim())

	def AssertSuccessfulCompilation(argv as (string)):
		exitCode, output as string = ExecuteUs(argv)
		Assert.AreEqual(0, exitCode, output)
		assert output.Trim().StartsWith("Successfully compiled")

	def ExecuteUs(argv as (string)):
		if PlatformInformation.IsMono:
			monoexe = System.Environment.GetEnvironmentVariable ("MONO_EXECUTABLE")
			if (monoexe == null):
				monoexe = "mono"
			p = shellp(monoexe, UsExePath + " ${join(argv)}")
		else:
			p = shellp(UsExePath, join(argv))

		output = p.StandardOutput.ReadToEnd()
		p.WaitForExit()
		return p.ExitCode, output

	static def shellp(filename as string, arguments as string):
		p = System.Diagnostics.Process()
		p.StartInfo.Arguments = arguments
		p.StartInfo.CreateNoWindow = true
		p.StartInfo.UseShellExecute = false
		p.StartInfo.RedirectStandardOutput = true
		p.StartInfo.RedirectStandardInput = true
		p.StartInfo.RedirectStandardError = true
		p.StartInfo.FileName = filename
		p.Start()
		return p

	UsExePath:
		get: return typeof(us.CommandLineOptions).Module.FullyQualifiedName


add Extension attribute to Boo.Lang.Extensions so gmcs can treat EnvironmentExtensions methods as extension methods

#region license
// Copyright (c) 2009 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.Environments

import System.Runtime.CompilerServices

[Extension]
[CompilerGlobalScope]
static class EnvironmentExtensions:

	[Extension] def Run(this as IEnvironment, action as System.Action):
	"""
	Runs the given action in this environment.
	"""
		ActiveEnvironment.With(this, { action() })

	[Extension] def Invoke[of TResult](this as IEnvironment, function as System.Func of TResult):
	"""
	Invokes the given function in this environment and returns the result.
	"""
		invoker = FunctionInvoker of TResult(Function: function)
		ActiveEnvironment.With(this, invoker.Action)
		return invoker.Result

	internal class FunctionInvoker[of TResult]:
	"""workaround for compiler not supporting closures capturing generic parameters"""
		public Function as System.Func of TResult
		public Result as TResult
		def Action():
			Result = Function()

[assembly: Extension]


fixed test case for .net 1.1

fixed test case for .net 1.1

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

"""
foo called
"""
import System
import System.IO
import System.Reflection
import System.Reflection.Emit
import Boo.Lang.Compiler
import Boo.Lang.Compiler.IO

def createUIntPtrDelegateAssembly():
	name = AssemblyName(Name: "UIntPtrDelegateAssembly")
	assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Save, Path.GetTempPath())
	module = assembly.DefineDynamicModule("UIntPtrDelegateAssembly.dll")

	t = module.DefineType("UIntPtrDelegate", TypeAttributes.Public|TypeAttributes.Sealed, MulticastDelegate)
	ctor = t.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, (object, System.UIntPtr))
	ctor.SetImplementationFlags(MethodImplAttributes.Runtime|MethodImplAttributes.Managed)

	invoke = t.DefineMethod("Invoke", MethodAttributes.Public, CallingConventions.HasThis, void, array(Type, 0))
	invoke.SetImplementationFlags(MethodImplAttributes.Runtime|MethodImplAttributes.Managed)

	t.CreateType()
	assembly.Save("UIntPtrDelegateAssembly.dll")
	return Assembly.LoadFrom(module.FullyQualifiedName)

code = """
def foo():
	print 'foo called'

d as UIntPtrDelegate = foo
d()
"""

compiler = BooCompiler()
compiler.Parameters.References.Add(createUIntPtrDelegateAssembly())
compiler.Parameters.Input.Add(StringInput("test", code))
compiler.Parameters.Pipeline = Pipelines.CompileToMemory()
compiler.Parameters.GenerateInMemory = true
result = compiler.Run()
assert 0 == len(result.Errors), result.Errors.ToString(false)

result.GeneratedAssembly.EntryPoint.Invoke(null, (array(string,0),))

"""
foo called
"""
import System
import System.IO
import System.Reflection
import System.Reflection.Emit
import Boo.Lang.Compiler
import Boo.Lang.Compiler.IO

def createUIntPtrDelegateAssembly():
	name = AssemblyName("UIntPtrDelegateAssembly")
	assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Save, Path.GetTempPath())
	module = assembly.DefineDynamicModule("UIntPtrDelegateAssembly.dll")

	t = module.DefineType("UIntPtrDelegate", TypeAttributes.Public|TypeAttributes.Sealed, MulticastDelegate)
	ctor = t.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, (object, System.UIntPtr))
	ctor.SetImplementationFlags(MethodImplAttributes.Runtime|MethodImplAttributes.Managed)

	invoke = t.DefineMethod("Invoke", MethodAttributes.Public, CallingConventions.HasThis, void, array(Type, 0))
	invoke.SetImplementationFlags(MethodImplAttributes.Runtime|MethodImplAttributes.Managed)

	t.CreateType()
	assembly.Save("UIntPtrDelegateAssembly.dll")
	return Assembly.LoadFrom(module.FullyQualifiedName)

code = """
def foo():
	print 'foo called'

d as UIntPtrDelegate = foo
d()
"""

compiler = BooCompiler()
compiler.Parameters.References.Add(createUIntPtrDelegateAssembly())
compiler.Parameters.Input.Add(StringInput("test", code))
compiler.Parameters.Pipeline = Pipelines.CompileToMemory()
compiler.Parameters.GenerateInMemory = true
result = compiler.Run()
assert 0 == len(result.Errors), result.Errors.ToString(false)

result.GeneratedAssembly.EntryPoint.Invoke(null, (array(string,0),))


use System.Drawing.Rectangle instead of System.Drawing.Point because the latter conflicts with BooCompiler.Tests:System.Drawing.Point which is used to test assembly qualified imports

"""
System.Text.StringBuilder
System.Drawing.Rectangle
"""
from System.Text import *
from System import Console as Cons
from System.Drawing import Rectangle

print StringBuilder().GetType()
Cons.Write(Rectangle(0, 0, 1, 1).GetType())

"""
System.Text.StringBuilder
System.Drawing.Point
"""
from System.Text import *
from System import Console as Cons
from System.Drawing import Point

print StringBuilder().GetType()
Cons.Write(Point(0, 0).GetType())


any rule

any rule

namespace Boo.OMeta

import Boo.Adt
import System.Collections.Generic

data OMetaMatch(Input as OMetaInput) = SuccessfulMatch(Value as object) | FailedMatch()

callable OMetaRule(context as OMetaGrammar, input as OMetaInput) as OMetaMatch

interface OMetaGrammar:

	def InstallRule(ruleName as string, rule as OMetaRule)
	def Apply(context as OMetaGrammar, rule as string, input as OMetaInput) as OMetaMatch
	def SuperApply(context as OMetaGrammar, rule as string, input as OMetaInput) as OMetaMatch

class OMetaGrammarRoot(OMetaGrammar):

	class MemoKey:
		final _rule as string
		final _input as OMetaInput

		def constructor(rule as string, input as OMetaInput):
			_rule = rule
			_input = input

		override def Equals(o):
			other as MemoKey = o
			return _input is other._input and _rule is other._rule

		override def GetHashCode():
			return _rule.GetHashCode() ^ _input.GetHashCode()

	_rules = Dictionary[of string, OMetaRule]()
	_memo = Dictionary[of MemoKey, OMetaMatch]()

	def InstallRule(ruleName as string, rule as OMetaRule):
		_rules[ruleName] = rule

	def Apply(context as OMetaGrammar, rule as string, input as OMetaInput):

		key = MemoKey(rule, input)
		memo as OMetaMatch
		if _memo.TryGetValue(key, memo):
			return memo

		_memo[key] = FailedMatch(input)
		memo = Eval(context, rule, input)
		_memo[key] = memo
		return memo

	def Eval(context as OMetaGrammar, rule as string, input as OMetaInput):
		found as OMetaRule
		if _rules.TryGetValue(rule, found):
			return found(context, input)
		return RuleMissing(context, rule, input)

	def SuperApply(context as OMetaGrammar, rule as string, input as OMetaInput):
		return Apply(context, rule, input)

	virtual def RuleMissing(context as OMetaGrammar, rule as string, input as OMetaInput) as OMetaMatch:
		raise "Rule '${rule}' missing!"

class OMetaGrammarPrototype(OMetaGrammarRoot):

	def constructor():
		InstallRule("whitespace", makeRule(char.IsWhiteSpace))
		InstallRule("letter", makeRule(char.IsLetter))
		InstallRule("digit", makeRule(char.IsDigit))
		InstallRule("_", makeRule({ o | return true }))

class OMetaDelegatingGrammar(OMetaGrammarRoot):

	_prototype as OMetaGrammar

	def constructor(prototype as OMetaGrammar):
		_prototype = prototype

	override def RuleMissing(context as OMetaGrammar, rule as string, input as OMetaInput):
		return SuperApply(context, rule, input)

	override def SuperApply(context as OMetaGrammar, rule as string, input as OMetaInput):
		return _prototype.SuperApply(context, rule, input)

def makeRule(predicate as System.Predicate[of object]) as OMetaRule:
	def rule(context as OMetaGrammar, input as OMetaInput) as OMetaMatch:
		if not input.IsEmpty and predicate(input.Head):
			return SuccessfulMatch(input.Tail, input.Head)
		return FailedMatch(input)
	return rule

namespace Boo.OMeta

import Boo.Adt
import System.Collections.Generic

data OMetaMatch(Input as OMetaInput) = SuccessfulMatch(Value as object) | FailedMatch()

callable OMetaRule(context as OMetaGrammar, input as OMetaInput) as OMetaMatch

interface OMetaGrammar:

	def InstallRule(ruleName as string, rule as OMetaRule)
	def Apply(context as OMetaGrammar, rule as string, input as OMetaInput) as OMetaMatch
	def SuperApply(context as OMetaGrammar, rule as string, input as OMetaInput) as OMetaMatch

class OMetaGrammarRoot(OMetaGrammar):

	class MemoKey:
		final _rule as string
		final _input as OMetaInput

		def constructor(rule as string, input as OMetaInput):
			_rule = rule
			_input = input

		override def Equals(o):
			other as MemoKey = o
			return _input is other._input and _rule is other._rule

		override def GetHashCode():
			return _rule.GetHashCode() ^ _input.GetHashCode()

	_rules = Dictionary[of string, OMetaRule]()
	_memo = Dictionary[of MemoKey, OMetaMatch]()

	def InstallRule(ruleName as string, rule as OMetaRule):
		_rules[ruleName] = rule

	def Apply(context as OMetaGrammar, rule as string, input as OMetaInput):

		key = MemoKey(rule, input)
		memo as OMetaMatch
		if _memo.TryGetValue(key, memo):
			return memo

		_memo[key] = FailedMatch(input)
		memo = Eval(context, rule, input)
		_memo[key] = memo
		return memo

	def Eval(context as OMetaGrammar, rule as string, input as OMetaInput):
		found as OMetaRule
		if _rules.TryGetValue(rule, found):
			return found(context, input)
		return RuleMissing(context, rule, input)

	def SuperApply(context as OMetaGrammar, rule as string, input as OMetaInput):
		return Apply(context, rule, input)

	virtual def RuleMissing(context as OMetaGrammar, rule as string, input as OMetaInput) as OMetaMatch:
		raise "Rule '${rule}' missing!"

class OMetaGrammarPrototype(OMetaGrammarRoot):

	def constructor():
		InstallRule("whitespace", makeRule(char.IsWhiteSpace))
		InstallRule("letter", makeRule(char.IsLetter))
		InstallRule("digit", makeRule(char.IsDigit))

class OMetaDelegatingGrammar(OMetaGrammarRoot):

	_prototype as OMetaGrammar

	def constructor(prototype as OMetaGrammar):
		_prototype = prototype

	override def RuleMissing(context as OMetaGrammar, rule as string, input as OMetaInput):
		return SuperApply(context, rule, input)

	override def SuperApply(context as OMetaGrammar, rule as string, input as OMetaInput):
		return _prototype.SuperApply(context, rule, input)

def makeRule(predicate as System.Predicate[of object]) as OMetaRule:
	def rule(context as OMetaGrammar, input as OMetaInput) as OMetaMatch:
		if not input.IsEmpty and predicate(input.Head):
			return SuccessfulMatch(input.Tail, input.Head)
		return FailedMatch(input)
	return rule