Fixing infinite loop of -- operator.

Fixing infinite loop of -- operator.

namespace Boo.OMeta

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

class OMetaMacroRuleProcessor:

	_ruleName as string
	_collectingParseTree as DynamicVariable[of bool]
	_optionParseTree as bool

	def constructor(ruleName as string, options as List):
		_ruleName = ruleName
		_optionParseTree = "ParseTree" in options
		_collectingParseTree = DynamicVariable[of bool](_optionParseTree)

	def expand(e as Expression, *args as (Expression)) as Block:

		input = [| input_ |]
		block = Block(LexicalInfo: e.LexicalInfo)
		for arg in args:
			code = [|
				block:
					lastMatch = any($input)
					smatch = lastMatch as SuccessfulMatch
					if smatch is null: return lastMatch
					$input = smatch.Input
					$arg = smatch.Value
			|].Body
			block.Add(code)

		expand block, e, input, [| lastMatch |]
		block.Add([| return lastMatch |])

		return block

	def expand(e as Expression, input as Expression, lastMatch as ReferenceExpression) as Block:
		block = Block()
		expand block, e, input, lastMatch
		return block

	def expandChoices(block as Block, choices as List, input as Expression, lastMatch as ReferenceExpression):
		currentBlock = block

		oldInput = uniqueName()
		failureList = uniqueName()
		currentBlock.Add([| $failureList = Boo.Lang.List[of FailedMatch]($(len(choices))) |])
		currentBlock.Add([| $oldInput = $input |])
		for choice in choices:
			expand currentBlock, choice, oldInput, lastMatch
			code = [|
				if $lastMatch isa FailedMatch:
					$failureList.Add($lastMatch)
			|]
			currentBlock.Add(code)
			currentBlock = code.TrueBlock
		currentBlock.Add([| $lastMatch = FailedMatch($oldInput, ChoiceFailure($failureList)) |])

	def resultAppend(result as Expression):
		if collectingParseTree:
			return [| $result.Add(smatch.Value) if smatch.Value is not null |]
		return ExpressionStatement([| $result = smatch.Value |])

	def expandRepetition(block as Block, e as Expression, input as Expression, lastMatch as ReferenceExpression):

		temp = lastMatch #uniqueName()
		result = uniqueName()
		block.Add(expand(e, input, temp))
		block.Add([| smatch = $temp as SuccessfulMatch |])
		code = [|
			if smatch is not null:
				$(resultAppend(result))
				$(expandRepetitionLoop(e, [| $temp.Input |], temp, result))
		|]
		if collectingParseTree:
			code.TrueBlock.Insert(0, [| $result = [] |])
		block.Add(code)

	def expandRepetitionLoop(e as Expression, input as Expression, lastMatch as ReferenceExpression, result as Expression):
		tempInput = uniqueName()
		code = [|
			block:
				$tempInput = $input
				while true:
					$(expand(e, tempInput, lastMatch))
					smatch = $lastMatch as SuccessfulMatch
					break if (smatch is null) or ($tempInput == smatch.Input)
					$tempInput = smatch.Input
					$(resultAppend(result))

				$lastMatch = SuccessfulMatch($tempInput, $result)
		|]
		return code.Body

	def collectChoices(choices as List, e as Expression):
		match e:
			case [| $l | $r |]:
				collectChoices choices, l
				collectChoices choices, r
			otherwise:
				choices.Add(e)

	collectingParseTree:
		get: return _collectingParseTree.Value

	def expandSequence(block as Block, sequence as ExpressionCollection, input as Expression, lastMatch as ReferenceExpression):

		if _optionParseTree and collectingParseTree:
			expandSequenceWithParseTree block, sequence, input, lastMatch
		else:
			expandSequenceWithoutParseTree block, sequence, input, lastMatch

	def expandSequenceWithoutParseTree(block as Block, sequence as ExpressionCollection, input as Expression, lastMatch as ReferenceExpression):

		currentBlock = block
		for item in sequence.ToArray()[:-1]:
			expand currentBlock, item, input, lastMatch
			input = [| $lastMatch.Input |]
			code = [|
				if $lastMatch isa SuccessfulMatch:
					pass
			|]
			currentBlock.Add(code)
			currentBlock = code.TrueBlock
		expand currentBlock, sequence[-1], input, lastMatch

	def expandSequenceWithParseTree(block as Block, sequence as ExpressionCollection, input as Expression, lastMatch as ReferenceExpression):

		result = uniqueName()
		currentBlock = block
		currentBlock.Add([| $result = [] |])

		for item in sequence:
			expand currentBlock, item, input, lastMatch
			currentBlock.Add([| smatch = $lastMatch as SuccessfulMatch |])
			code = [|
				if smatch is not null:
					$(resultAppend(result))
			|]
			currentBlock.Add(code)
			currentBlock = code.TrueBlock
			input = [| $lastMatch.Input |]

		currentBlock.Add([| $lastMatch = SuccessfulMatch(smatch.Input, $result) |])

	def expandNegation(block as Block, rule as Expression, input as Expression, lastMatch as ReferenceExpression):
		oldInput = uniqueName()
		block.Add([| $oldInput = $input |])

		_collectingParseTree.With(false):
			expand block, rule, input, lastMatch
		block.Add([| smatch = $lastMatch as SuccessfulMatch |])
		code = [|
			if smatch is null:
				$lastMatch = SuccessfulMatch($oldInput, null)
			else:
				$lastMatch = FailedMatch($oldInput, NegationFailure($(rule.ToCodeString())))
		|]
		block.Add(code)
		return code

	def processVariables(e as Expression, input as Expression):
		e.ReplaceNodes([| input |], [| $input |])
		return e

	def expand(block as Block, e as Expression, input as Expression, lastMatch as ReferenceExpression):
		match e:
			case SpliceExpression(Expression: rule):
				block.Add([| $lastMatch = $(processVariables(rule, input)) |])

			case [| $rule[$arg] |]:
				newInput = uniqueName()
				block.Add([| $newInput = OMetaInput.Prepend($arg, $input) |])
				expand block, rule, newInput, lastMatch

			case [| $pattern and $predicate |]:
				oldInput = uniqueName()
				block.Add([| $oldInput = $input |])
				expand block, pattern, input, lastMatch
				checkPredicate = [|
					if $lastMatch isa SuccessfulMatch and not $(processVariables(predicate, input)):
						$lastMatch = FailedMatch($oldInput, PredicateFailure($(predicate.ToCodeString())))
				|]
				block.Add(checkPredicate)

			case [| $pattern ^ $value |]:
				_collectingParseTree.With(false):
					expand block, pattern, input, lastMatch
					code = [|
						block:
							smatch = $lastMatch as SuccessfulMatch
							if smatch is not null:
								$lastMatch = SuccessfulMatch(smatch.Input, $(processVariables(value, input)))
					|].Body
					block.Add(code)

			case [| $pattern >> $variable |]:
				_collectingParseTree.With(true):
					expand block, pattern, input, lastMatch
					match variable:
						case [| $name as $typeref |]:
							code = [|
								block:
									smatch = $lastMatch as SuccessfulMatch
									if smatch is not null:
										$name = cast($typeref, smatch.Value)
							|].Body
						otherwise:
							code = [|
								block:
									smatch = $lastMatch as SuccessfulMatch
									if smatch is not null:
										$variable = smatch.Value
							|].Body
					block.Add(code)

			case [| $_ | $_ |]:
				choices = []
				collectChoices choices, e
				expandChoices block, choices, input, lastMatch

			case StringLiteralExpression(Value: v):
				if len(v) == 0:
					block.Add([| $lastMatch = SuccessfulMatch($input, null) |])
				elif len(v) == 1:
					block.Add([| $lastMatch = character($input, $(CharLiteralExpression(e.LexicalInfo, v[0]))) |])
				else:
					block.Add([| $lastMatch = characters($input, $e) |])

			case [| ++$rule |]:
				expandRepetition block, rule, input, lastMatch

			case [| --$rule |]:
				result = uniqueName()
				block.Add([| $result = [] |]) if collectingParseTree
				block.Add(expandRepetitionLoop(rule, input, lastMatch, result))

			case [| ~$rule |]:
				expandNegation block, rule, input, lastMatch

			case ReferenceExpression(Name: name):
				block.Add([| $lastMatch = context.Eval($name, $input) |])

			case [| super |]:
				block.Add([| $lastMatch = SuperApply(context, $_ruleName, $input) |])

			case [| $_() |]:
				rules = processObjectPatternRules(e)
				condition = Boo.Lang.PatternMatching.Impl.PatternExpander().Expand([| smatch.Value |], e)
				code = [|
					block:
						$lastMatch = any($input)
						smatch = $lastMatch as SuccessfulMatch
						if smatch is not null:
							if $condition:
								$(expandObjectPatternRules(rules, lastMatch))
							else:
								$lastMatch = FailedMatch($input, ObjectPatternFailure($(e.ToCodeString())))
				|].Body
				block.Add(code)

			case ArrayLiteralExpression(Items: items):
				match items[0]:
					case [| ~$rule |]:
						negation = expandNegation(block, rule, input, lastMatch)
						if len(items) > 2:
							expandSequence negation.TrueBlock, items.PopRange(1), input, lastMatch
						else:
							expand negation.TrueBlock, items[1], input, lastMatch
					otherwise:
						expandSequence block, items, input, lastMatch

	def processObjectPatternRules(pattern as Expression):
		rules = []
		processObjectPatternRules rules, pattern
		return rules

	def expandObjectPatternRules(rules, lastMatch as Expression) as Block:
		block = Block()
		input = uniqueName()

		currentBlock = block
		for temp as Expression, rule as Expression in rules:
			block.Add([| $input = OMetaInput.Singleton($temp) |])
			expand block, rule, input, lastMatch
			code = [|
				if $lastMatch isa SuccessfulMatch:
					pass
			|]
			currentBlock.Add(code)
			currentBlock = code.TrueBlock
		return block

	def processObjectPatternRules(rules as List, pattern as MethodInvocationExpression):
		for arg in pattern.NamedArguments:
			match arg.Second:
				case [| $_ >> $_ |]:
					temp = uniqueName()
					rules.Add((temp, arg.Second))
					arg.Second = temp
				otherwise:
					pass

namespace Boo.OMeta

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

class OMetaMacroRuleProcessor:

	_ruleName as string
	_collectingParseTree as DynamicVariable[of bool]
	_optionParseTree as bool

	def constructor(ruleName as string, options as List):
		_ruleName = ruleName
		_optionParseTree = "ParseTree" in options
		_collectingParseTree = DynamicVariable[of bool](_optionParseTree)

	def expand(e as Expression, *args as (Expression)) as Block:

		input = [| input_ |]
		block = Block(LexicalInfo: e.LexicalInfo)
		for arg in args:
			code = [|
				block:
					lastMatch = any($input)
					smatch = lastMatch as SuccessfulMatch
					if smatch is null: return lastMatch
					$input = smatch.Input
					$arg = smatch.Value
			|].Body
			block.Add(code)

		expand block, e, input, [| lastMatch |]
		block.Add([| return lastMatch |])

		return block

	def expand(e as Expression, input as Expression, lastMatch as ReferenceExpression) as Block:
		block = Block()
		expand block, e, input, lastMatch
		return block

	def expandChoices(block as Block, choices as List, input as Expression, lastMatch as ReferenceExpression):
		currentBlock = block

		oldInput = uniqueName()
		failureList = uniqueName()
		currentBlock.Add([| $failureList = Boo.Lang.List[of FailedMatch]($(len(choices))) |])
		currentBlock.Add([| $oldInput = $input |])
		for choice in choices:
			expand currentBlock, choice, oldInput, lastMatch
			code = [|
				if $lastMatch isa FailedMatch:
					$failureList.Add($lastMatch)
			|]
			currentBlock.Add(code)
			currentBlock = code.TrueBlock
		currentBlock.Add([| $lastMatch = FailedMatch($oldInput, ChoiceFailure($failureList)) |])

	def resultAppend(result as Expression):
		if collectingParseTree:
			return [| $result.Add(smatch.Value) if smatch.Value is not null |]
		return ExpressionStatement([| $result = smatch.Value |])

	def expandRepetition(block as Block, e as Expression, input as Expression, lastMatch as ReferenceExpression):

		temp = lastMatch #uniqueName()
		result = uniqueName()
		block.Add(expand(e, input, temp))
		block.Add([| smatch = $temp as SuccessfulMatch |])
		code = [|
			if smatch is not null:
				$(resultAppend(result))
				$(expandRepetitionLoop(e, [| $temp.Input |], temp, result))
		|]
		if collectingParseTree:
			code.TrueBlock.Insert(0, [| $result = [] |])
		block.Add(code)

	def expandRepetitionLoop(e as Expression, input as Expression, lastMatch as ReferenceExpression, result as Expression):
		tempInput = uniqueName()
		code = [|
			block:
				$tempInput = $input
				while true:
					$(expand(e, tempInput, lastMatch))
					smatch = $lastMatch as SuccessfulMatch
					break if smatch is null
					$tempInput = smatch.Input
					$(resultAppend(result))

				$lastMatch = SuccessfulMatch($tempInput, $result)
		|]
		return code.Body

	def collectChoices(choices as List, e as Expression):
		match e:
			case [| $l | $r |]:
				collectChoices choices, l
				collectChoices choices, r
			otherwise:
				choices.Add(e)

	collectingParseTree:
		get: return _collectingParseTree.Value

	def expandSequence(block as Block, sequence as ExpressionCollection, input as Expression, lastMatch as ReferenceExpression):

		if _optionParseTree and collectingParseTree:
			expandSequenceWithParseTree block, sequence, input, lastMatch
		else:
			expandSequenceWithoutParseTree block, sequence, input, lastMatch

	def expandSequenceWithoutParseTree(block as Block, sequence as ExpressionCollection, input as Expression, lastMatch as ReferenceExpression):

		currentBlock = block
		for item in sequence.ToArray()[:-1]:
			expand currentBlock, item, input, lastMatch
			input = [| $lastMatch.Input |]
			code = [|
				if $lastMatch isa SuccessfulMatch:
					pass
			|]
			currentBlock.Add(code)
			currentBlock = code.TrueBlock
		expand currentBlock, sequence[-1], input, lastMatch

	def expandSequenceWithParseTree(block as Block, sequence as ExpressionCollection, input as Expression, lastMatch as ReferenceExpression):

		result = uniqueName()
		currentBlock = block
		currentBlock.Add([| $result = [] |])

		for item in sequence:
			expand currentBlock, item, input, lastMatch
			currentBlock.Add([| smatch = $lastMatch as SuccessfulMatch |])
			code = [|
				if smatch is not null:
					$(resultAppend(result))
			|]
			currentBlock.Add(code)
			currentBlock = code.TrueBlock
			input = [| $lastMatch.Input |]

		currentBlock.Add([| $lastMatch = SuccessfulMatch(smatch.Input, $result) |])

	def expandNegation(block as Block, rule as Expression, input as Expression, lastMatch as ReferenceExpression):
		oldInput = uniqueName()
		block.Add([| $oldInput = $input |])

		_collectingParseTree.With(false):
			expand block, rule, input, lastMatch
		block.Add([| smatch = $lastMatch as SuccessfulMatch |])
		code = [|
			if smatch is null:
				$lastMatch = SuccessfulMatch($oldInput, null)
			else:
				$lastMatch = FailedMatch($oldInput, NegationFailure($(rule.ToCodeString())))
		|]
		block.Add(code)
		return code

	def processVariables(e as Expression, input as Expression):
		e.ReplaceNodes([| input |], [| $input |])
		return e

	def expand(block as Block, e as Expression, input as Expression, lastMatch as ReferenceExpression):
		match e:
			case SpliceExpression(Expression: rule):
				block.Add([| $lastMatch = $(processVariables(rule, input)) |])

			case [| $rule[$arg] |]:
				newInput = uniqueName()
				block.Add([| $newInput = OMetaInput.Prepend($arg, $input) |])
				expand block, rule, newInput, lastMatch

			case [| $pattern and $predicate |]:
				oldInput = uniqueName()
				block.Add([| $oldInput = $input |])
				expand block, pattern, input, lastMatch
				checkPredicate = [|
					if $lastMatch isa SuccessfulMatch and not $(processVariables(predicate, input)):
						$lastMatch = FailedMatch($oldInput, PredicateFailure($(predicate.ToCodeString())))
				|]
				block.Add(checkPredicate)

			case [| $pattern ^ $value |]:
				_collectingParseTree.With(false):
					expand block, pattern, input, lastMatch
					code = [|
						block:
							smatch = $lastMatch as SuccessfulMatch
							if smatch is not null:
								$lastMatch = SuccessfulMatch(smatch.Input, $(processVariables(value, input)))
					|].Body
					block.Add(code)

			case [| $pattern >> $variable |]:
				_collectingParseTree.With(true):
					expand block, pattern, input, lastMatch
					match variable:
						case [| $name as $typeref |]:
							code = [|
								block:
									smatch = $lastMatch as SuccessfulMatch
									if smatch is not null:
										$name = cast($typeref, smatch.Value)
							|].Body
						otherwise:
							code = [|
								block:
									smatch = $lastMatch as SuccessfulMatch
									if smatch is not null:
										$variable = smatch.Value
							|].Body
					block.Add(code)

			case [| $_ | $_ |]:
				choices = []
				collectChoices choices, e
				expandChoices block, choices, input, lastMatch

			case StringLiteralExpression(Value: v):
				if len(v) == 0:
					block.Add([| $lastMatch = SuccessfulMatch($input, null) |])
				elif len(v) == 1:
					block.Add([| $lastMatch = character($input, $(CharLiteralExpression(e.LexicalInfo, v[0]))) |])
				else:
					block.Add([| $lastMatch = characters($input, $e) |])

			case [| ++$rule |]:
				expandRepetition block, rule, input, lastMatch

			case [| --$rule |]:
				result = uniqueName()
				block.Add([| $result = [] |]) if collectingParseTree
				block.Add(expandRepetitionLoop(rule, input, lastMatch, result))

			case [| ~$rule |]:
				expandNegation block, rule, input, lastMatch

			case ReferenceExpression(Name: name):
				block.Add([| $lastMatch = context.Eval($name, $input) |])

			case [| super |]:
				block.Add([| $lastMatch = SuperApply(context, $_ruleName, $input) |])

			case [| $_() |]:
				rules = processObjectPatternRules(e)
				condition = Boo.Lang.PatternMatching.Impl.PatternExpander().Expand([| smatch.Value |], e)
				code = [|
					block:
						$lastMatch = any($input)
						smatch = $lastMatch as SuccessfulMatch
						if smatch is not null:
							if $condition:
								$(expandObjectPatternRules(rules, lastMatch))
							else:
								$lastMatch = FailedMatch($input, ObjectPatternFailure($(e.ToCodeString())))
				|].Body
				block.Add(code)

			case ArrayLiteralExpression(Items: items):
				match items[0]:
					case [| ~$rule |]:
						negation = expandNegation(block, rule, input, lastMatch)
						if len(items) > 2:
							expandSequence negation.TrueBlock, items.PopRange(1), input, lastMatch
						else:
							expand negation.TrueBlock, items[1], input, lastMatch
					otherwise:
						expandSequence block, items, input, lastMatch

	def processObjectPatternRules(pattern as Expression):
		rules = []
		processObjectPatternRules rules, pattern
		return rules

	def expandObjectPatternRules(rules, lastMatch as Expression) as Block:
		block = Block()
		input = uniqueName()

		currentBlock = block
		for temp as Expression, rule as Expression in rules:
			block.Add([| $input = OMetaInput.Singleton($temp) |])
			expand block, rule, input, lastMatch
			code = [|
				if $lastMatch isa SuccessfulMatch:
					pass
			|]
			currentBlock.Add(code)
			currentBlock = code.TrueBlock
		return block

	def processObjectPatternRules(rules as List, pattern as MethodInvocationExpression):
		for arg in pattern.NamedArguments:
			match arg.Second:
				case [| $_ >> $_ |]:
					temp = uniqueName()
					rules.Add((temp, arg.Second))
					arg.Second = temp
				otherwise:
					pass


fixed test case

fixed test case

namespace Boo.PatternMatching.Tests

import NUnit.Framework
import Boo.PatternMatching

class Item:
	public static final Default = Item(Name: "default")

	[property(Name)] _name = ""
	[property(Child)] _child as Item

[TestFixture]
class MatchMacroTest:

	[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 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]
	[ExpectedException(MatchError)]
	def TestMatchErrorOnPropertyPattern():
		itemByName(42)

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

	[Test]
	[ExpectedException(MatchError)]
	def TestMatchErrorOnNestedPropertyPattern():
		nestedByName(42)

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

	enum Foo:
		None
		Bar
		Baz

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

	[Test]
	[ExpectedException(MatchError)]
	def TestMatchErrorOnMemberReferencePattern():
		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"

namespace Boo.PatternMatching.Tests

import NUnit.Framework
import Boo.PatternMatching

class Item:
	public static final Default = Item(Name: "default")

	[property(Name)] _name = ""
	[property(Child)] _child as Item

[TestFixture]
class MatchMacroTest:

	[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 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]
	[ExpectedException(MatchError)]
	def TestMatchErrorOnPropertyPattern():
		itemByName(42)

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

	[Test]
	[ExpectedException(MatchError)]
	def TestMatchErrorOnNestedPropertyPattern():
		nestedByName(42)

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

	enum Foo:
		None
		Bar
		Baz

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

	[Test]
	[ExpectedException(MatchError)]
	def TestMatchErrorOnMemberReferencePattern():
		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"


Ability to execute unsaved scripts

Ability to execute unsaved scripts

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

namespace BooExplorer

import ICSharpCode.TextEditor
import ICSharpCode.TextEditor.Document
import ICSharpCode.TextEditor.Actions
import WeifenLuo.WinFormsUI
import System
import System.Windows.Forms
import System.Drawing
import Boo.Lang.Compiler
import Boo.Lang.Compiler.IO
import Boo.Lang.Compiler.Pipeline.Definitions

class BooEditor(Content):

	static NewFileName = "untitled.boo"

	_editor as TextEditorControl
	_main as MainForm

	[getter(FileName)]
	_fname as string

	[getter(IsDirty)]
	_dirty = false

	def constructor(main as MainForm):
		_main = main
		_editor = TextEditorControl(Dock: DockStyle.Fill,
							Font: System.Drawing.Font("Lucida Console", 13.0),
							EnableFolding: true)

		_editor.Encoding = System.Text.Encoding.UTF8
		_editor.Document.FormattingStrategy = BooFormattingStrategy()
		_editor.Document.HighlightingStrategy = GetBooHighlighting()
		_editor.Document.DocumentChanged += _editor_DocumentChanged

		SuspendLayout()
		Controls.Add(_editor)
		self.AllowedStates = ContentStates.Document
		self.Text = NewFileName
		self.DockPadding.All = 1
		self.Menu = CreateMenu()
		ResumeLayout(false)

	TextArea:
		get:
			return _editor.ActiveTextAreaControl.TextArea

	TextContent:
		get:
			return _editor.Document.TextContent

	def GoTo(line as int):
		_editor.ActiveTextAreaControl.JumpTo(line, 1)

	def SaveFile():
		if _fname:
			_editor.SaveFile(_fname)
		else:
			dlg = SaveFileDialog(AddExtension: true,
							DefaultExt: ".boo",
							OverwritePrompt: true,
							Filter: "boo files (*.boo)|*.boo")
			if DialogResult.OK == dlg.ShowDialog():
				_editor.SaveFile(dlg.FileName)
				_fname = dlg.FileName

		_dirty = false
		self.Text = _fname

	def LoadFile([required] fname as string):
		_editor.LoadFile(fname)
		self.Text = _fname = fname
		_dirty = false

	def _editor_DocumentChanged(sender, args as DocumentEventArgs):
		if not _dirty:
			self.Text = "${GetSafeFileName()} (modified)"
			_dirty = true

	def _menuItemRun_Click(sender, args as EventArgs):
		savedCursor = Cursor
		self.Cursor = Cursors.WaitCursor

		compiler = BooCompiler()
		compiler.Parameters.Input.Add(StringInput(GetSafeFileName(), self.TextContent))
		compiler.Parameters.Pipeline.Load(BoomPipelineDefinition)

		started = date.Now
		result = compiler.Run()
		finished = date.Now
		_main.StatusText = "Compilation finished in ${finished-started} with ${len(result.Errors)} error(s)."

		if len(result.Errors):
			print(join(result.Errors, "\n"))
		else:
			try:
				result.GeneratedAssemblyEntryPoint.Invoke(null, (null,))
			except x:
				print(x)

		self.Cursor = savedCursor

	def GetSafeFileName():
		if _fname:
			return _fname
		return NewFileName

	def CreateMenu():
		menu = MainMenu()

		script = MenuItem(Text: "&Script")
		script.MenuItems.Add(MenuItem(Text: "Run",
									Click: _menuItemRun_Click,
									Shortcut: Shortcut.F5))

		menu.MenuItems.AddRange((script,))
		return menu

	def GetBooHighlighting():
		return HighlightingManager.Manager.FindHighlighter("boo")

namespace BooExplorer

import ICSharpCode.TextEditor
import ICSharpCode.TextEditor.Document
import ICSharpCode.TextEditor.Actions
import WeifenLuo.WinFormsUI
import System
import System.Windows.Forms
import System.Drawing
import Boo.Lang.Compiler
import Boo.Lang.Compiler.IO
import Boo.Lang.Compiler.Pipeline.Definitions

class BooEditor(Content):

	_editor as TextEditorControl
	_main as MainForm

	[getter(FileName)]
	_fname as string

	[getter(IsDirty)]
	_dirty = false

	def constructor(main as MainForm):
		_main = main
		_editor = TextEditorControl(Dock: DockStyle.Fill,
							Font: System.Drawing.Font("Lucida Console", 13.0),
							EnableFolding: true)

		_editor.Encoding = System.Text.Encoding.UTF8
		_editor.Document.FormattingStrategy = BooFormattingStrategy()
		_editor.Document.HighlightingStrategy = GetBooHighlighting()
		_editor.Document.DocumentChanged += _editor_DocumentChanged

		SuspendLayout()
		Controls.Add(_editor)
		self.AllowedStates = ContentStates.Document
		self.Text = "unnamed.boo"
		self.DockPadding.All = 1
		self.Menu = CreateMenu()
		ResumeLayout(false)

	TextArea:
		get:
			return _editor.ActiveTextAreaControl.TextArea

	TextContent:
		get:
			return _editor.Document.TextContent

	def GoTo(line as int):
		_editor.ActiveTextAreaControl.JumpTo(line, 1)

	def SaveFile():
		if _fname:
			_editor.SaveFile(_fname)
		else:
			dlg = SaveFileDialog(AddExtension: true,
							DefaultExt: ".boo",
							OverwritePrompt: true,
							Filter: "boo files (*.boo)|*.boo")
			if DialogResult.OK == dlg.ShowDialog():
				_editor.SaveFile(dlg.FileName)
				_fname = dlg.FileName

		_dirty = false
		self.Text = _fname

	def LoadFile([required] fname as string):
		_editor.LoadFile(fname)
		self.Text = _fname = fname
		_dirty = false

	def _editor_DocumentChanged(sender, args as DocumentEventArgs):
		if not _dirty:
			self.Text = "${_fname} (modified)"
			_dirty = true

	def _menuItemRun_Click(sender, args as EventArgs):
		savedCursor = Cursor
		self.Cursor = Cursors.WaitCursor

		compiler = BooCompiler()
		compiler.Parameters.Input.Add(StringInput(self.Text, self.TextContent))
		compiler.Parameters.Pipeline.Load(BoomPipelineDefinition)

		started = date.Now
		result = compiler.Run()
		finished = date.Now
		_main.StatusText = "Compilation finished in ${finished-started} with ${len(result.Errors)} error(s)."

		if len(result.Errors):
			print(join(result.Errors, "\n"))
		else:
			try:
				result.GeneratedAssemblyEntryPoint.Invoke(null, (null,))
			except x:
				print(x)

		self.Cursor = savedCursor

	def CreateMenu():
		menu = MainMenu()

		script = MenuItem(Text: "&Script")
		script.MenuItems.Add(MenuItem(Text: "Run",
									Click: _menuItemRun_Click,
									Shortcut: Shortcut.F5))

		menu.MenuItems.AddRange((script,))
		return menu

	def GetBooHighlighting():
		return HighlightingManager.Manager.FindHighlighter("boo")


better code organization

better code organization

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

#region license
// boo - an extensible programming language for the CLI
// Copyright (C) 2004 Rodrigo B. de Oliveira
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//
// As a special exception, if you link this library with other files to
// produce an executable, this library does not by itself cause the
// resulting executable to be covered by the GNU General Public License.
// This exception does not however invalidate any other reasons why the
// executable file might be covered by the GNU General Public License.
//
// Contact Information
//
// mailto:rbo@acm.org
#endregion

import System
import System.IO
import System.Reflection
import Boo.Lang.Compiler
import Boo.Lang.Compiler.IO

class AssemblyResolver:

	_cache = {}

	def AssemblyResolve(sender, args as ResolveEventArgs) as Assembly:
		parts = /,\s*/.Split(args.Name)
		simpleName = parts[0]

		asm as Assembly = _cache[simpleName]
		if asm is null:
			basePath = Path.GetFullPath(simpleName)
			asm = probeFile(basePath + ".dll")
			if asm is null:
				asm = probeFile(basePath + ".exe")
			_cache[simpleName] = asm

		return asm

	def probeFile(fname as string):
		if File.Exists(fname):
			return Assembly.LoadFrom(fname)

def main(argv as (string)):
	compiler = BooCompiler()
	if "-" == argv[0]:
		compiler.Parameters.Input.Add(ReaderInput("<stdin>", System.Console.In))
	else:
		compiler.Parameters.Input.Add(FileInput(argv[0]))

	// boo memory pipeline
	// compiles the code in memory only
	compiler.Parameters.Pipeline.Load("boom")

	AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolver().AssemblyResolve
	result = compiler.Run()
	if len(result.Errors):
		for error as CompilerError in result.Errors:
			print(error.ToString(true))
	else:
		try:
			result.GeneratedAssemblyEntryPoint.Invoke(null, (argv[1:],))
		except x as TargetInvocationException:
			print(x.InnerException)

if len(argv) > 0:
	main(argv)
else:
	print("booi <script.boo>")

#region license
// boo - an extensible programming language for the CLI
// Copyright (C) 2004 Rodrigo B. de Oliveira
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//
// As a special exception, if you link this library with other files to
// produce an executable, this library does not by itself cause the
// resulting executable to be covered by the GNU General Public License.
// This exception does not however invalidate any other reasons why the
// executable file might be covered by the GNU General Public License.
//
// Contact Information
//
// mailto:rbo@acm.org
#endregion

import System
import System.IO
import System.Reflection
import Boo.Lang.Compiler
import Boo.Lang.Compiler.IO

class AssemblyResolver:

	_cache = {}

	def AssemblyResolve(sender, args as ResolveEventArgs) as Assembly:
		parts = /,\s*/.Split(args.Name)
		simpleName = parts[0]

		asm as Assembly = _cache[simpleName]
		if asm is null:
			basePath = Path.GetFullPath(simpleName)
			asm = probeFile(basePath + ".dll")
			if asm is null:
				asm = probeFile(basePath + ".exe")
			_cache[simpleName] = asm

		return asm

def main(argv as (string)):
	compiler = BooCompiler()
	if "-" == argv[0]:
		compiler.Parameters.Input.Add(ReaderInput("<stdin>", System.Console.In))
	else:
		compiler.Parameters.Input.Add(FileInput(argv[0]))

	// boo memory pipeline
	// compiles the code in memory only
	compiler.Parameters.Pipeline.Load("boom")

	AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolver().AssemblyResolve
	result = compiler.Run()
	if len(result.Errors):
		for error as CompilerError in result.Errors:
			print(error.ToString(true))
	else:
		try:
			result.GeneratedAssemblyEntryPoint.Invoke(null, (argv[1:],))
		except x as TargetInvocationException:
			print(x.InnerException)

def probeFile(fname as string):
	if File.Exists(fname):
		return Assembly.LoadFrom(fname)

if len(argv) > 0:
	main(argv)
else:
	print("booi <script.boo>")


unnecessary colon

unnecessary colon

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

"""
caught!
should have passed here first!
should end up here!

"""
try:
	raise System.ApplicationException("caught!")
	print("should not get here!")
except x:
	print(x.Message)
ensure:
	print("should have passed here first!")
print("should end up here!");


BOO-1002 : fix booish(2) stability problems

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

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

namespace Boo.Lang.Interpreter

import System
import System.Collections
import System.Collections.Generic
import System.IO
import System.Text
import System.Text.RegularExpressions
import Boo.Lang
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.TypeSystem
import Boo.Lang.Compiler.IO

class InteractiveInterpreter2(AbstractInterpreter):

	_values = {}

	_declarations = {}

	_representers = []

	[property(ShowWarnings)]
	_showWarnings = false

	[property(BlockStarters, value is not null)]
	_blockStarters = (char(':'), char('\\'),)

	[getter(LastValue)]
	_lastValue

	[property(Print, value is not null)]
	_print as callable(object) = print

	_entityNameComparer = EntityNameComparer()

	def constructor():
		super()
		Initialize()

	def constructor(parser as ICompilerStep):
		super(parser)
		Initialize()

	def Initialize():
		_disableColors = true if Environment.GetEnvironmentVariable("BOOISH_DISABLE_COLORS") is not null
		_disableColors = true if Environment.GetEnvironmentVariable("INSIDE_EMACS") is not null
		_disableAutocompletion = true if Environment.GetEnvironmentVariable("BOOISH_DISABLE_AUTOCOMPLETION") is not null
		InitializeStandardReferences()
		LoadHistory()

	def Reset():
		_values.Clear()
		_declarations.Clear()
		_lastValue = null
		_indent = 0
		InitializeStandardReferences()

	public final static HISTORY_FILENAME = "booish_history"
	public final static HISTORY_CAPACITY = 100
	protected _history = System.Collections.Generic.Queue of string(HISTORY_CAPACITY)
	protected _historyFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), HISTORY_FILENAME)
	protected _historyIndex = 0
	protected _session = System.Collections.Generic.List of string()

	private _buffer = StringBuilder()	#buffer to be executed
	private _line = StringBuilder()		#line being edited
	private _multiline = false			#is the current line a multi-line?

	CurrentPrompt as string:
		get:
			if _indent > 0:
				return BlockPrompt
			return DefaultPrompt

	[property(DefaultPrompt)]
	_defaultPrompt = ">>>"

	[property(BlockPrompt)]
	_blockPrompt = "..."

	[property(DisableColors)]
	_disableColors = false

	[property(DisableAutocompletion)]
	_disableAutocompletion = false

	[property(InterpreterColor)] # messages from the interpreter (not from user code)
	_interpreterColor = ConsoleColor.Gray

	[property(PromptColor)]
	_promptColor = ConsoleColor.Green

	[property(ExceptionColor)]
	_exceptionColor = ConsoleColor.Red

	[property(SuggestionsColor)]
	_suggestionsColor = ConsoleColor.Yellow

	[property(SelectedSuggestionColor)]
	_selectedSuggestionColor = ConsoleColor.Magenta

	[property(SelectedSuggestionIndex)]
	_selectedSuggIdx as int?

	[property(Suggestions)]
	_suggestions as (object)

	CanAutoComplete as bool:
		get:
			return _selectedSuggIdx is not null

	private _builtins as (IEntity)
	private _filter as string

	private def ConsolePrintPrompt():
		Console.ForegroundColor = _promptColor if not _disableColors
		Console.Write(CurrentPrompt)
		#TODO: automatic indentation option ?
		#for i in range(_indent):
		#	_line.Append("\t")
		#	Console.Write("\t")
		Console.ResetColor() if not _disableColors

	private def ConsolePrintMessage(msg as string):
		Console.ForegroundColor = _interpreterColor if not _disableColors
		print msg
		Console.ResetColor() if not _disableColors

	private def ConsolePrintException(e as Exception):
		Console.ForegroundColor = _exceptionColor if not _disableColors
		print e
		Console.ResetColor() if not _disableColors

	private def ConsolePrintError(msg as string):
		Console.ForegroundColor = _exceptionColor if not _disableColors
		print msg
		Console.ResetColor() if not _disableColors

	protected def ConsolePrintSuggestions():
		cursorLeft = Console.CursorLeft
		#cursorTop = Console.CursorTop
		Console.Write(Environment.NewLine)

		i = 0

		Array.Sort(_suggestions) if _suggestions isa (string)
		Array.Sort[of IEntity](_suggestions, _entityNameComparer) if _suggestions isa (IEntity)

		for s in _suggestions as (object):
			Console.ForegroundColor = _suggestionsColor if not _disableColors
			Console.Write(", ") if i > 0
			if i > 20: #TODO: maxcandidates pref + paging?
				Console.Write("... (too much candidates)")
				break
			if i == _selectedSuggIdx:
				Console.ForegroundColor = _selectedSuggestionColor if not _disableColors
			if s isa IEntity:
				Console.Write(DescribeEntity(s as IEntity))
			else:
				Console.Write(s)
			i++

		Console.ResetColor() if not _disableColors
		#Console.CursorTop = cursorTop
		Console.Write(Environment.NewLine)
		ConsolePrintPrompt()
		Console.Write(_line.ToString())
		Console.CursorLeft = cursorLeft

	protected def Write(s as string):
		Console.Write(s)
		_line.Append(s)

	private static re_open = Regex("\\(", RegexOptions.Singleline)
	private static re_close = Regex("\\)", RegexOptions.Singleline)

	def DisplaySuggestions(query as string):
		return if DisableAutocompletion

		#TODO: FIXME: refactor to one regex?
		p_open = re_open.Matches(query).Count
		p_close = re_close.Matches(query).Count
		if p_open > p_close:
			query = query.Split(" ,(\t".ToCharArray(), 100)[-1]
		else:
			query = query.Split(" ,\t".ToCharArray(), 100)[-1]
		if query.LastIndexOf('.') > 0:
			codeToComplete = query[0:query.LastIndexOf('.')+1]
			_suggestions = SuggestCodeCompletion(codeToComplete+"__codecomplete__")
			_filter = query[query.LastIndexOf('.')+1:]
			_suggestions = array(e for e in _suggestions as (IEntity)
								unless not e.Name.StartsWith(_filter)) as (IEntity)

		if not _suggestions or 0 == len(_suggestions): #suggest a  var
			_filter = query
			_suggestions = array(var.ToString() for var in _values.Keys
									unless not var.ToString().StartsWith(_filter))

		if not _suggestions or 0 == len(_suggestions):
			_selectedSuggIdx = null
			#Console.Beep() #TODO: flash background?
		elif 1 == len(_suggestions):
			AutoComplete()
		else:
			ConsolePrintSuggestions()

	def AutoComplete():
		raise InvalidOperationException("no suggestions") if _suggestions is null or _selectedSuggIdx is null

		s = _suggestions[_selectedSuggIdx.Value] as IEntity
		completion = (s.Name[len(_filter):] if s is not null else (_suggestions[_selectedSuggIdx.Value] as string)[len(_filter):])
		Write(completion)
		if s and s.EntityType == EntityType.Method:
			Write("(")
			m = s as IMethod
			Write(')') if m.GetParameters().Length == 0

		_selectedSuggIdx = null
		_suggestions = null

	private _beforeHistory = string.Empty

	def DisplayHistory():
		if _history.Count == 0 or _historyIndex < 0 or _historyIndex > _history.Count:
			return
		Console.CursorLeft = len(CurrentPrompt)
		Console.Write(string.Empty.PadLeft(_line.Length, char(' ')))
		line = _history.ToArray()[_historyIndex]
		_line.Length = 0
		Console.CursorLeft = len(CurrentPrompt)
		Write(line)

	def ConsoleLoopEval():
		Console.CursorVisible = true
		ConsolePrintPrompt()

		lastChar = char('0')
		key = ConsoleKey.LeftArrow
		while not _quit:
			cki = Console.ReadKey(true)
			key = cki.Key
			keyChar = cki.KeyChar
			control = false

			newLine = keyChar in Environment.NewLine

			if char.IsControl(keyChar):
				control = true
				if keyChar == char('\t'):
					test = char(' ')
					test = _line.ToString()[_line.Length-1] if _line.Length > 0
					if char.IsLetterOrDigit(test) or test == char('.'):
						_selectedSuggIdx = 0
						DisplaySuggestions(_line.ToString())
					else:
						Write("    ")

				#line-editing support
				if not _multiline:
					if key == ConsoleKey.Backspace:
						if Console.CursorLeft > len(CurrentPrompt) and _line.Length > 0:
							cx = Console.CursorLeft-len(CurrentPrompt)-1
							_line.Remove(cx, 1)
							cx2 = --Console.CursorLeft
							Console.Write("${_line.ToString(cx, _line.Length-cx)} ")
							Console.CursorLeft = cx2
					elif key == ConsoleKey.Delete:
						if Console.CursorLeft >= len(CurrentPrompt) and _line.Length > 0:
							cx = Console.CursorLeft-len(CurrentPrompt)
							if cx < _line.Length:
								_line.Remove(cx, 1)
								cx2 = Console.CursorLeft
								Console.Write("${_line.ToString(cx, _line.Length-cx)} ")
								Console.CursorLeft = cx2
					elif key == ConsoleKey.LeftArrow:
						if Console.CursorLeft > len(CurrentPrompt) and _line.Length > 0:
							Console.CursorLeft--
					elif key == ConsoleKey.RightArrow:
						if Console.CursorLeft < (len(CurrentPrompt)+_line.Length):
							Console.CursorLeft++
					elif key == ConsoleKey.Home:
						Console.CursorLeft = len(CurrentPrompt)
					elif key == ConsoleKey.End:
						Console.CursorLeft = len(CurrentPrompt) + _line.Length

				#history support
				if key == ConsoleKey.UpArrow:
					if _historyIndex > 0:
						_historyIndex--
						DisplayHistory()
				elif key == ConsoleKey.DownArrow:
					if _historyIndex < _history.Count-1:
						_historyIndex++
						DisplayHistory()

				#auto-completion support
				if CanAutoComplete:
					if key == ConsoleKey.LeftArrow:
						if _selectedSuggIdx > 0:
							_selectedSuggIdx--
						else:
							_selectedSuggIdx = len(_suggestions)
						DisplaySuggestions(_line.ToString())
					elif key == ConsoleKey.RightArrow:
						if _selectedSuggIdx <= len(_suggestions):
							_selectedSuggIdx++
						else:
							_selectedSuggIdx = 0
						DisplaySuggestions(_line.ToString())
					if newLine:
						AutoComplete()
						continue
				if not newLine:
					continue

			_selectedSuggIdx = null

			cx = Console.CursorLeft-len(CurrentPrompt)
			#multi-line?
			if cx < 0 or _line.Length >= Console.WindowWidth-len(CurrentPrompt):
				cx = _line.Length
				_multiline = true

			if not newLine:
				#line-editing support
				if cx < _line.Length and not _multiline:
					_line.Insert(cx, keyChar) if not control
					Console.Write(_line.ToString(cx, _line.Length-cx))
					Console.CursorLeft = len(CurrentPrompt)+cx+1
				else:
					_line.Append(keyChar) if not control
					Console.Write(keyChar)

			if newLine:
				_multiline = false
				Console.Write(Environment.NewLine)

				line = _line.ToString()

				if not TryRunCommand(line):
					_buffer.Append(line)
					_buffer.Append(Environment.NewLine)
					AddToHistory(line)

					try:
						if len(line) > 0:
							_indent++ if line[len(line)-1] in _blockStarters
							_indent-- if line.EndsWith(keyChar.ToString()) and _indent > 0
						elif _indent > 0:
							_indent--
						if _indent == 0:
							InternalLoopEval(_buffer.ToString())
							_buffer.Length = 0 #truncate buffer
					except x as System.Reflection.TargetInvocationException:
						ConsolePrintException(x.InnerException)
					except x:
						ConsolePrintException(x)

					_line.Length = 0 #truncate line
					ConsolePrintPrompt()

			lastChar = keyChar

		SaveHistory()
		DisplayGoodbye()

	/* returns false if no command has been processed, true otherwise */
	def TryRunCommand(line as string):
		if not line.StartsWith("/"):
			return false

		cmd = line.Split()

		if len(cmd) == 1:
			if cmd[0] == "/q" or cmd[0] == "/quit":
				quit()
			elif cmd[0] == "/?" or cmd[0] == "/h" or cmd[0] == "/help":
				DisplayHelp()
				_selectedSuggIdx = null
				_buffer.Length = 0
				ConsolePrintPrompt()
			elif cmd[0] == "/g" or cmd[0] == "/globals":
				globals()
			else:
				return false

		elif len(cmd) == 2:
			if cmd[0] == "/l" or cmd[0] == "/load":
				load(cmd[1])
				_line.Length = 0
				ConsolePrintPrompt()
			elif cmd[0] == "/s" or cmd[0] == "/save":
				save(cmd[1])
				_line.Length = 0
				ConsolePrintPrompt()
			else:
				return false

		else:
			return false
		return true

	private _indent as int = 0

	def LoopEval(code as string):
		using console = ConsoleCapture():
			result = InternalLoopEval(code)
			for line in System.IO.StringReader(console.ToString()):
				_print(line)
		return result

	private def InternalLoopEval(code as string):
		result = self.Eval(code)
		if ShowWarnings:
			self.DisplayProblems(result.Warnings)
		if not self.DisplayProblems(result.Errors):
			ProcessLastValue()
			_session.Add(code)
		return result

	private def ProcessLastValue():
		_ = self.LastValue
		if _ is not null:
			_print(repr(_))
			SetValue("_", _)

	override def Declare([required] name as string,
				[required] type as System.Type):
		_declarations.Add(name, type)

	override def SetLastValue(value):
		_lastValue = value

	override def SetValue(name as string, value):
		_values[name] = value
		return value

	override def GetValue(name as string):
		return _values[name]

	override def Lookup([required] name as string):
		type as System.Type = _declarations[name]
		return type if type is not null

		value = GetValue(name)
		return value.GetType() if value is not null

	def DisplayProblems(problems as ICollection):
		return if problems is null or problems.Count == 0
		Console.ForegroundColor = _exceptionColor if not _disableColors
		for problem as duck in problems:
			markLocation(problem.LexicalInfo)
			type = ("WARNING", "ERROR")[problem isa CompilerError]
			_print("${type}: ${problem.Message}")
		Console.ResetColor() if not _disableColors
		if problems.Count > 0:
			return true
		return false

	private def markLocation(location as LexicalInfo):
		pos = location.Column
		_print("---" + "-" * pos + "^") if pos > 0

	private def InitializeStandardReferences():
		SetValue("interpreter", self)
		SetValue("dir", dir)
		SetValue("help", help)
		SetValue("print", { value | _print(value) })
		SetValue("load", load)
		SetValue("globals", globals)
		SetValue("getRootNamespace", Namespace.GetRootNamespace)

	def DisplayHelp():
		Console.ForegroundColor = _interpreterColor	if not _disableColors
		print """Welcome to booish, an interpreter for the boo programming language.
Running boo ${BooVersion} in CLR v${Environment.Version}.

The following builtin functions are available:
    /d[ir] type : lists the members of a type
    /h[elp] type : prints detailed information about a type
    /l[oad] file : evals an external boo file
    /s[ave] file : writes your current booish session into file
    /g[lobals] : returns names of all variables known to interpreter
    /n[ames] : namespace navigation
    /q[uit] : exits the interpreter

Enter boo code in the prompt below."""
		Console.ResetColor() if not _disableColors

	def DisplayGoodbye():	// booish is friendly
		Console.ForegroundColor = _interpreterColor if not _disableColors
		print ""
		print "All your boo are belong to us!"
		Console.ResetColor() if not _disableColors

	def LoadHistory():
		try:
			using history = File.OpenText(_historyFile):
				while line = history.ReadLine():
					AddToHistory(line)
		except:
			ConsolePrintError("Cannot load history from '${_historyFile}'")

	def AddToHistory(line as string):
		return if 0 == (len(line) - len(Environment.NewLine))
		_history.Dequeue() if _history.Count >= HISTORY_CAPACITY
		line = line.Replace(Environment.NewLine, "")
		_history.Enqueue(line)
		_historyIndex = _history.Count

	def SaveHistory():
		try:
			using sw = System.IO.File.CreateText(_historyFile):
				for line in _history:
					sw.WriteLine(line)
		except:
			ConsolePrintError("Cannot save history to '${_historyFile}'.")

	def globals():
		return array(string, _values.Keys)

	def dir([required] obj):
		type = (obj as Type) or obj.GetType()
		return array(member for member in type.GetMembers()
						unless (method=(member as System.Reflection.MethodInfo))
							and method.IsSpecialName)

	def load([required] path as string):
		if path.EndsWith(".boo"):
			ConsolePrintMessage("Evaluating '${path}' ...")
			result = EvalCompilerInput(FileInput(path))
			if ShowWarnings:
				DisplayProblems(result.Warnings)
			if not DisplayProblems(result.Errors):
				ProcessLastValue()
		else:
			try:
				ConsolePrintMessage("Adding reference to '${path}'")
				References.Add(System.Reflection.Assembly.LoadFrom(path))
			except e:
				ConsolePrintError(e.Message)

	def save([required] path as string):
		if path is string.Empty:
			path = "booish_session.boo"
		elif not path.EndsWith(".boo"):
			path = "${path}.boo"
		try:
			using sw = System.IO.File.CreateText(path):
				for line in _session:
					sw.Write(line)
			ConsolePrintMessage("Session saved to '${path}'.")
		except:
			ConsolePrintError("Cannot save to '${path}'. Check if path is valid and has correct permissions.")

	def help(obj):
		type = (obj as Type) or obj.GetType()
		DescribeType("    ", type)

	private _quit = false
	def quit():
		_quit = true

	def DescribeType(indent as string, type as Type):
		if type.IsInterface:
			typeDef = "interface"
			baseTypes = array(GetBooTypeName(t) for t in type.GetInterfaces())
		else:
			typeDef = "class"
			baseTypes = (GetBooTypeName(type.BaseType),) + array(GetBooTypeName(t) for t in type.GetInterfaces())

		_print("${typeDef} ${type.Name}(${join(baseTypes, ', ')}):")
		_print("")

		for ctor in type.GetConstructors():
			_print("${indent}def constructor(${DescribeParameters(ctor.GetParameters())})")
			_print("")

		sortByName = def (lhs as Reflection.MemberInfo, rhs as Reflection.MemberInfo):
			return lhs.Name.CompareTo(rhs.Name)

		for f as Reflection.FieldInfo in List(type.GetFields()).Sort(sortByName):
			_print("${indent}public ${DescribeField(f)}")
			_print("")

		for p as Reflection.PropertyInfo in List(type.GetProperties()).Sort(sortByName):
			_print("${indent}${DescribeProperty(p)}:")
			_print("${indent}${indent}get") if p.GetGetMethod() is not null
			_print("${indent}${indent}set") if p.GetSetMethod() is not null
			_print("")

		for m as Reflection.MethodInfo in List(type.GetMethods()).Sort(sortByName):
			continue if m.IsSpecialName
			_print("${indent}${DescribeMethod(m)}")
			_print("")

		for e as Reflection.EventInfo in List(type.GetEvents()).Sort(sortByName):
			_print("${indent}${DescribeEvent(e)}")
			_print("")

	static def DescribeEntity(entity as IEntity):
		method = entity as ExternalMethod
		if method is not null:
			return InteractiveInterpreter.DescribeMethod(method.MethodInfo)
		field = entity as ExternalField
		if field is not null:
			return InteractiveInterpreter.DescribeField(field.FieldInfo)
		property = entity as ExternalProperty
		if property is not null:
			return InteractiveInterpreter.DescribeProperty(property.PropertyInfo)
		e = entity as ExternalEvent
		if e is not null:
			return InteractiveInterpreter.DescribeEvent(e.EventInfo)
		return entity.ToString()

	static def DescribeEvent(e as Reflection.EventInfo):
		return "${DescribeModifiers(e)}event ${e.Name} as ${e.EventHandlerType}"

	static def DescribeProperty(p as Reflection.PropertyInfo):
		modifiers = DescribeModifiers(p)
		params = DescribePropertyParameters(p.GetIndexParameters())
		return "${modifiers}${p.Name}${params} as ${GetBooTypeName(p.PropertyType)}"

	static def DescribeField(f as Reflection.FieldInfo):
		return "${DescribeModifiers(f)}${f.Name} as ${GetBooTypeName(f.FieldType)}"

	static def DescribeMethod(m as Reflection.MethodInfo):
		returnType = GetBooTypeName(m.ReturnType)
		modifiers = DescribeModifiers(m)
		return "${modifiers}def ${m.Name}(${DescribeParameters(m.GetParameters())}) as ${returnType}"

	static def DescribeModifiers(f as Reflection.FieldInfo):
		return "static " if f.IsStatic
		return ""

	static def DescribeModifiers(m as Reflection.MethodBase):
		return "static " if m.IsStatic
		return ""

	static def DescribeModifiers(e as Reflection.EventInfo):
		return DescribeModifiers(e.GetAddMethod(true) or e.GetRemoveMethod(true))

	static def DescribeModifiers(p as Reflection.PropertyInfo):
		accessor = p.GetGetMethod(true) or p.GetSetMethod(true)
		return DescribeModifiers(accessor)

	static def DescribePropertyParameters(parameters as (Reflection.ParameterInfo)):
		return "" if 0 == len(parameters)
		return "(${DescribeParameters(parameters)})"

	static def DescribeParameters(parameters as (Reflection.ParameterInfo)):
		return join(DescribeParameter(p) for p in parameters, ", ")

	static def DescribeParameter(p as Reflection.ParameterInfo):
		return "${p.Name} as ${GetBooTypeName(p.ParameterType)}"

	static def GetBooTypeName(type as System.Type) as string:
		return "(${GetBooTypeName(type.GetElementType())})" if type.IsArray
		return "object" if object is type
		return "string" if string is type
		return "void" if void is type
		return "bool" if bool is type
		return "byte" if byte is type
		return "char" if char is type
		return "sbyte" if sbyte is type
		return "short" if short is type
		return "ushort" if ushort is type
		return "int" if int is type
		return "uint" if uint is type
		return "long" if long is type
		return "ulong" if ulong is type
		return "single" if single is type
		return "double" if double is type
		return "date" if date is type
		return "timespan" if timespan is type
		return "regex" if regex is type
		return type.FullName

	def repr(value):
		writer = System.IO.StringWriter()
		repr(value, writer)
		return writer.ToString()

	def repr(value, writer as System.IO.TextWriter):
		return unless value is not null
		InitializeRepresenters() if 0 == len(_representers)
		GetBestRepresenter(value.GetType())(value, writer)

	private def InitializeRepresenters():
		AddRepresenter(string) do (value as string, writer as TextWriter):
			Visitors.BooPrinterVisitor.WriteStringLiteral(value, writer)

		AddRepresenter(bool) do (value as bool, writer as TextWriter):
			writer.Write(("false", "true")[value])

		AddRepresenter(Array) do (a as Array, writer as TextWriter):
			writer.Write("(")
			RepresentItems(a, writer)
			writer.Write(")")

		AddRepresenter(Delegate) do (d as Delegate, writer as TextWriter):
			method = d.Method
			writer.Write(method.DeclaringType.FullName)
			writer.Write(".")
			writer.Write(method.Name)

		AddRepresenter(IDictionary) do (value as IDictionary, writer as TextWriter):
			writer.Write("{")
			i = 0
			for key in value.Keys:
				writer.Write(", ") if i
				repr(key, writer)
				writer.Write(": ")
				repr(value[key], writer)
				++i
			writer.Write("}")

		AddRepresenter(IList) do (value as IList, writer as TextWriter):
			writer.Write("[")
			RepresentItems(value, writer)
			writer.Write("]")

		AddRepresenter(object) do (value, writer as TextWriter):
			writer.Write(value)

	private def RepresentItems(items, writer as TextWriter):
		i = 0
		for item in items:
			writer.Write(", ") if i > 0
			repr(item, writer)
			++i

	callable Representer(value, writer as TextWriter)

	private def AddRepresenter(type as Type, value as Representer):
		_representers.Add((type, value))

	def GetBestRepresenter(type as Type) as Representer:
		for key as Type, value in _representers:
			return value if key.IsAssignableFrom(type)
		assert false, "An appropriate representer could not be found!"

class EntityNameComparer(IComparer of IEntity):
	def Compare(a as IEntity, b as IEntity) as int:
		return string.Compare(a.Name, b.Name)

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

namespace Boo.Lang.Interpreter

import System
import System.Collections
import System.Collections.Generic
import System.IO
import System.Text
import System.Text.RegularExpressions
import Boo.Lang
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.TypeSystem
import Boo.Lang.Compiler.IO

class InteractiveInterpreter2(AbstractInterpreter):

	_values = {}

	_declarations = {}

	_representers = []

	[property(ShowWarnings)]
	_showWarnings = false

	[property(BlockStarters, value is not null)]
	_blockStarters = (char(':'), char('\\'),)

	[getter(LastValue)]
	_lastValue

	[property(Print, value is not null)]
	_print as callable(object) = print

	_entityNameComparer = EntityNameComparer()

	def constructor():
		super()
		Initialize()

	def constructor(parser as ICompilerStep):
		super(parser)
		Initialize()

	def Initialize():
		_disableColors = true if Environment.GetEnvironmentVariable("BOOISH_DISABLE_COLORS") is not null
		_disableAutocompletion = true if Environment.GetEnvironmentVariable("BOOISH_DISABLE_AUTOCOMPLETION") is not null
		InitializeStandardReferences()
		LoadHistory()

	def Reset():
		_values.Clear()
		_declarations.Clear()
		_lastValue = null
		_indent = 0
		InitializeStandardReferences()

	public final static HISTORY_FILENAME = "booish_history"
	public final static HISTORY_CAPACITY = 100
	protected _history = System.Collections.Generic.Queue of string(HISTORY_CAPACITY)
	protected _historyFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), HISTORY_FILENAME)
	protected _historyIndex = 0
	protected _session = System.Collections.Generic.List of string()

	private _buffer = StringBuilder()	#buffer to be executed
	private _line = StringBuilder()		#line being edited

	CurrentPrompt as string:
		get:
			if _indent > 0:
				return BlockPrompt
			return DefaultPrompt

	[property(DefaultPrompt)]
	_defaultPrompt = ">>>"

	[property(BlockPrompt)]
	_blockPrompt = "..."

	[property(DisableColors)]
	_disableColors = false

	[property(DisableAutocompletion)]
	_disableAutocompletion = false

	[property(InterpreterColor)] # messages from the interpreter (not from user code)
	_interpreterColor = ConsoleColor.Gray

	[property(PromptColor)]
	_promptColor = ConsoleColor.Green

	[property(ExceptionColor)]
	_exceptionColor = ConsoleColor.Red

	[property(SuggestionsColor)]
	_suggestionsColor = ConsoleColor.Yellow

	[property(SelectedSuggestionColor)]
	_selectedSuggestionColor = ConsoleColor.Magenta

	[property(SelectedSuggestionIndex)]
	_selectedSuggestionIndex = -1

	[property(Suggestions)]
	_suggestions as (object)

	CanAutoComplete as bool:
		get:
			return _selectedSuggestionIndex >= 0 and _suggestions is not null

	private _builtins as (IEntity)
	private _filter as string

	private def ConsolePrintPrompt():
		Console.ForegroundColor = _promptColor if not _disableColors
		Console.Write(CurrentPrompt)
		#TODO: automatic indentation option ?
		#for i in range(_indent):
		#	_line.Append("\t")
		#	Console.Write("\t")
		Console.ResetColor() if not _disableColors

	private def ConsolePrintMessage(msg as string):
		Console.ForegroundColor = _interpreterColor if not _disableColors
		print msg
		Console.ResetColor() if not _disableColors

	private def ConsolePrintException(e as Exception):
		Console.ForegroundColor = _exceptionColor if not _disableColors
		print e
		Console.ResetColor() if not _disableColors

	private def ConsolePrintError(msg as string):
		Console.ForegroundColor = _exceptionColor if not _disableColors
		print msg
		Console.ResetColor() if not _disableColors

	protected def ConsolePrintSuggestions():
		cursorLeft = Console.CursorLeft
		#cursorTop = Console.CursorTop
		Console.Write(Environment.NewLine)

		i = 0

		Array.Sort(_suggestions) if _suggestions isa (string)
		Array.Sort[of IEntity](_suggestions, _entityNameComparer) if _suggestions isa (IEntity)

		for s in _suggestions as (object):
			Console.ForegroundColor = _suggestionsColor if not _disableColors
			Console.Write(", ") if i > 0
			if i > 20: #TODO: maxcandidates pref + paging?
				Console.Write("... (too much candidates)")
				break
			if i == _selectedSuggestionIndex:
				Console.ForegroundColor = _selectedSuggestionColor if not _disableColors
			if s isa IEntity:
				Console.Write(DescribeEntity(s as IEntity))
			else:
				Console.Write(s)
			i++

		Console.ResetColor() if not _disableColors
		#Console.CursorTop = cursorTop
		Console.Write(Environment.NewLine)
		ConsolePrintPrompt()
		Console.Write(_line.ToString())
		Console.CursorLeft = cursorLeft

	private static re_open = Regex("\\(", RegexOptions.Singleline)
	private static re_close = Regex("\\)", RegexOptions.Singleline)

	def DisplaySuggestions(query as string):
		return if DisableAutocompletion

		#TODO: FIXME: refactor to one regex?
		p_open = re_open.Matches(query).Count
		p_close = re_close.Matches(query).Count
		if p_open > p_close:
			query = query.Split(" ,(\t".ToCharArray(), 100)[-1]
		else:
			query = query.Split(" ,\t".ToCharArray(), 100)[-1]
		if query.LastIndexOf('.') > 0:
			codeToComplete = query[0:query.LastIndexOf('.')+1]
			_suggestions = SuggestCodeCompletion(codeToComplete+"__codecomplete__")
			_filter = query[query.LastIndexOf('.')+1:]
			_suggestions = array(e for e in _suggestions as (IEntity)
								unless not e.Name.StartsWith(_filter)) as (IEntity)

		if not _suggestions or 0 == len(_suggestions): #suggest a  var
			_filter = query
			_suggestions = array(var.ToString() for var in _values.Keys
									unless not var.ToString().StartsWith(_filter))

		if not _suggestions or 0 == len(_suggestions):
			_selectedSuggestionIndex = -1
			#Console.Beep() #TODO: flash background?
		elif 1 == len(_suggestions):
			AutoComplete()
		else:
			ConsolePrintSuggestions()

	def AutoComplete():
		if not _suggestions:
			raise InvalidOperationException("no suggestions")

		s = _suggestions[_selectedSuggestionIndex] as IEntity
		if s:
			if s.EntityType == EntityType.Method:
				m = s as IMethod
				completion = s.Name[len(_filter):]
				Console.Write(completion) #FIXME: insert at cursor!
				_line.Append(completion)
				if not m.GetParameters() or m.GetParameters().Length == 0:
					Console.Write('()') #FIXME: insert at cursor!
					_line.Append('()')
				else:
					Console.Write('(') #FIXME: insert at cursor!
					_line.Append('(')

			else: # not a Method
				completion = s.Name[len(_filter):]
				Console.Write(completion) #FIXME: insert at cursor!
				_line.Append(completion)
		else:
			completion = (_suggestions[_selectedSuggestionIndex] as string)[len(_filter):]
			Console.Write(completion) #FIXME: insert at cursor!
			_line.Append(completion)

		_selectedSuggestionIndex = -1
		_suggestions = null

	private _beforeHistory = string.Empty

	def DisplayHistory():
		if _history.Count == 0 or _historyIndex < 0 or _historyIndex > _history.Count:
			return
		Console.CursorLeft = len(CurrentPrompt)
		Console.Write(string.Empty.PadLeft(_line.Length, char(' ')))
		line = _history.ToArray()[_historyIndex]
		_line.Length = 0
		_line.Append(line)
		Console.CursorLeft = len(CurrentPrompt)
		Console.Write(line)

	def ConsoleLoopEval():
		Console.CursorVisible = true
		ConsolePrintPrompt()

		lastChar = char('0')
		key = ConsoleKey.LeftArrow
		while not _quit:
			cki = Console.ReadKey(true) #TODO: fix mono different behavior when ()
			key = cki.Key
			keyChar = cki.KeyChar
			control = false

			newLine = keyChar in Environment.NewLine

			if char.IsControl(keyChar):
				control = true
				if keyChar == char('\t'):
					test = char(' ')
					test = _line.ToString()[_line.Length-1] if _line.Length > 0
					if char.IsLetterOrDigit(test) or test == char('.'):
						_selectedSuggestionIndex = 0
						DisplaySuggestions(_line.ToString())
					else:
						_line.Append("    ") #TODO: _tabWidth pref
						Console.Write("    ")

				#line-editing support
				if key == ConsoleKey.Backspace:
					if Console.CursorLeft > len(CurrentPrompt) and _line.Length > 0:
						cx = Console.CursorLeft-len(CurrentPrompt)-1
						_line.Remove(cx, 1)
						cx2 = --Console.CursorLeft
						Console.Write("${_line.ToString(cx, _line.Length-cx)} ")
						Console.CursorLeft = cx2
				elif key == ConsoleKey.Delete:
					if Console.CursorLeft >= len(CurrentPrompt) and _line.Length > 0:
						cx = Console.CursorLeft-len(CurrentPrompt)
						if cx < _line.Length:
							_line.Remove(cx, 1)
							cx2 = Console.CursorLeft
							Console.Write("${_line.ToString(cx, _line.Length-cx)} ")
							Console.CursorLeft = cx2
				elif key == ConsoleKey.LeftArrow:
					if Console.CursorLeft > len(CurrentPrompt) and _line.Length > 0:
						Console.CursorLeft--
				elif key == ConsoleKey.RightArrow:
					if Console.CursorLeft < (len(CurrentPrompt)+_line.Length):
						Console.CursorLeft++
				elif key == ConsoleKey.Home:
					Console.CursorLeft = len(CurrentPrompt)
				elif key == ConsoleKey.End:
					Console.CursorLeft = len(CurrentPrompt) + _line.Length

				#history support
				if key == ConsoleKey.UpArrow:
					if _historyIndex > 0:
						_historyIndex--
						DisplayHistory()
				if key == ConsoleKey.DownArrow:
					if _historyIndex < _history.Count-1:
						_historyIndex++
						DisplayHistory()

				#auto-completion support
				if CanAutoComplete:
					if key == ConsoleKey.LeftArrow:
						if _selectedSuggestionIndex > 0:
							_selectedSuggestionIndex--
						else:
							_selectedSuggestionIndex = len(_suggestions)
						DisplaySuggestions(_line.ToString())
					if key == ConsoleKey.RightArrow:
						if _selectedSuggestionIndex+1 < len(_suggestions):
							_selectedSuggestionIndex++
						else:
							_selectedSuggestionIndex = 0
						DisplaySuggestions(_line.ToString())
					if newLine:
						AutoComplete()
						continue
				if not newLine:
					continue

			_selectedSuggestionIndex = -1

			cx = Console.CursorLeft-len(CurrentPrompt)
			line = _line.ToString()

			if not newLine:
				if cx < len(line):	#line-editing support
					_line.Insert(cx, keyChar) if not control
					Console.Write(_line.ToString(cx, _line.Length-cx))
					Console.CursorLeft = len(CurrentPrompt)+cx+1
				else:
					_line.Append(keyChar) if not control
					Console.Write(keyChar)

			line = _line.ToString()

			if newLine:
				Console.Write(Environment.NewLine)

				if not TryRunCommand(line):
					_buffer.Append(line)
					_buffer.Append(Environment.NewLine)
					AddToHistory(line)

					try:
						if len(line) > 0:
							_indent++ if line[len(line)-1] in _blockStarters
							_indent-- if line[len(line)-1] == keyChar and _indent > 0
						else:
							_indent--
						if _indent == 0:
							InternalLoopEval(_buffer.ToString())
							_buffer.Length = 0
					except x as System.Reflection.TargetInvocationException:
						ConsolePrintException(x.InnerException)
					except x:
						ConsolePrintException(x)

					_line.Length = 0
					ConsolePrintPrompt()

			lastChar = keyChar

		SaveHistory()
		DisplayGoodbye()

	/* returns false if no command has been processed, true otherwise */
	def TryRunCommand(line as string):
		if not line.StartsWith("/"):
			return false

		cmd = line.Split()

		if len(cmd) == 1:
			if cmd[0] == "/q" or cmd[0] == "/quit":
				quit()
			elif cmd[0] == "/?" or cmd[0] == "/h" or cmd[0] == "/help":
				DisplayHelp()
				_selectedSuggestionIndex = -1
				_buffer.Length = 0
				ConsolePrintPrompt()
			elif cmd[0] == "/g" or cmd[0] == "/globals":
				globals()
			else:
				return false

		elif len(cmd) == 2:
			if cmd[0] == "/l" or cmd[0] == "/load":
				load(cmd[1])
				_line.Length = 0
				ConsolePrintPrompt()
			elif cmd[0] == "/s" or cmd[0] == "/save":
				save(cmd[1])
				_line.Length = 0
				ConsolePrintPrompt()
			else:
				return false

		else:
			return false
		return true

	private _indent as int = 0

	def LoopEval(code as string):
		using console = ConsoleCapture():
			result = InternalLoopEval(code)
			for line in System.IO.StringReader(console.ToString()):
				_print(line)
		return result

	private def InternalLoopEval(code as string):
		result = self.Eval(code)
		if ShowWarnings:
			self.DisplayProblems(result.Warnings)
		if not self.DisplayProblems(result.Errors):
			ProcessLastValue()
			_session.Add(code)
		return result

	private def ProcessLastValue():
		_ = self.LastValue
		if _ is not null:
			_print(repr(_))
			SetValue("_", _)

	override def Declare([required] name as string,
				[required] type as System.Type):
		_declarations.Add(name, type)

	override def SetLastValue(value):
		_lastValue = value

	override def SetValue(name as string, value):
		_values[name] = value
		return value

	override def GetValue(name as string):
		return _values[name]

	override def Lookup([required] name as string):
		type as System.Type = _declarations[name]
		return type if type is not null

		value = GetValue(name)
		return value.GetType() if value is not null

	def DisplayProblems(problems as ICollection):
		return if problems is null or problems.Count == 0
		Console.ForegroundColor = _exceptionColor if not _disableColors
		for problem as duck in problems:
			markLocation(problem.LexicalInfo)
			type = ("WARNING", "ERROR")[problem isa CompilerError]
			_print("${type}: ${problem.Message}")
		Console.ResetColor() if not _disableColors
		if problems.Count > 0:
			return true
		return false

	private def markLocation(location as LexicalInfo):
		pos = location.Column
		_print("---" + "-" * pos + "^") if pos > 0

	private def InitializeStandardReferences():
		SetValue("interpreter", self)
		SetValue("dir", dir)
		SetValue("help", help)
		SetValue("print", { value | _print(value) })
		SetValue("load", load)
		SetValue("globals", globals)
		SetValue("getRootNamespace", Namespace.GetRootNamespace)

	def DisplayHelp():
		Console.ForegroundColor = _interpreterColor	if not _disableColors
		print """Welcome to booish, an interpreter for the boo programming language.
Running boo ${BooVersion} in CLR v${Environment.Version}.

The following builtin functions are available:
    /d[ir] type : lists the members of a type
    /h[elp] type : prints detailed information about a type
    /l[oad] file : evals an external boo file
    /s[ave] file : writes your current booish session into file
    /g[lobals] : returns names of all variables known to interpreter
    /n[ames] : namespace navigation
    /q[uit] : exits the interpreter

Enter boo code in the prompt below."""
		Console.ResetColor() if not _disableColors

	def DisplayGoodbye():	// booish is friendly
		Console.ForegroundColor = _interpreterColor if not _disableColors
		print ""
		print "All your boo are belong to us!"
		Console.ResetColor() if not _disableColors

	def LoadHistory():
		try:
			using history = File.OpenText(_historyFile):
				while line = history.ReadLine():
					AddToHistory(line)
		except:
			ConsolePrintError("Cannot load history from '${_historyFile}'")

	def AddToHistory(line as string):
		return if 0 == (len(line) - len(Environment.NewLine))
		_history.Dequeue() if _history.Count >= HISTORY_CAPACITY
		line = line.Replace(Environment.NewLine, "")
		_history.Enqueue(line)
		_historyIndex = _history.Count

	def SaveHistory():
		try:
			using sw = System.IO.File.CreateText(_historyFile):
				for line in _history:
					sw.WriteLine(line)
		except:
			ConsolePrintError("Cannot save history to '${_historyFile}'.")

	def globals():
		return array(string, _values.Keys)

	def dir([required] obj):
		type = (obj as Type) or obj.GetType()
		return array(member for member in type.GetMembers()
						unless (method=(member as System.Reflection.MethodInfo))
							and method.IsSpecialName)

	def load([required] path as string):
		if path.EndsWith(".boo"):
			ConsolePrintMessage("Evaluating '${path}' ...")
			result = EvalCompilerInput(FileInput(path))
			if ShowWarnings:
				DisplayProblems(result.Warnings)
			if not DisplayProblems(result.Errors):
				ProcessLastValue()
		else:
			try:
				ConsolePrintMessage("Adding reference to '${path}'")
				References.Add(System.Reflection.Assembly.LoadFrom(path))
			except e:
				ConsolePrintError(e.Message)

	def save([required] path as string):
		if path is string.Empty:
			path = "booish_session.boo"
		elif not path.EndsWith(".boo"):
			path = "${path}.boo"
		try:
			using sw = System.IO.File.CreateText(path):
				for line in _session:
					sw.Write(line)
			ConsolePrintMessage("Session saved to '${path}'.")
		except:
			ConsolePrintError("Cannot save to '${path}'. Check if path is valid and has correct permissions.")

	def help(obj):
		type = (obj as Type) or obj.GetType()
		DescribeType("    ", type)

	private _quit = false
	def quit():
		_quit = true

	def DescribeType(indent as string, type as Type):
		if type.IsInterface:
			typeDef = "interface"
			baseTypes = array(GetBooTypeName(t) for t in type.GetInterfaces())
		else:
			typeDef = "class"
			baseTypes = (GetBooTypeName(type.BaseType),) + array(GetBooTypeName(t) for t in type.GetInterfaces())

		_print("${typeDef} ${type.Name}(${join(baseTypes, ', ')}):")
		_print("")

		for ctor in type.GetConstructors():
			_print("${indent}def constructor(${DescribeParameters(ctor.GetParameters())})")
			_print("")

		sortByName = def (lhs as Reflection.MemberInfo, rhs as Reflection.MemberInfo):
			return lhs.Name.CompareTo(rhs.Name)

		for f as Reflection.FieldInfo in List(type.GetFields()).Sort(sortByName):
			_print("${indent}public ${DescribeField(f)}")
			_print("")

		for p as Reflection.PropertyInfo in List(type.GetProperties()).Sort(sortByName):
			_print("${indent}${DescribeProperty(p)}:")
			_print("${indent}${indent}get") if p.GetGetMethod() is not null
			_print("${indent}${indent}set") if p.GetSetMethod() is not null
			_print("")

		for m as Reflection.MethodInfo in List(type.GetMethods()).Sort(sortByName):
			continue if m.IsSpecialName
			_print("${indent}${DescribeMethod(m)}")
			_print("")

		for e as Reflection.EventInfo in List(type.GetEvents()).Sort(sortByName):
			_print("${indent}${DescribeEvent(e)}")
			_print("")

	static def DescribeEntity(entity as IEntity):
		method = entity as ExternalMethod
		if method is not null:
			return InteractiveInterpreter.DescribeMethod(method.MethodInfo)
		field = entity as ExternalField
		if field is not null:
			return InteractiveInterpreter.DescribeField(field.FieldInfo)
		property = entity as ExternalProperty
		if property is not null:
			return InteractiveInterpreter.DescribeProperty(property.PropertyInfo)
		e = entity as ExternalEvent
		if e is not null:
			return InteractiveInterpreter.DescribeEvent(e.EventInfo)
		return entity.ToString()

	static def DescribeEvent(e as Reflection.EventInfo):
		return "${DescribeModifiers(e)}event ${e.Name} as ${e.EventHandlerType}"

	static def DescribeProperty(p as Reflection.PropertyInfo):
		modifiers = DescribeModifiers(p)
		params = DescribePropertyParameters(p.GetIndexParameters())
		return "${modifiers}${p.Name}${params} as ${GetBooTypeName(p.PropertyType)}"

	static def DescribeField(f as Reflection.FieldInfo):
		return "${DescribeModifiers(f)}${f.Name} as ${GetBooTypeName(f.FieldType)}"

	static def DescribeMethod(m as Reflection.MethodInfo):
		returnType = GetBooTypeName(m.ReturnType)
		modifiers = DescribeModifiers(m)
		return "${modifiers}def ${m.Name}(${DescribeParameters(m.GetParameters())}) as ${returnType}"

	static def DescribeModifiers(f as Reflection.FieldInfo):
		return "static " if f.IsStatic
		return ""

	static def DescribeModifiers(m as Reflection.MethodBase):
		return "static " if m.IsStatic
		return ""

	static def DescribeModifiers(e as Reflection.EventInfo):
		return DescribeModifiers(e.GetAddMethod(true) or e.GetRemoveMethod(true))

	static def DescribeModifiers(p as Reflection.PropertyInfo):
		accessor = p.GetGetMethod(true) or p.GetSetMethod(true)
		return DescribeModifiers(accessor)

	static def DescribePropertyParameters(parameters as (Reflection.ParameterInfo)):
		return "" if 0 == len(parameters)
		return "(${DescribeParameters(parameters)})"

	static def DescribeParameters(parameters as (Reflection.ParameterInfo)):
		return join(DescribeParameter(p) for p in parameters, ", ")

	static def DescribeParameter(p as Reflection.ParameterInfo):
		return "${p.Name} as ${GetBooTypeName(p.ParameterType)}"

	static def GetBooTypeName(type as System.Type) as string:
		return "(${GetBooTypeName(type.GetElementType())})" if type.IsArray
		return "object" if object is type
		return "string" if string is type
		return "void" if void is type
		return "bool" if bool is type
		return "byte" if byte is type
		return "char" if char is type
		return "sbyte" if sbyte is type
		return "short" if short is type
		return "ushort" if ushort is type
		return "int" if int is type
		return "uint" if uint is type
		return "long" if long is type
		return "ulong" if ulong is type
		return "single" if single is type
		return "double" if double is type
		return "date" if date is type
		return "timespan" if timespan is type
		return "regex" if regex is type
		return type.FullName

	def repr(value):
		writer = System.IO.StringWriter()
		repr(value, writer)
		return writer.ToString()

	def repr(value, writer as System.IO.TextWriter):
		return unless value is not null
		InitializeRepresenters() if 0 == len(_representers)
		GetBestRepresenter(value.GetType())(value, writer)

	private def InitializeRepresenters():
		AddRepresenter(string) do (value as string, writer as TextWriter):
			Visitors.BooPrinterVisitor.WriteStringLiteral(value, writer)

		AddRepresenter(bool) do (value as bool, writer as TextWriter):
			writer.Write(("false", "true")[value])

		AddRepresenter(Array) do (a as Array, writer as TextWriter):
			writer.Write("(")
			RepresentItems(a, writer)
			writer.Write(")")

		AddRepresenter(Delegate) do (d as Delegate, writer as TextWriter):
			method = d.Method
			writer.Write(method.DeclaringType.FullName)
			writer.Write(".")
			writer.Write(method.Name)

		AddRepresenter(IDictionary) do (value as IDictionary, writer as TextWriter):
			writer.Write("{")
			i = 0
			for key in value.Keys:
				writer.Write(", ") if i
				repr(key, writer)
				writer.Write(": ")
				repr(value[key], writer)
				++i
			writer.Write("}")

		AddRepresenter(IList) do (value as IList, writer as TextWriter):
			writer.Write("[")
			RepresentItems(value, writer)
			writer.Write("]")

		AddRepresenter(object) do (value, writer as TextWriter):
			writer.Write(value)

	private def RepresentItems(items, writer as TextWriter):
		i = 0
		for item in items:
			writer.Write(", ") if i > 0
			repr(item, writer)
			++i

	callable Representer(value, writer as TextWriter)

	private def AddRepresenter(type as Type, value as Representer):
		_representers.Add((type, value))

	def GetBestRepresenter(type as Type) as Representer:
		for key as Type, value in _representers:
			return value if key.IsAssignableFrom(type)
		assert false, "An appropriate representer could not be found!"

class EntityNameComparer(IComparer of IEntity):
	def Compare(a as IEntity, b as IEntity) as int:
		return string.Compare(a.Name, b.Name)


boox currently is not up to date. One potential fix using BOO. However, does not work with current Sharpdevelop releases.

#region license
// Copyright (c) 2004, Rodrigo B. de Oliveira (rbo@acm.org)
// All rights reserved.
//
// This file is part of Boo Explorer.
//
// Boo Explorer is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// Boo Explorer is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Foobar; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#endregion

namespace BooExplorer.Common

import Boo.Lang.Compiler
import Boo.Lang.Compiler.Pipelines
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.IO
import Boo.Lang.Compiler.Steps
import Boo.Lang.Compiler.TypeSystem
import Boo.Lang.Compiler.TypeSystem.Services
import System.IO

class CodeCompletionHunter(ProcessMethodBodiesWithDuckTyping):

	static def GetCompletion(source as string):

		hunter = CodeCompletionHunter()
		compiler = BooCompiler()
		compiler.Parameters.OutputWriter = StringWriter()
		compiler.Parameters.Pipeline = MakePipeline(hunter)
		compiler.Parameters.Input.Add(StringInput("none", source))
		result = compiler.Run()
		print(result.Errors.ToString(true))

		return hunter.Members

	[getter(Members)]
	_members = array(IEntity, 0)

	override protected def ProcessMemberReferenceExpression(node as MemberReferenceExpression):
		if node.Name == '__codecomplete__':
			_members = MemberCollector.CollectAllMembers(MyGetReferenceNamespace(node))
		else:
			super(node)

	protected def MyGetReferenceNamespace(expression as MemberReferenceExpression) as INamespace:
		target as Expression = expression.Target
		if target.ExpressionType is not null:
			if target.ExpressionType.EntityType != EntityType.Error:
				return cast(INamespace, target.ExpressionType)
		return cast(INamespace, TypeSystemServices.GetOptionalEntity(target))

	protected static def MakePipeline(hunter):
		pipeline = ResolveExpressions(BreakOnErrors: false)
		index = pipeline.Find(Boo.Lang.Compiler.Steps.ProcessMethodBodiesWithDuckTyping)
		pipeline[index] = hunter
		return pipeline

#region license
// Copyright (c) 2004, Rodrigo B. de Oliveira (rbo@acm.org)
// All rights reserved.
//
// This file is part of Boo Explorer.
//
// Boo Explorer is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// Boo Explorer is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Foobar; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#endregion

namespace BooExplorer.Common

import Boo.Lang.Compiler
import Boo.Lang.Compiler.Pipelines
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.IO
import Boo.Lang.Compiler.Steps
import Boo.Lang.Compiler.TypeSystem
import System.IO

class CodeCompletionHunter(ProcessMethodBodiesWithDuckTyping):

	static def GetCompletion(source as string):

		hunter = CodeCompletionHunter()
		compiler = BooCompiler()
		compiler.Parameters.OutputWriter = StringWriter()
		compiler.Parameters.Pipeline = MakePipeline(hunter)
		compiler.Parameters.Input.Add(StringInput("none", source))
		result = compiler.Run()
		print(result.Errors.ToString(true))

		return hunter.Members

	[getter(Members)]
	_members = array(IEntity, 0)

	override protected def ProcessMemberReferenceExpression(node as MemberReferenceExpression):
		if node.Name == '__codecomplete__':
			_members = TypeSystemServices.GetAllMembers(MyGetReferenceNamespace(node))
		else:
			super(node)

	protected def MyGetReferenceNamespace(expression as MemberReferenceExpression) as INamespace:
		target as Expression = expression.Target
		if target.ExpressionType is not null:
			if target.ExpressionType.EntityType != EntityType.Error:
				return cast(INamespace, target.ExpressionType)
		return cast(INamespace, TypeSystemServices.GetOptionalEntity(target))

	protected static def MakePipeline(hunter):
		pipeline = ResolveExpressions(BreakOnErrors: false)
		index = pipeline.Find(Boo.Lang.Compiler.Steps.ProcessMethodBodiesWithDuckTyping)
		pipeline[index] = hunter
		return pipeline


BOO-69: src attribute for boo task

BOO-69: src attribute for boo task

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

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

namespace Boo.NAnt

import System.Diagnostics
import System.IO
import NAnt.Core
import NAnt.Core.Attributes
import NAnt.Core.Types
import Boo.Lang.Compiler
import Boo.Lang.Compiler.IO
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.Pipelines
import Boo.Lang.Compiler.Steps

class AbstractScript:

	[property(Project)]
	_project as Project

	[property(Task)]
	_task as BooTask

	def print(msg):
		_task.LogInfo(msg)

	abstract def Run():
		pass

class PrepareScriptStep(AbstractCompilerStep):

	override def Run():
		module = CompileUnit.Modules[0]

		method = Method(Name: "Run",
						Modifiers: TypeMemberModifiers.Override,
						Body: module.Globals)

		module.Globals = Block()

		script = ClassDefinition(Name: "__Script__")
		script.BaseTypes.Add(SimpleTypeReference("Boo.NAnt.AbstractScript"))
		script.Members.Add(method)

		for member in module.Members:
			script.Members.Add(member)

		module.Members.Clear()
		module.Members.Add(script)

[TaskName("boo")]
class BooTask(AbstractBooTask):

	_src as FileInfo

	[TaskAttribute("src", Required: false)]
	Source:
		get:
			return _src
		set:
			_src = value

	override protected def ExecuteTask():

		compiler = BooCompiler()
		parameters = compiler.Parameters
		parameters.OutputType = CompilerOutputType.Library
		parameters.Pipeline = CompileToMemory()
		parameters.Pipeline.Insert(1, PrepareScriptStep())

		if _src:
			parameters.Input.Add(FileInput(_src.ToString()))
		else:
			parameters.Input.Add(StringInput("boo", reindent(self.XmlNode.InnerText)))
		parameters.References.Add(GetType().Assembly)
		parameters.References.Add(typeof(NAnt.Core.Project).Assembly)

		result = RunCompiler(compiler)

		print("script successfully compiled.")

		try:
			scriptType = result.GeneratedAssembly.GetType("__Script__", true)
			script as AbstractScript = scriptType()
			script.Project = Project
			script.Task = self
			script.Run()
		except x:
			raise BuildException(x.Message, Location, x)

	def reindent(code as string):
		lines = /\n/.Split(code.Replace("\r\n", "\n"))
		lines = [line for line in lines if len(line.Trim())].ToArray(string)

		first = lines[0]
		indent = /(\s*)/.Match(first).Groups[0].Value
		return code if 0 == len(indent)

		buffer = System.Text.StringBuilder()
		for line in lines:
			if not line.StartsWith(indent):
				return code // let the parser complain about it
			else:
				buffer.Append(line[len(indent):])
				buffer.Append("\n")
		return buffer.ToString()

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

namespace Boo.NAnt

import System.Diagnostics
import System.IO
import NAnt.Core
import NAnt.Core.Attributes
import NAnt.Core.Types
import Boo.Lang.Compiler
import Boo.Lang.Compiler.IO
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.Pipelines
import Boo.Lang.Compiler.Steps

class AbstractScript:

	[property(Project)]
	_project as Project

	[property(Task)]
	_task as BooTask

	def print(msg):
		_task.LogInfo(msg)

	abstract def Run():
		pass

class PrepareScriptStep(AbstractCompilerStep):

	override def Run():
		module = CompileUnit.Modules[0]

		method = Method(Name: "Run",
						Modifiers: TypeMemberModifiers.Override,
						Body: module.Globals)

		module.Globals = Block()

		script = ClassDefinition(Name: "__Script__")
		script.BaseTypes.Add(SimpleTypeReference("Boo.NAnt.AbstractScript"))
		script.Members.Add(method)

		for member in module.Members:
			script.Members.Add(member)

		module.Members.Clear()
		module.Members.Add(script)

[TaskName("boo")]
class BooTask(AbstractBooTask):
	override protected def ExecuteTask():
		code = self.XmlNode.InnerText

		compiler = BooCompiler()
		parameters = compiler.Parameters
		parameters.OutputType = CompilerOutputType.Library
		parameters.Pipeline = CompileToMemory()
		parameters.Pipeline.Insert(1, PrepareScriptStep())
		parameters.Input.Add(StringInput("boo", reindent(code)))
		parameters.References.Add(GetType().Assembly)
		parameters.References.Add(typeof(NAnt.Core.Project).Assembly)

		result = RunCompiler(compiler)

		print("script successfully compiled.")

		try:
			scriptType = result.GeneratedAssembly.GetType("__Script__", true)
			script as AbstractScript = scriptType()
			script.Project = Project
			script.Task = self
			script.Run()
		except x:
			raise BuildException(x.Message, Location, x)

	def reindent(code as string):
		lines = /\n/.Split(code.Replace("\r\n", "\n"))
		lines = [line for line in lines if len(line.Trim())].ToArray(string)

		first = lines[0]
		indent = /(\s*)/.Match(first).Groups[0].Value
		return code if 0 == len(indent)

		buffer = System.Text.StringBuilder()
		for line in lines:
			if not line.StartsWith(indent):
				return code // let the parser complain about it
			else:
				buffer.Append(line[len(indent):])
				buffer.Append("\n")
		return buffer.ToString()