issue #57

issue #57

#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.Linq
import System.Text
import System.Text.RegularExpressions
import Boo.Lang.Compiler
import Boo.Lang.Compiler.TypeSystem
import PatternMatching
import Environments

class InteractiveInterpreterConsole:

	public final static HISTORY_FILENAME = "booish_history"
	public final static HISTORY_CAPACITY = 100

	_history = System.Collections.Generic.Queue of string(HISTORY_CAPACITY)
	_historyFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), HISTORY_FILENAME)
	_historyIndex = 0
	_session = System.Collections.Generic.List of string()

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

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

	[property(ShowWarnings)]
	_showWarnings = false

	QQBegin = "[|"
	QQEnd= "|]"

	_interpreter as InteractiveInterpreter

	def constructor():
		self(InteractiveInterpreter())

	def constructor(interpreter as InteractiveInterpreter):
		_interpreter = interpreter
		_interpreter.RememberLastValue = true

		DisableColors = not string.IsNullOrEmpty(Environment.GetEnvironmentVariable("BOOISH_DISABLE_COLORS"))
		if not DisableColors: #make sure setting color does not throw an exception
			try:
				Console.ForegroundColor = ConsoleColor.DarkGray
			except:
				DisableColors = true
		DisableAutocompletion = not string.IsNullOrEmpty(Environment.GetEnvironmentVariable("BOOISH_DISABLE_AUTOCOMPLETION"))

		_interpreter.SetValue("load", Load)
		_interpreter.SetValue("save", Save)
		_interpreter.SetValue("quit", Quit)

		LoadHistory()

	def SetValue(name as string, value):
		_interpreter.SetValue(name, value)

	Line:
		get: return _line.ToString()

	LineLastChar:
		get: return (_line.Chars[LineLen-1] if LineLen != 0 else char('\0'))

	LineLen:
		get: return _line.Length
		set: _line.Length = value

	LineIndentLen:
		get: return IndentChars.Length * _indent

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

	PrintModules:
		get: return _interpreter.Pipeline.Find(Boo.Lang.Compiler.Steps.PrintBoo) != -1
		set:
			if value:
				if not PrintModules:
					_interpreter.Pipeline.Add(Boo.Lang.Compiler.Steps.PrintBoo())
			else:
				_interpreter.Pipeline.Remove(Boo.Lang.Compiler.Steps.PrintBoo)

	property DefaultPrompt = ">>> "

	property BlockPrompt = "... "

	property IndentChars = "    "

	property DisableColors = false

	property DisableAutocompletion = false

	# messages from the interpreter (not from user code)
	property InterpreterColor = ConsoleColor.DarkGray

	property PromptColor = ConsoleColor.DarkGreen

	property ExceptionColor = ConsoleColor.DarkRed

	property WarningColor = ConsoleColor.Yellow

	property ErrorColor = ConsoleColor.Red

	property SuggestionsColor = ConsoleColor.DarkYellow

	property SelectedSuggestionColor = ConsoleColor.DarkMagenta

	_selectedSuggestionIndex as int?

	_suggestions as EnvironmentBoundValue[of (object)]

	CanAutoComplete as bool:
		get: return _selectedSuggestionIndex is not null

	private _builtins as (IEntity)
	private _filter as string

	def Eval(code as string):
		try:
			result = _interpreter.Eval(code)
			DisplayResults(result)
			return result
		except x as System.Reflection.TargetInvocationException:
			ConsolePrintException(x.InnerException)
		except x:
			ConsolePrintException(x)

	private def DisplayResults(results as CompilerContext):
		if ShowWarnings:
			DisplayProblems(results.Warnings, WarningColor)
		if not DisplayProblems(results.Errors, ErrorColor):
			ProcessLastValue()

	private def ConsolePrintPrompt():
		ConsolePrintPrompt(true)

	private def ConsolePrintPrompt(autoIndent as bool):
		return if _quit
		WithColor PromptColor:
			Console.Write(CurrentPrompt)
		if autoIndent and CurrentPrompt == BlockPrompt:
			for i in range(_indent):
				WriteIndent()

	private def WithColor(color as ConsoleColor, block as System.Action):
		if DisableColors:
			block()
		else:
			Console.ForegroundColor = color
			try:
				block()
			ensure:
				Console.ResetColor()

	private def ConsolePrintMessage(msg as string):
		WithColor InterpreterColor:
			print msg

	private def ConsolePrintException(e as Exception):
		WithColor ExceptionColor:
			print e

	private def ConsolePrintError(msg as string):
		WithColor ExceptionColor:
			print msg

	private def NewLine():
		Console.Write(Environment.NewLine)

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

		i = 0

		for s in SuggestionDescriptions():
			Console.ForegroundColor = SuggestionsColor if not DisableColors
			Console.Write(", ") if i > 0
			if i > 20: #TODO: maxcandidates pref + paging?
				Console.Write("... (too many candidates)")
				break
			if i == _selectedSuggestionIndex:
				Console.ForegroundColor = SelectedSuggestionColor if not DisableColors
			Console.Write(s)
			i++

		Console.ResetColor() if not DisableColors
		#Console.CursorTop = cursorTop
		NewLine()
		ConsolePrintPrompt(false)
		Console.Write(Line)
		Console.CursorLeft = cursorLeft

	def SuggestionDescriptions():
		return _suggestions.Select(DescriptionsFor).Value

	def DescriptionsFor(suggestions as (object)):
		return array((DescriptionFor(s) for s in suggestions).Distinct())

	def DescriptionFor(s):
		match s:
			case e = IEntity():
				return Boo.Lang.Interpreter.Builtins.DescribeEntity(e)
			otherwise:
				return s.ToString()

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

	protected def WriteIndent():
		Write(IndentChars)

	protected def Indent():
		WriteIndent()
		_indent++

	protected def Unindent():
		return if _indent == 0
		Delete(IndentChars.Length)
		_indent--

	protected def Delete(count as int): #if count is 0, forward-delete
		return if LineLen == 0
		cx = Console.CursorLeft-len(CurrentPrompt)-count
		count=1 if cx >= LineLen and count == 0
		return if cx >= LineLen
		dcount = (count if count != 0 else 1)
		_line.Remove(cx, dcount)
		curX = Console.CursorLeft - count
		Console.CursorLeft = curX
		Console.Write("${_line.ToString(cx, LineLen-cx)} ")
		Console.CursorLeft = curX

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

	def DisplaySuggestions():
		DisplaySuggestions(Line)

	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]
			_filter = query[query.LastIndexOf('.')+1:]
			_suggestions = (
				_interpreter
				.SuggestCompletionsFor(codeToComplete+"__codecomplete__")
				.Select[of (object)](
					{ es | array(e as object for e in es if e.Name.StartsWith(_filter)) }))

		if _suggestions.Value is null or 0 == len(_suggestions.Value): #suggest a  var
			_filter = query
			_suggestions = EnvironmentBoundValue.Create(
							null,
							array(
								var.Key.ToString() as object
								for var in _interpreter.Values
								if var.ToString().StartsWith(_filter)))

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

	def AutoComplete():
		raise InvalidOperationException("no suggestions") if _suggestions.Value is null or _selectedSuggestionIndex is null

		Write(_suggestions.Select[of string]({ ss | AutoCompletionFor(ss[_selectedSuggestionIndex.Value]) }).Value)

		_selectedSuggestionIndex = null
		_suggestions = EnvironmentBoundValue[of (object)](null, null)

	def AutoCompletionFor(s):
		match s:
			case m = IMethod(Name: name):
				if len(m.GetParameters()) == 0:
					return "$(TrimFilter(name))()"
				return "$(TrimFilter(name))("
			case IEntity(Name: name):
				return TrimFilter(name)
			otherwise:
				return TrimFilter(s.ToString())

	def TrimFilter(s as string):
		return s[len(_filter):]

	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(LineLen, char(' ')))
		line = _history.ToArray()[_historyIndex]
		LineLen = 0
		Console.CursorLeft = len(CurrentPrompt)
		Write(line)

	def ReadEvalPrintLoop():
		Console.CursorVisible = true
		ConsolePrintPrompt()
		while not _quit:
			ReadEvalPrintLoopStep()
		SaveHistory()
		DisplayGoodbye()

	private def ReadEvalPrintLoopStep():
		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'):
				if LineLen > 0 and (char.IsLetterOrDigit(LineLastChar) or LineLastChar == char('.')):
					_selectedSuggestionIndex = 0
					DisplaySuggestions()
				else:
					Indent()

			# delete the whole line
			if key == ConsoleKey.Escape:
				curX = len(CurrentPrompt)
				sizeL = _line.Length
				_line.Remove(0, sizeL)
				Console.CursorLeft = curX
				Console.Write(string(' '[0], sizeL))
				Console.CursorLeft = curX

			#line-editing support
			if not _multiline and LineLen > 0:
				if Console.CursorLeft > len(CurrentPrompt):
					if key == ConsoleKey.Backspace:
						if _indent > 0 and LineLen == LineIndentLen:
							Unindent()
						else:
							Delete(1)
					elif key == ConsoleKey.LeftArrow:
						Console.CursorLeft--
				if key == ConsoleKey.Delete:
					Delete(0)
				elif key == ConsoleKey.RightArrow:
					if Console.CursorLeft < (len(CurrentPrompt)+LineLen):
						Console.CursorLeft++
				elif key == ConsoleKey.Home:
					Console.CursorLeft = len(CurrentPrompt)
				elif key == ConsoleKey.End:
					Console.CursorLeft = len(CurrentPrompt) + LineLen

			#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 _selectedSuggestionIndex > 0:
						_selectedSuggestionIndex--
					else:
						_selectedSuggestionIndex = len(_suggestions.Value) - 1
					DisplaySuggestions()
				elif key == ConsoleKey.RightArrow:
					if _selectedSuggestionIndex < len(_suggestions.Value) - 1:
						_selectedSuggestionIndex++
					else:
						_selectedSuggestionIndex = 0
					DisplaySuggestions()
				if newLine:
					AutoComplete()
					return
			if not newLine:
				return

		_selectedSuggestionIndex = null

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

		if not newLine:
			#line-editing support
			if cx < LineLen and not _multiline:
				_line.Insert(cx, keyChar) if not control
				Console.Write(_line.ToString(cx, LineLen-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)

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

				_indent++ if LineLastChar in _blockStarters or Line.EndsWith(QQBegin)
				_indent-- if Line.EndsWith(IndentChars+"pass")
				if Line.EndsWith(QQEnd):
					CheckBooLangCompilerReferenced()
					_indent--

				if _indent <= 0:
					_indent = 0
					try:
						Eval(_buffer.ToString())
					ensure:
						_buffer.Length = 0 #truncate buffer

			LineLen = 0 #truncate line
			ConsolePrintPrompt()

	/* 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()
			elif cmd[0] == "/g" or cmd[0] == "/globals":
				Eval("globals()")
			else:
				return false

		elif len(cmd) == 2:
			if cmd[0] == "/l" or cmd[0] == "/load":
				Load(cmd[1])
			elif cmd[0] == "/s" or cmd[0] == "/save":
				Save(cmd[1])
			else:
				return false

		else:
			return false
		return true

	private _indent as int = 0

	def DisplayLogo():
		WithColor InterpreterColor:
			print """Welcome to booish, an interactive interpreter for the boo programming language.
Running boo ${BooVersion} on ${Boo.Lang.Runtime.RuntimeServices.RuntimeDisplayName}.

Enter boo code in the prompt below (or type /help)."""

	def DisplayHelp():
		WithColor InterpreterColor:
			print """The following builtin functions are available :
    /? or /h or /help : display this help
    dir(type) : returns the members of a type
    describe(type) : describe a type as boo code
    globals() or /g : returns names of all variables known to interpreter
    load(file) or /l file : evals an external boo file
    save(file) or /s file : writes your current booish session into file
    quit() or /q : exits the interpreter"""

	def DisplayGoodbye():	// booish is friendly
		WithColor InterpreterColor:
			print ""
			print "All your boo are belong to us!"

	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 Load([required] path as string):
		if path.EndsWith(".boo"):
			ConsolePrintMessage("Evaluating '${path}' ...")
			DisplayResults(_interpreter.EvalCompilerInput(Boo.Lang.Compiler.IO.FileInput(path)))
		else:
			ConsolePrintMessage("Adding reference to '${path}'")
			try:
				_interpreter.References.Add(System.Reflection.Assembly.LoadFrom(path))
			except e:
				ConsolePrintException(e)

	private def ProcessLastValue():
		_ = _interpreter.LastValue
		if _ is not null:
			Console.WriteLine(Boo.Lang.Interpreter.Builtins.repr(_))
			_interpreter.SetValue("_", _)

	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.")

	private _quit = false

	def Quit():
		_quit = true

	def DisplayProblems(problems as ICollection, color as ConsoleColor):
		return if problems is null or problems.Count == 0
		WithColor color:
			for problem as duck in problems:
				markLocation(problem.LexicalInfo)
				type = ("ERROR" if problem isa CompilerError else "WARNING")
				Console.WriteLine("${type}: ${problem.Message}")
		return true

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

	private def CheckBooLangCompilerReferenced():
		return if _blcReferenced
		_interpreter.References.Add(typeof(Boo.Lang.Compiler.CompilerContext).Assembly)
		_blcReferenced = true

	_blcReferenced = false

#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.Linq
import System.Text
import System.Text.RegularExpressions
import Boo.Lang.Compiler
import Boo.Lang.Compiler.TypeSystem
import PatternMatching
import Environments

class InteractiveInterpreterConsole:

	public final static HISTORY_FILENAME = "booish_history"
	public final static HISTORY_CAPACITY = 100

	_history = System.Collections.Generic.Queue of string(HISTORY_CAPACITY)
	_historyFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), HISTORY_FILENAME)
	_historyIndex = 0
	_session = System.Collections.Generic.List of string()

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

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

	[property(ShowWarnings)]
	_showWarnings = false

	QQBegin = "[|"
	QQEnd= "|]"

	_interpreter as InteractiveInterpreter

	def constructor():
		self(InteractiveInterpreter())

	def constructor(interpreter as InteractiveInterpreter):
		_interpreter = interpreter
		_interpreter.RememberLastValue = true

		DisableColors = not string.IsNullOrEmpty(Environment.GetEnvironmentVariable("BOOISH_DISABLE_COLORS"))
		if not DisableColors: #make sure setting color does not throw an exception
			try:
				Console.ForegroundColor = ConsoleColor.DarkGray
			except:
				DisableColors = true
		DisableAutocompletion = not string.IsNullOrEmpty(Environment.GetEnvironmentVariable("BOOISH_DISABLE_AUTOCOMPLETION"))

		_interpreter.SetValue("load", Load)
		_interpreter.SetValue("save", Save)
		_interpreter.SetValue("quit", Quit)

		LoadHistory()

	def SetValue(name as string, value):
		_interpreter.SetValue(name, value)

	Line:
		get: return _line.ToString()

	LineLastChar:
		get: return (_line.Chars[LineLen-1] if LineLen != 0 else char('\0'))

	LineLen:
		get: return _line.Length
		set: _line.Length = value

	LineIndentLen:
		get: return IndentChars.Length * _indent

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

	PrintModules:
		get: return _interpreter.Pipeline.Find(Boo.Lang.Compiler.Steps.PrintBoo) != -1
		set:
			if value:
				if not PrintModules:
					_interpreter.Pipeline.Add(Boo.Lang.Compiler.Steps.PrintBoo())
			else:
				_interpreter.Pipeline.Remove(Boo.Lang.Compiler.Steps.PrintBoo)

	property DefaultPrompt = ">>> "

	property BlockPrompt = "... "

	property IndentChars = "    "

	property DisableColors = false

	property DisableAutocompletion = false

	# messages from the interpreter (not from user code)
	property InterpreterColor = ConsoleColor.DarkGray

	property PromptColor = ConsoleColor.DarkGreen

	property ExceptionColor = ConsoleColor.DarkRed

	property WarningColor = ConsoleColor.Yellow

	property ErrorColor = ConsoleColor.Red

	property SuggestionsColor = ConsoleColor.DarkYellow

	property SelectedSuggestionColor = ConsoleColor.DarkMagenta

	_selectedSuggestionIndex as int?

	_suggestions as EnvironmentBoundValue[of (object)]

	CanAutoComplete as bool:
		get: return _selectedSuggestionIndex is not null

	private _builtins as (IEntity)
	private _filter as string

	def Eval(code as string):
		try:
			result = _interpreter.Eval(code)
			DisplayResults(result)
			return result
		except x as System.Reflection.TargetInvocationException:
			ConsolePrintException(x.InnerException)
		except x:
			ConsolePrintException(x)

	private def DisplayResults(results as CompilerContext):
		if ShowWarnings:
			DisplayProblems(results.Warnings, WarningColor)
		if not DisplayProblems(results.Errors, ErrorColor):
			ProcessLastValue()

	private def ConsolePrintPrompt():
		ConsolePrintPrompt(true)

	private def ConsolePrintPrompt(autoIndent as bool):
		return if _quit
		WithColor PromptColor:
			Console.Write(CurrentPrompt)
		if autoIndent and CurrentPrompt == BlockPrompt:
			for i in range(_indent):
				WriteIndent()

	private def WithColor(color as ConsoleColor, block as System.Action):
		if DisableColors:
			block()
		else:
			Console.ForegroundColor = color
			try:
				block()
			ensure:
				Console.ResetColor()

	private def ConsolePrintMessage(msg as string):
		WithColor InterpreterColor:
			print msg

	private def ConsolePrintException(e as Exception):
		WithColor ExceptionColor:
			print e

	private def ConsolePrintError(msg as string):
		WithColor ExceptionColor:
			print msg

	private def NewLine():
		Console.Write(Environment.NewLine)

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

		i = 0

		for s in SuggestionDescriptions():
			Console.ForegroundColor = SuggestionsColor if not DisableColors
			Console.Write(", ") if i > 0
			if i > 20: #TODO: maxcandidates pref + paging?
				Console.Write("... (too many candidates)")
				break
			if i == _selectedSuggestionIndex:
				Console.ForegroundColor = SelectedSuggestionColor if not DisableColors
			Console.Write(s)
			i++

		Console.ResetColor() if not DisableColors
		#Console.CursorTop = cursorTop
		NewLine()
		ConsolePrintPrompt(false)
		Console.Write(Line)
		Console.CursorLeft = cursorLeft

	def SuggestionDescriptions():
		return _suggestions.Select(DescriptionsFor).Value

	def DescriptionsFor(suggestions as (object)):
		return array((DescriptionFor(s) for s in suggestions).Distinct())

	def DescriptionFor(s):
		match s:
			case e = IEntity():
				return Boo.Lang.Interpreter.Builtins.DescribeEntity(e)
			otherwise:
				return s.ToString()

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

	protected def WriteIndent():
		Write(IndentChars)

	protected def Indent():
		WriteIndent()
		_indent++

	protected def Unindent():
		return if _indent == 0
		Delete(IndentChars.Length)
		_indent--

	protected def Delete(count as int): #if count is 0, forward-delete
		cx = Console.CursorLeft-len(CurrentPrompt)-count
		return if cx < LineLen and count == 0
		dcount = (count if count != 0 else 1)
		_line.Remove(cx, dcount)
		curX = Console.CursorLeft - dcount
		Console.CursorLeft = curX
		Console.Write("${_line.ToString(cx, LineLen-cx)} ")
		Console.CursorLeft = curX

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

	def DisplaySuggestions():
		DisplaySuggestions(Line)

	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]
			_filter = query[query.LastIndexOf('.')+1:]
			_suggestions = (
				_interpreter
				.SuggestCompletionsFor(codeToComplete+"__codecomplete__")
				.Select[of (object)](
					{ es | array(e as object for e in es if e.Name.StartsWith(_filter)) }))

		if _suggestions.Value is null or 0 == len(_suggestions.Value): #suggest a  var
			_filter = query
			_suggestions = EnvironmentBoundValue.Create(
							null,
							array(
								var.Key.ToString() as object
								for var in _interpreter.Values
								if var.ToString().StartsWith(_filter)))

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

	def AutoComplete():
		raise InvalidOperationException("no suggestions") if _suggestions.Value is null or _selectedSuggestionIndex is null

		Write(_suggestions.Select[of string]({ ss | AutoCompletionFor(ss[_selectedSuggestionIndex.Value]) }).Value)

		_selectedSuggestionIndex = null
		_suggestions = EnvironmentBoundValue[of (object)](null, null)

	def AutoCompletionFor(s):
		match s:
			case m = IMethod(Name: name):
				if len(m.GetParameters()) == 0:
					return "$(TrimFilter(name))()"
				return "$(TrimFilter(name))("
			case IEntity(Name: name):
				return TrimFilter(name)
			otherwise:
				return TrimFilter(s.ToString())

	def TrimFilter(s as string):
		return s[len(_filter):]

	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(LineLen, char(' ')))
		line = _history.ToArray()[_historyIndex]
		LineLen = 0
		Console.CursorLeft = len(CurrentPrompt)
		Write(line)

	def ReadEvalPrintLoop():
		Console.CursorVisible = true
		ConsolePrintPrompt()
		while not _quit:
			ReadEvalPrintLoopStep()
		SaveHistory()
		DisplayGoodbye()

	private def ReadEvalPrintLoopStep():
		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'):
				if LineLen > 0 and (char.IsLetterOrDigit(LineLastChar) or LineLastChar == char('.')):
					_selectedSuggestionIndex = 0
					DisplaySuggestions()
				else:
					Indent()

			#line-editing support
			if not _multiline and LineLen > 0:
				if Console.CursorLeft > len(CurrentPrompt):
					if key == ConsoleKey.Backspace:
						if _indent > 0 and LineLen == LineIndentLen:
							Unindent()
						else:
							Delete(1)
					elif key == ConsoleKey.LeftArrow:
						Console.CursorLeft--
				if key == ConsoleKey.Delete:
					Delete(0)
				elif key == ConsoleKey.RightArrow:
					if Console.CursorLeft < (len(CurrentPrompt)+LineLen):
						Console.CursorLeft++
				elif key == ConsoleKey.Home:
					Console.CursorLeft = len(CurrentPrompt)
				elif key == ConsoleKey.End:
					Console.CursorLeft = len(CurrentPrompt) + LineLen

			#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 _selectedSuggestionIndex > 0:
						_selectedSuggestionIndex--
					else:
						_selectedSuggestionIndex = len(_suggestions.Value) - 1
					DisplaySuggestions()
				elif key == ConsoleKey.RightArrow:
					if _selectedSuggestionIndex < len(_suggestions.Value) - 1:
						_selectedSuggestionIndex++
					else:
						_selectedSuggestionIndex = 0
					DisplaySuggestions()
				if newLine:
					AutoComplete()
					return
			if not newLine:
				return

		_selectedSuggestionIndex = null

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

		if not newLine:
			#line-editing support
			if cx < LineLen and not _multiline:
				_line.Insert(cx, keyChar) if not control
				Console.Write(_line.ToString(cx, LineLen-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)

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

				_indent++ if LineLastChar in _blockStarters or Line.EndsWith(QQBegin)
				_indent-- if Line.EndsWith(IndentChars+"pass")
				if Line.EndsWith(QQEnd):
					CheckBooLangCompilerReferenced()
					_indent--

				if _indent <= 0:
					_indent = 0
					try:
						Eval(_buffer.ToString())
					ensure:
						_buffer.Length = 0 #truncate buffer

			LineLen = 0 #truncate line
			ConsolePrintPrompt()

	/* 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()
			elif cmd[0] == "/g" or cmd[0] == "/globals":
				Eval("globals()")
			else:
				return false

		elif len(cmd) == 2:
			if cmd[0] == "/l" or cmd[0] == "/load":
				Load(cmd[1])
			elif cmd[0] == "/s" or cmd[0] == "/save":
				Save(cmd[1])
			else:
				return false

		else:
			return false
		return true

	private _indent as int = 0

	def DisplayLogo():
		WithColor InterpreterColor:
			print """Welcome to booish, an interactive interpreter for the boo programming language.
Running boo ${BooVersion} on ${Boo.Lang.Runtime.RuntimeServices.RuntimeDisplayName}.

Enter boo code in the prompt below (or type /help)."""

	def DisplayHelp():
		WithColor InterpreterColor:
			print """The following builtin functions are available :
    /? or /h or /help : display this help
    dir(type) : returns the members of a type
    describe(type) : describe a type as boo code
    globals() or /g : returns names of all variables known to interpreter
    load(file) or /l file : evals an external boo file
    save(file) or /s file : writes your current booish session into file
    quit() or /q : exits the interpreter"""

	def DisplayGoodbye():	// booish is friendly
		WithColor InterpreterColor:
			print ""
			print "All your boo are belong to us!"

	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 Load([required] path as string):
		if path.EndsWith(".boo"):
			ConsolePrintMessage("Evaluating '${path}' ...")
			DisplayResults(_interpreter.EvalCompilerInput(Boo.Lang.Compiler.IO.FileInput(path)))
		else:
			ConsolePrintMessage("Adding reference to '${path}'")
			try:
				_interpreter.References.Add(System.Reflection.Assembly.LoadFrom(path))
			except e:
				ConsolePrintException(e)

	private def ProcessLastValue():
		_ = _interpreter.LastValue
		if _ is not null:
			Console.WriteLine(Boo.Lang.Interpreter.Builtins.repr(_))
			_interpreter.SetValue("_", _)

	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.")

	private _quit = false

	def Quit():
		_quit = true

	def DisplayProblems(problems as ICollection, color as ConsoleColor):
		return if problems is null or problems.Count == 0
		WithColor color:
			for problem as duck in problems:
				markLocation(problem.LexicalInfo)
				type = ("ERROR" if problem isa CompilerError else "WARNING")
				Console.WriteLine("${type}: ${problem.Message}")
		return true

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

	private def CheckBooLangCompilerReferenced():
		return if _blcReferenced
		_interpreter.References.Add(typeof(Boo.Lang.Compiler.CompilerContext).Assembly)
		_blcReferenced = true

	_blcReferenced = false


prints a sorted view of the Hash since the iteration order is not the same across platforms

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

"""
foo = 3
key = value
"""
h = { "key" : "value", "foo": 3 }

for key, value in [(item.Key, item.Value) for item in h].Sort():
	print key, "=", value

"""
key = value
foo = 3
"""
h = { "key" : "value", "foo": 3 }

for item in h:
	print item.Key, "=", item.Value


Ctrl-Space now shows all members of the current class (instance, static and inherited).

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

namespace BooBinding.CodeCompletion

import BooBinding
import System
import System.Collections
import System.Diagnostics
import System.IO
import ICSharpCode.SharpDevelop.Services
import SharpDevelop.Internal.Parser
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast as AST
import Boo.Lang.Compiler.IO
import Boo.Lang.Compiler.Steps

class Resolver:
	_parserService as IParserService

	def CtrlSpace(parserService as IParserService, caretLine as int, caretColumn as int, fileName as string) as ArrayList:
		_parserService = parserService
		result = ArrayList(BooAmbience.TypeConversionTable.Values)
		// Add special boo types (types defined in Boo.dll)
		result.Add("hash")
		result.Add("list")
		result.Add("System") // system namespace can be used everywhere

		parseInfo = parserService.GetParseInformation(fileName)
		cu = parseInfo.MostRecentCompilationUnit as CompilationUnit
		if cu != null:
			curClass = parserService.GetInnermostClass(cu, caretLine, caretColumn) as IClass
			result = AddCurrentClassMembers(result, curClass) if curClass != null

		return result

	def AddCurrentClassMembers(result as ArrayList, curClass as IClass) as ArrayList:
		result = _parserService.ListMembers(result, curClass, curClass, false)
		// Add static members, but only from this class (not from base classes)
		for method as IMethod in curClass.Methods:
			result.Add(method) if (method.Modifiers & ModifierEnum.Static) == ModifierEnum.Static
		for field as IField in curClass.Fields:
			result.Add(field) if (field.Modifiers & ModifierEnum.Static) == ModifierEnum.Static
		for property as IProperty in curClass.Properties:
			result.Add(property) if (property.Modifiers & ModifierEnum.Static) == ModifierEnum.Static
		for e as Event in curClass.Events:
			result.Add(e) if (e.Modifiers & ModifierEnum.Static) == ModifierEnum.Static
		return result

	def Resolve(parserService as IParserService, expression as string, caretLine as int, caretColumn as int, fileName as string, fileContent as string) as ResolveResult:
		if expression == null or expression == '':
			return null

		_parserService = parserService
		try:
			int.Parse(expression)
			return null
		except exception as Exception:
			pass

		parseInfo = parserService.GetParseInformation(fileName)
		cu = parseInfo.MostRecentCompilationUnit as CompilationUnit
		if cu == null:
			print "BooResolver: No parse information!"
			return null

		callingClass as Class = parserService.GetInnermostClass(cu, caretLine, caretColumn)
		returnClass as Class = null
		returnClass = callingClass if expression == "self"
		return null if returnClass == null
		return ResolveResult(returnClass, parserService.ListMembers(ArrayList(), returnClass, callingClass, false))

namespace BooBinding.CodeCompletion

import BooBinding
import System
import System.Collections
import System.Diagnostics
import System.IO
import ICSharpCode.SharpDevelop.Services
import SharpDevelop.Internal.Parser
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast as AST
import Boo.Lang.Compiler.IO
import Boo.Lang.Compiler.Steps

class Resolver:
	def CtrlSpace(parserService as IParserService, caretLine as int, caretColumn as int, fileName as string) as ArrayList:
		result = ArrayList(BooAmbience.TypeConversionTable.Values)
		// Add special boo types (types defining in Boo.dll)
		result.Add("hash")
		result.Add("list")
		result.Add("System") // system namespace can be used everywhere

		//parseInfo = parserService.GetParseInformation(fileName)
		//cu = parseInfo.MostRecentCompilationUnit as CompilationUnit
		//if cu != null:
		//	curClass = parserService.GetInnermostClass(cu, caretLine, caretColumn) as Class
		//	if
		return result

	def Resolve(parserService as IParserService, expression as string, caretLine as int, caretColumn as int, fileName as string, fileContent as string) as ResolveResult:
		if expression == null or expression == '':
			return null

		try:
			int.Parse(expression)
			return null
		except exception as Exception:
			pass

		//self._caretLine = caretLineNumber
		//self._caretColumn = caretColumn
		//self._parserService = parserService
		parseInfo = parserService.GetParseInformation(fileName)
		cu = parseInfo.MostRecentCompilationUnit as CompilationUnit
		if cu == null:
			print "BooResolver: No parse information!"
			return null

		callingClass as Class = parserService.GetInnermostClass(cu, caretLine, caretColumn)
		returnClass as Class = null
		if callingClass == null:
			print "BooResolver: CallingClass not found!"
			return null
		if expression == "self":
			returnClass = callingClass
		return null if returnClass == null
		return ResolveResult(returnClass, parserService.ListMembers(ArrayList(), returnClass, callingClass, false))


more descriptive variable names

more descriptive variable names

namespace UnityScript.Steps

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

import Boo.Lang.Environments

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

class ProcessUnityScriptMethods(ProcessMethodBodiesWithDuckTyping):

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

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

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

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

	deferred _UnityRuntimeServices_GetEnumerator = ResolveUnityRuntimeMethod("GetEnumerator")

	deferred _UnityRuntimeServices_Update = ResolveUnityRuntimeMethod("Update")

	deferred _UnityRuntimeServices_GetTypeOf = ResolveUnityRuntimeMethod("GetTypeOf")

	_implicit = false

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

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

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

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

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

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

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

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

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

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

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

	override def VisitMemberPreservingContext(node as TypeMember):

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

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

	private def EnterModuleContext(module as Module):
		_activeModule = module
		Parameters.Strict = Pragmas.IsEnabledOn(module, Pragmas.Strict)
		_implicit = Pragmas.IsEnabledOn(module, Pragmas.Implicit)
		if Pragmas.IsDisabledOn(module, Pragmas.Downcast):
			Parameters.DisableWarning(ImplicitDowncast)
		else:
			Parameters.EnableWarning(ImplicitDowncast)

	ImplicitDowncast:
		get: return Boo.Lang.Compiler.CompilerWarningFactory.Codes.ImplicitDowncast

	_activeModule as Module

	Strict:
		get: return Parameters.Strict

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

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

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

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

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

		ContextAnnotations.SetEntryPoint(_context, node)

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

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

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

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

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

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

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

		if not IsPossibleStartCoroutineInvocation(node):
			return

		if method.IsStatic: return

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

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

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

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

	def ApplyImplicitArrayConversion(node as BinaryExpression):
		leftType = GetExpressionType(node.Left)
		return unless leftType.IsArray

		rightType = GetExpressionType(node.Right)
		return unless rightType is UnityScriptLangArray()

		node.Right = CodeBuilder.CreateCast(leftType,
						CodeBuilder.CreateMethodInvocation(
							node.Right,
							ResolveMethod(rightType, "ToBuiltin"),
							CodeBuilder.CreateTypeofExpression(leftType.ElementType)))

	def UnityScriptLangArray():
		return TypeSystemServices.Map(UnityScript.Lang.Array)

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

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

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

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

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

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

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

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

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

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

	class LoopVariableUpdater(DepthFirstVisitor):

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

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

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

			parentNode = node.ParentNode

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

			parentNode.Replace(node, block)

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

			referent = node.Entity
			_found = referent is _loopVariable

namespace UnityScript.Steps

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

import Boo.Lang.Environments

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

class ProcessUnityScriptMethods(ProcessMethodBodiesWithDuckTyping):

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

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

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

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

	deferred _UnityRuntimeServices_GetEnumerator = ResolveUnityRuntimeMethod("GetEnumerator")

	deferred _UnityRuntimeServices_Update = ResolveUnityRuntimeMethod("Update")

	deferred _UnityRuntimeServices_GetTypeOf = ResolveUnityRuntimeMethod("GetTypeOf")

	_implicit = false

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

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

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

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

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

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

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

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

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

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

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

	override def VisitMemberPreservingContext(node as TypeMember):

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

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

	private def EnterModuleContext(module as Module):
		_activeModule = module
		Parameters.Strict = Pragmas.IsEnabledOn(module, Pragmas.Strict)
		_implicit = Pragmas.IsEnabledOn(module, Pragmas.Implicit)
		if Pragmas.IsDisabledOn(module, Pragmas.Downcast):
			Parameters.DisableWarning(ImplicitDowncast)
		else:
			Parameters.EnableWarning(ImplicitDowncast)

	ImplicitDowncast:
		get: return Boo.Lang.Compiler.CompilerWarningFactory.Codes.ImplicitDowncast

	_activeModule as Module

	Strict:
		get: return Parameters.Strict

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

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

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

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

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

		ContextAnnotations.SetEntryPoint(_context, node)

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

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

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

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

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

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

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

		if not IsPossibleStartCoroutineInvocation(node):
			return

		if method.IsStatic: return

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	class LoopVariableUpdater(DepthFirstVisitor):

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

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

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

			parentNode = node.ParentNode

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

			parentNode.Replace(node, block)

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

			referent = node.Entity
			_found = referent is _loopVariable


pipeline option

pipeline option

git-svn-id: fcce7d6ce0ac15bf24f68871b5145d46fc068279@2208 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
import System.Globalization
import System.IO
import System.Text.RegularExpressions
import NAnt.Core
import NAnt.Core.Attributes
import NAnt.Core.Types
import NAnt.Core.Util
import NAnt.DotNet.Types

import NAnt.DotNet.Tasks
import System.Reflection

[TaskName('booc')]
public class BoocTask(CompilerBase):
	#region Private Instance Fields
	private _debugOutput as DebugOutput = DebugOutput.Enable
	private _exe as string

	private _useruntime = true //keep true for mono compatibility (don't call booc.exe directly)
	private _noconfig = false
	private _nostdlib = false
	private _wsa = false
	private _ducky = false
	private _pipeline as string

	#endregion Private Instance Fields
	#region Private Static Fields
	private static _classNameRegex = Regex('^((?<comment>/\\*.*?(\\*/|$))|[\\s\\.\\{]+|class\\s+(?<class>\\w+)|(?<keyword>\\w+))*')

	private static _namespaceRegex = Regex('^((?<comment>/\\*.*?(\\*/|$))|[\\s\\.\\{]+|namespace\\s+(?<namespace>(\\w+(\\.\\w+)*)+)|(?<keyword>\\w+))*')

	#endregion Private Static Fields

	def constructor():
		SupportsKeyFile = true
		SupportsKeyContainer = true

	[FrameworkConfigurable("exename")]
	[TaskAttribute('exename')]
	public override ExeName as string:
		get:
			return _exe
		set:
			_exe = value

	[FrameworkConfigurable("useruntimeengine")]
	[TaskAttribute('useruntimeengine')]
	override UseRuntimeEngine as bool:
		get:
			return _useruntime
		set:
			_useruntime = value

	#region Public Instance Properties

	[TaskAttribute('debug')]
	public DebugOutput as DebugOutput:
		get:
			return _debugOutput
		set:
			_debugOutput = value

	[TaskAttribute('pipeline')]
	public Pipeline:
		get:
			return _pipeline
		set:
			_pipeline = value

	public Debug as bool:
		get:
			return (DebugOutput != DebugOutput.None)
		set:
			if value:
				DebugOutput = DebugOutput.Enable
			else:
				DebugOutput = DebugOutput.None

	[FrameworkConfigurable('noconfig')]
	[TaskAttribute('noconfig')]
	[BooleanValidator]
	public NoConfig as bool:
		get:
			return _noconfig
		set:
			_noconfig = value

	[FrameworkConfigurable('nostdlib')]
	[TaskAttribute('nostdlib')]
	[BooleanValidator]
	public NoStdLib as bool:
		get:
			return _nostdlib
		set:
			_nostdlib = value

	[TaskAttribute('wsa')]
	[BooleanValidator]
	public WhiteSpaceAgnostic as bool:
		get:
			return _wsa
		set:
			_wsa = value

	[TaskAttribute('ducky')]
	[BooleanValidator]
	public Ducky as bool:
		get:
			return _ducky
		set:
			_ducky = value

	private def FindBooc() as string:
		path as string
		dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
		if Project.TargetFramework:
			frameworkname = Project.TargetFramework.Name
			path = Path.Combine(Path.Combine(dir, frameworkname), "booc.exe")
			if File.Exists(path):
				return path
			//treat mono-1.0==net-1.1 and mono-##==net-##
			frameworkname = frameworkname.Replace("mono-1.0","net-1.1").Replace("mono-","net-")
			path = Path.Combine(Path.Combine(dir, frameworkname), "booc.exe")
			if File.Exists(path):
				return path
		path = Path.Combine(dir, "booc.exe")
		if File.Exists(path):
			return path
		_useruntime = false
		return "booc" //try booc in PATH

	protected override def ExecuteTask():
		if not ExeName or ExeName == string.Empty:
			ExeName = FindBooc()
		super()

	#endregion Public Instance Properties
	#region Override implementation of CompilerBase
	protected override def WriteOptions(writer as TextWriter):
		if DebugOutput != DebugOutput.None:
			WriteOption(writer, 'debug')
		else:
			WriteOption(writer, 'debug-')
		if NoConfig and (not Arguments.Contains('-noconfig')):
			Arguments.Add(Argument('-noconfig'))
		if NoStdLib:
			WriteOption(writer, "nostdlib")
		if Verbose:
			WriteOption(writer, "vv")
		if WhiteSpaceAgnostic:
			WriteOption(writer, "wsa")
		if Ducky:
			WriteOption(writer, "ducky")
		if Pipeline:
			WriteOption(writer, "p", _pipeline)

	protected override def WriteOption(writer as TextWriter, name as string):
		writer.WriteLine("-{0}", name)

	protected override def WriteOption(writer as TextWriter, name as string, value as string):
		if value.IndexOf(" ") > 0 and (not value.StartsWith("\"")
						or not value.EndsWith("\"")):
			writer.WriteLine("-{0}:\"{1}\"", name, value)
		else:
			writer.WriteLine("-{0}:{1}", name, value)

	public Extension as string:
		get:
			return 'boo'

	protected ClassNameRegex as Regex:
		get:
			return _classNameRegex

	protected NamespaceRegex as Regex:
		get:
			return _namespaceRegex
	#endregion Override implementation of CompilerBase

#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
import System.Globalization
import System.IO
import System.Text.RegularExpressions
import NAnt.Core
import NAnt.Core.Attributes
import NAnt.Core.Types
import NAnt.Core.Util
import NAnt.DotNet.Types

import NAnt.DotNet.Tasks
import System.Reflection

[TaskName('booc')]
public class BoocTask(CompilerBase):
	#region Private Instance Fields
	private _debugOutput as DebugOutput = DebugOutput.Enable
	private _exe as string

	private _useruntime = true //keep true for mono compatibility (don't call booc.exe directly)
	private _noconfig = false
	private _nostdlib = false
	private _wsa = false
	private _ducky = false

	#endregion Private Instance Fields
	#region Private Static Fields
	private static _classNameRegex = Regex('^((?<comment>/\\*.*?(\\*/|$))|[\\s\\.\\{]+|class\\s+(?<class>\\w+)|(?<keyword>\\w+))*')

	private static _namespaceRegex = Regex('^((?<comment>/\\*.*?(\\*/|$))|[\\s\\.\\{]+|namespace\\s+(?<namespace>(\\w+(\\.\\w+)*)+)|(?<keyword>\\w+))*')

	#endregion Private Static Fields

	def constructor():
		SupportsKeyFile = true
		SupportsKeyContainer = true

	[FrameworkConfigurable("exename")]
	[TaskAttribute('exename')]
	public override ExeName as string:
		get:
			return _exe
		set:
			_exe = value

	[FrameworkConfigurable("useruntimeengine")]
	[TaskAttribute('useruntimeengine')]
	override UseRuntimeEngine as bool:
		get:
			return _useruntime
		set:
			_useruntime = value

	#region Public Instance Properties

	[TaskAttribute('debug')]
	public DebugOutput as DebugOutput:
		get:
			return _debugOutput
		set:
			_debugOutput = value

	public Debug as bool:
		get:
			return (DebugOutput != DebugOutput.None)
		set:
			if value:
				DebugOutput = DebugOutput.Enable
			else:
				DebugOutput = DebugOutput.None

	[FrameworkConfigurable('noconfig')]
	[TaskAttribute('noconfig')]
	[BooleanValidator]
	public NoConfig as bool:
		get:
			return _noconfig
		set:
			_noconfig = value

	[FrameworkConfigurable('nostdlib')]
	[TaskAttribute('nostdlib')]
	[BooleanValidator]
	public NoStdLib as bool:
		get:
			return _nostdlib
		set:
			_nostdlib = value

	[TaskAttribute('wsa')]
	[BooleanValidator]
	public WhiteSpaceAgnostic as bool:
		get:
			return _wsa
		set:
			_wsa = value

	[TaskAttribute('ducky')]
	[BooleanValidator]
	public Ducky as bool:
		get:
			return _ducky
		set:
			_ducky = value

	private def FindBooc() as string:
		path as string
		dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
		if Project.TargetFramework:
			frameworkname = Project.TargetFramework.Name
			path = Path.Combine(Path.Combine(dir, frameworkname), "booc.exe")
			if File.Exists(path):
				return path
			//treat mono-1.0==net-1.1 and mono-##==net-##
			frameworkname = frameworkname.Replace("mono-1.0","net-1.1").Replace("mono-","net-")
			path = Path.Combine(Path.Combine(dir, frameworkname), "booc.exe")
			if File.Exists(path):
				return path
		path = Path.Combine(dir, "booc.exe")
		if File.Exists(path):
			return path
		_useruntime = false
		return "booc" //try booc in PATH

	protected override def ExecuteTask():
		if not ExeName or ExeName == string.Empty:
			ExeName = FindBooc()
		super()

	#endregion Public Instance Properties
	#region Override implementation of CompilerBase
	protected override def WriteOptions(writer as TextWriter):
		if DebugOutput != DebugOutput.None:
			WriteOption(writer, 'debug')
		else:
			WriteOption(writer, 'debug-')
		if NoConfig and (not Arguments.Contains('-noconfig')):
			Arguments.Add(Argument('-noconfig'))
		if NoStdLib:
			WriteOption(writer, "nostdlib")
		if Verbose:
			WriteOption(writer, "vv")
		if WhiteSpaceAgnostic:
			WriteOption(writer, "wsa")
		if Ducky:
			WriteOption(writer, "ducky")

	protected override def WriteOption(writer as TextWriter, name as string):
		writer.WriteLine("-{0}", name)

	protected override def WriteOption(writer as TextWriter, name as string, value as string):
		if value.IndexOf(" ") > 0 and (not value.StartsWith("\"")
						or not value.EndsWith("\"")):
			writer.WriteLine("-{0}:\"{1}\"", name, value)
		else:
			writer.WriteLine("-{0}:{1}", name, value)

	public Extension as string:
		get:
			return 'boo'

	protected ClassNameRegex as Regex:
		get:
			return _classNameRegex

	protected NamespaceRegex as Regex:
		get:
			return _namespaceRegex
	#endregion Override implementation of CompilerBase


test for the Ducky interpreter option

test for the Ducky interpreter option

git-svn-id: c3b8989232654601f1acea7403f771adaaa44a81@1480 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.Tests

import System
import System.IO
import NUnit.Framework
import Boo.Lang.Interpreter
import Boo.Lang.Compiler.TypeSystem

[TestFixture]
class InteractiveInterpreterTestFixture:

	public static LifeTheUniverseAndEverything = 42

	class ConsoleCapture(IDisposable):
		_console = StringWriter()
		_old

		def constructor():
			_old = Console.Out
			Console.SetOut(_console)

		override def ToString():
			return _console.ToString()

		def Dispose():
			Console.SetOut(_old)

	_interpreter as InteractiveInterpreter

	[SetUp]
	def SetUp():
		_interpreter = InteractiveInterpreter()
		_interpreter.SetValue("name", "boo")
		_interpreter.SetValue("age", 3)

	[Test]
	def DefaultValues():
		assert false == _interpreter.RememberLastValue
		assert _interpreter.LastValue is null

	[Test]
	def UseInterpreterValues():
		using console=ConsoleCapture():
			Eval("print(name);print(age)")
		newLine = Environment.NewLine
		Assert.AreEqual("boo${newLine}3${newLine}", console.ToString())

	[Test]
	def Unpacking():
		Eval("a, b = 1, 2")
		assert 1 == _interpreter.GetValue("a")
		assert 2 == _interpreter.GetValue("b")

		Eval("a, b = b, a")
		assert 2 == _interpreter.GetValue("a")
		assert 1 == _interpreter.GetValue("b")

	[Test]
	def ArraySlicing():
		Eval("a = 1, 2, 3")
		Eval("a1 = a[0]")
		Eval("a12 = a[0:-1]")
		Eval("a3 = a[-1]")

		assert _interpreter.GetValue("a") == (1, 2, 3)
		assert _interpreter.GetValue("a1") == 1
		assert _interpreter.GetValue("a12") == (1, 2)
		assert _interpreter.GetValue("a3") == 3

	[Test]
	def AssignmentToSlice():
		Eval("a = 1, 2, 3")
		Eval("a[-1] = 1")
		Eval("a[0] = 3")

		assert _interpreter.GetValue("a") == (3, 2, 1)

	[Test]
	def ChangeInterpreterValues():
		Eval("age = 4")
		assert 4 == _interpreter.GetValue("age")

		Eval("age = 42")
		assert 42 == _interpreter.GetValue("age")

	[Test]
	def Import():
		Eval("import Boo.Lang.Interpreter.Tests from Boo.Lang.Interpreter.Tests")
		Eval("value = InteractiveInterpreterTestFixture.LifeTheUniverseAndEverything")
		assert 42 == _interpreter.GetValue("value")

	[Test]
	def ImportingTwiceIsNoProblem():
		Eval("import Boo.Lang.Interpreter.Tests from Boo.Lang.Interpreter.Tests")
		Eval("import Boo.Lang.Interpreter.Tests from Boo.Lang.Interpreter.Tests")

		Eval("value = InteractiveInterpreterTestFixture.LifeTheUniverseAndEverything")
		assert 42 == _interpreter.GetValue("value")

	[Test]
	def AssignmentPreservesType():
		Eval("value = (a = 3).ToString()")
		Eval("a2 = a*2")
		Eval("value2 = value*2")
		assert 3 == _interpreter.GetValue("a")
		assert "3" == _interpreter.GetValue("value")
		assert 6 == _interpreter.GetValue("a2")
		assert "33" == _interpreter.GetValue("value2")

	[Test]
	[ExpectedException(System.Reflection.TargetInvocationException)]
	def RaiseInsideEval():
		Eval("raise System.ApplicationException()")

	[Test]
	def TreatObjectsAsDucks():
		Eval("""
class Person:
	[property(Name)] _name = ''

p as object = Person(Name: 'John')
""")
		Eval("name = p.Name")
		assert "John" == _interpreter.GetValue("name")

	[Test]
	def TreatObjectsAsDucksCanBeDisabled():
		Eval("""
class Person:
	[property(Name)] _name = ''

p as object = Person(Name: 'John')
""")
		_interpreter.Ducky = false
		result = _interpreter.Eval("name = p.Name")
		assert 1 == len(result.Errors)

	[Test]
	def InterpreterRememberDeclaredTypes():
		Eval("i as object = 3")
		assert 3 == _interpreter.GetValue("i")
		Eval("i = '3' # is is typed object")
		assert "3" == _interpreter.GetValue("i")

	[Test]
	def MethodDef():

		_interpreter.SetValue("eggs", "eggs")

		Eval("""
def spam():
	return eggs
""")

		Eval("value = spam()")
		assert 'eggs' == _interpreter.GetValue("value")

	[Test]
	def InterpreterReference():

		assert _interpreter.GetValue("interpreter") is _interpreter

	[Test]
	def Reset():

		_interpreter.RememberLastValue = true

		Eval("a as int = 3")
		Eval("a")

		assert 3 == _interpreter.LastValue
		assert 3 == _interpreter.GetValue("a")
		assert int is _interpreter.Lookup("a")

		_interpreter.Reset()

		assert _interpreter.LastValue is null
		assert _interpreter.GetValue("a") is null
		assert _interpreter.Lookup("a") is null

	[Test]
	def TypeDef():

		_interpreter.SetValue("DefaultName", "boo")

		Eval("""
class Language:
	[property(Name)] _name = DefaultName
""")

		Eval("language = Language()")
		language as duck = _interpreter.GetValue("language")
		assert 'boo' == language.Name

		Eval("language = Language(Name: 'portuguese')")
		language = _interpreter.GetValue("language")
		assert 'portuguese' == language.Name

	[Test]
	def ExpressionWithoutSideEffectsNotAllowedInDefaultMode():
		_interpreter.RememberLastValue = false
		errors = _interpreter.Eval("2+2").Errors

		assert 1 == len(errors)
		assert "BCE0034" == errors[0].Code

	[Test]
	def BeginInvokeEndInvoke():
		Eval("""
def foo():
	return 42

handle = foo.BeginInvoke()
result = foo.EndInvoke(handle)
""")

		handle as duck = _interpreter.GetValue("handle")
		assert handle is not null
		assert handle.IsCompleted
		assert handle.EndInvokeCalled

		Assert.AreEqual(42, _interpreter.GetValue("result"))

	[Test]
	def BeginInvokeEndInvokeInSequentialEvals():
		Eval("""
def foo():
	return 42

handle = foo.BeginInvoke()
""")
		handle as duck = _interpreter.GetValue("handle")
		assert handle is not null
		assert not handle.EndInvokeCalled

		Eval("result = foo.EndInvoke(handle)")

		Assert.AreEqual(42, _interpreter.GetValue("result"))

		assert handle.IsCompleted
		assert handle.EndInvokeCalled

	[Test]
	def MethodReferences():
		Eval("""
def foo():
	return 42""")

		Eval("f1 = foo")
		Eval("f2 = foo")

		f1 as callable = _interpreter.GetValue("f1")
		f2 as callable = _interpreter.GetValue("f2")
		assert f1 is not null
		assert 42 == f1()

		assert f2 is not null
		assert 42 == f2()

	[Test]
	def EvalSimpleLiterals():

		_interpreter.RememberLastValue = true
		Eval("true")
		assert true == _interpreter.LastValue

		Eval("false")
		assert false == _interpreter.LastValue

		Eval("null")
		assert _interpreter.LastValue is null

	[Test]
	def RememberLastValue():
		_interpreter.RememberLastValue = true

		Eval("2+2")
		assert 4 == _interpreter.LastValue

		Eval("3")
		assert 3 == _interpreter.LastValue

	[Test]
	def DisableRememberLastValue():
		_interpreter.RememberLastValue = false
		Eval("a=2+2")
		assert _interpreter.LastValue is null

	[Test]
	def MethodVariablesAreNotGlobalToTheInterpreter():
		Eval("""
def foo():
	a = 3

b = 4""")
		assert int is _interpreter.Lookup("b")
		assert _interpreter.Lookup("a") is null

	[Test]
	def EvaluateClosure():
		_interpreter.RememberLastValue = true
		Eval("{ return 42 }")
		assert 42 == cast(callable, _interpreter.LastValue)()

	[Test]
	def EvaluateVoidFunctionSetsValueToNull():

		_interpreter.RememberLastValue = true
		Eval("""
def dummy():
	pass
42
dummy()""")
		assert _interpreter.LastValue is null

	[Test]
	def Closures():

		Eval("x2 = { v as int | return v*2 }")

		x2 as callable = _interpreter.GetValue("x2")
		assert x2 is not null
		assert 4 == x2(2)

	[Test]
	def EvalSimpleReferenceGetsItsValue():

		_interpreter.RememberLastValue = true

		Eval("name")
		assert "boo" == _interpreter.LastValue

		Eval("age")
		assert 3 == _interpreter.LastValue

	[Test]
	def GeneratorExpressions():

		_interpreter.SetValue("value", 3)
		Eval("e = i*2 for i in range(value)")
		assert array(_interpreter.GetValue("e")) == (0, 2, 4)

	[Test]
	def LoopEval():

		value = ""
		_interpreter.RememberLastValue = true
		_interpreter.Print = { item | value = item }
		_interpreter.LoopEval("3+3")
		assert "6" == value
		assert 6 == _interpreter.GetValue("_")

		_interpreter.LoopEval("'42'*3")
		assert "'424242'" == value
		assert "424242" == _interpreter.GetValue("_")

	class Customer:

		[property(FirstName)]
		_fname as string

		public LastName as string

		event Changed as System.EventHandler

		def constructor(name as string):
			_fname = name

		def constructor():
			pass

	[Test]
	def Help():

		buffer = System.IO.StringWriter()
		buffer.WriteLine()
		_interpreter.Print = { item | buffer.WriteLine(item) }
		_interpreter.help(Customer)

		expected = """
class Customer(object):

    def constructor(name as string)

    def constructor()

    public LastName as string

    FirstName as string:
        get
        set

    def Equals(obj as object) as bool

    def GetHashCode() as int

    def GetType() as System.Type

    def ToString() as string

    event Changed as System.EventHandler

"""
		actual = buffer.ToString().Replace("\r\n", "\n")

		# mono compatibility fix
		# object.Equals arg on mono is called o
		actual = actual.Replace("o as object", "obj as object")

		Assert.AreEqual(expected, actual)

	[Test]
	def Loop():

		# let's loop
		Eval("""
l = []
for i in range(3):
	l.Add(i*2)
""")
		Assert.AreEqual([0, 2, 4], _interpreter.GetValue("l"))

	[Test]
	def UnpackingLoop():
		Eval("""
l = []
for i, j in ((1, 2), (3, 4)):
	l.Add((j, i))
""")

		Assert.AreEqual([(2, 1), (4, 3)], _interpreter.GetValue("l"))

	[Test]
	def SuggestCodeCompletion():
		Eval("a = 3")
		suggestion = _interpreter.SuggestCodeCompletion("a.__codecomplete__")
		assert suggestion isa ExternalType
		assert int is (suggestion as ExternalType).ActualType

		suggestion = _interpreter.SuggestCodeCompletion("'foo'.ToUpper().__codecomplete__")
		assert suggestion isa ExternalType
		assert string is (suggestion as ExternalType).ActualType

	[Test]
	def SuggestNamespaceCodeCompletion():
		suggestion = _interpreter.SuggestCodeCompletion("System.__codecomplete__")
		assert suggestion is not null
		assert suggestion.EntityType == EntityType.Namespace

	[Test]
	def SuggestCodeCompletionReferencesAreInSync():
		_interpreter.References.Add(System.Reflection.Assembly.GetExecutingAssembly())
		suggestion = _interpreter.SuggestCodeCompletion("Boo.Lang.Interpreter.Tests.__codecomplete__")
		assert suggestion is not null
		assert suggestion.EntityType == EntityType.Namespace

	[Test]
	def SuggestCodeCompletionFromImportedNamespaces():
		_interpreter.Eval("import System")
		suggestion = _interpreter.SuggestCodeCompletion("Console.__codecomplete__")
		assert suggestion is not null
		assert System.Console is (suggestion as ExternalType).ActualType

	[Test]
	def SuggestCodeCompletionForImportStatement():
		suggestion = _interpreter.SuggestCodeCompletion("import    System.__codecomplete__")
		assert suggestion is not null
		assert suggestion.EntityType == EntityType.Namespace

	def Eval(code as string):
		result = _interpreter.Eval(code)
		assert 0 == len(result.Errors), result.Errors.ToString(true)

#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.Tests

import System
import System.IO
import NUnit.Framework
import Boo.Lang.Interpreter
import Boo.Lang.Compiler.TypeSystem

[TestFixture]
class InteractiveInterpreterTestFixture:

	public static LifeTheUniverseAndEverything = 42

	class ConsoleCapture(IDisposable):
		_console = StringWriter()
		_old

		def constructor():
			_old = Console.Out
			Console.SetOut(_console)

		override def ToString():
			return _console.ToString()

		def Dispose():
			Console.SetOut(_old)

	_interpreter as InteractiveInterpreter

	[SetUp]
	def SetUp():
		_interpreter = InteractiveInterpreter()
		_interpreter.SetValue("name", "boo")
		_interpreter.SetValue("age", 3)

	[Test]
	def DefaultValues():
		assert false == _interpreter.RememberLastValue
		assert _interpreter.LastValue is null

	[Test]
	def UseInterpreterValues():
		using console=ConsoleCapture():
			Eval("print(name);print(age)")
		newLine = Environment.NewLine
		Assert.AreEqual("boo${newLine}3${newLine}", console.ToString())

	[Test]
	def Unpacking():
		Eval("a, b = 1, 2")
		assert 1 == _interpreter.GetValue("a")
		assert 2 == _interpreter.GetValue("b")

		Eval("a, b = b, a")
		assert 2 == _interpreter.GetValue("a")
		assert 1 == _interpreter.GetValue("b")

	[Test]
	def ArraySlicing():
		Eval("a = 1, 2, 3")
		Eval("a1 = a[0]")
		Eval("a12 = a[0:-1]")
		Eval("a3 = a[-1]")

		assert _interpreter.GetValue("a") == (1, 2, 3)
		assert _interpreter.GetValue("a1") == 1
		assert _interpreter.GetValue("a12") == (1, 2)
		assert _interpreter.GetValue("a3") == 3

	[Test]
	def AssignmentToSlice():
		Eval("a = 1, 2, 3")
		Eval("a[-1] = 1")
		Eval("a[0] = 3")

		assert _interpreter.GetValue("a") == (3, 2, 1)

	[Test]
	def ChangeInterpreterValues():
		Eval("age = 4")
		assert 4 == _interpreter.GetValue("age")

		Eval("age = 42")
		assert 42 == _interpreter.GetValue("age")

	[Test]
	def Import():
		Eval("import Boo.Lang.Interpreter.Tests from Boo.Lang.Interpreter.Tests")
		Eval("value = InteractiveInterpreterTestFixture.LifeTheUniverseAndEverything")
		assert 42 == _interpreter.GetValue("value")

	[Test]
	def ImportingTwiceIsNoProblem():
		Eval("import Boo.Lang.Interpreter.Tests from Boo.Lang.Interpreter.Tests")
		Eval("import Boo.Lang.Interpreter.Tests from Boo.Lang.Interpreter.Tests")

		Eval("value = InteractiveInterpreterTestFixture.LifeTheUniverseAndEverything")
		assert 42 == _interpreter.GetValue("value")

	[Test]
	def AssignmentPreservesType():
		Eval("value = (a = 3).ToString()")
		Eval("a2 = a*2")
		Eval("value2 = value*2")
		assert 3 == _interpreter.GetValue("a")
		assert "3" == _interpreter.GetValue("value")
		assert 6 == _interpreter.GetValue("a2")
		assert "33" == _interpreter.GetValue("value2")

	[Test]
	[ExpectedException(System.Reflection.TargetInvocationException)]
	def RaiseInsideEval():
		Eval("raise System.ApplicationException()")

	[Test]
	def TreatObjectsAsDucks():
		Eval("""
class Person:
	[property(Name)] _name = ''

p as object = Person(Name: 'John')
""")
		Eval("name = p.Name")
		assert "John" == _interpreter.GetValue("name")

	[Test]
	def InterpreterRememberDeclaredTypes():
		Eval("i as object = 3")
		assert 3 == _interpreter.GetValue("i")
		Eval("i = '3' # is is typed object")
		assert "3" == _interpreter.GetValue("i")

	[Test]
	def MethodDef():

		_interpreter.SetValue("eggs", "eggs")

		Eval("""
def spam():
	return eggs
""")

		Eval("value = spam()")
		assert 'eggs' == _interpreter.GetValue("value")

	[Test]
	def InterpreterReference():

		assert _interpreter.GetValue("interpreter") is _interpreter

	[Test]
	def Reset():

		_interpreter.RememberLastValue = true

		Eval("a as int = 3")
		Eval("a")

		assert 3 == _interpreter.LastValue
		assert 3 == _interpreter.GetValue("a")
		assert int is _interpreter.Lookup("a")

		_interpreter.Reset()

		assert _interpreter.LastValue is null
		assert _interpreter.GetValue("a") is null
		assert _interpreter.Lookup("a") is null

	[Test]
	def TypeDef():

		_interpreter.SetValue("DefaultName", "boo")

		Eval("""
class Language:
	[property(Name)] _name = DefaultName
""")

		Eval("language = Language()")
		language as duck = _interpreter.GetValue("language")
		assert 'boo' == language.Name

		Eval("language = Language(Name: 'portuguese')")
		language = _interpreter.GetValue("language")
		assert 'portuguese' == language.Name

	[Test]
	def ExpressionWithoutSideEffectsNotAllowedInDefaultMode():
		_interpreter.RememberLastValue = false
		errors = _interpreter.Eval("2+2").Errors

		assert 1 == len(errors)
		assert "BCE0034" == errors[0].Code

	[Test]
	def BeginInvokeEndInvoke():
		Eval("""
def foo():
	return 42

handle = foo.BeginInvoke()
result = foo.EndInvoke(handle)
""")

		handle as duck = _interpreter.GetValue("handle")
		assert handle is not null
		assert handle.IsCompleted
		assert handle.EndInvokeCalled

		Assert.AreEqual(42, _interpreter.GetValue("result"))

	[Test]
	def BeginInvokeEndInvokeInSequentialEvals():
		Eval("""
def foo():
	return 42

handle = foo.BeginInvoke()
""")
		handle as duck = _interpreter.GetValue("handle")
		assert handle is not null
		assert not handle.EndInvokeCalled

		Eval("result = foo.EndInvoke(handle)")

		Assert.AreEqual(42, _interpreter.GetValue("result"))

		assert handle.IsCompleted
		assert handle.EndInvokeCalled

	[Test]
	def MethodReferences():
		Eval("""
def foo():
	return 42""")

		Eval("f1 = foo")
		Eval("f2 = foo")

		f1 as callable = _interpreter.GetValue("f1")
		f2 as callable = _interpreter.GetValue("f2")
		assert f1 is not null
		assert 42 == f1()

		assert f2 is not null
		assert 42 == f2()

	[Test]
	def EvalSimpleLiterals():

		_interpreter.RememberLastValue = true
		Eval("true")
		assert true == _interpreter.LastValue

		Eval("false")
		assert false == _interpreter.LastValue

		Eval("null")
		assert _interpreter.LastValue is null

	[Test]
	def RememberLastValue():
		_interpreter.RememberLastValue = true

		Eval("2+2")
		assert 4 == _interpreter.LastValue

		Eval("3")
		assert 3 == _interpreter.LastValue

	[Test]
	def DisableRememberLastValue():
		_interpreter.RememberLastValue = false
		Eval("a=2+2")
		assert _interpreter.LastValue is null

	[Test]
	def MethodVariablesAreNotGlobalToTheInterpreter():
		Eval("""
def foo():
	a = 3

b = 4""")
		assert int is _interpreter.Lookup("b")
		assert _interpreter.Lookup("a") is null

	[Test]
	def EvaluateClosure():
		_interpreter.RememberLastValue = true
		Eval("{ return 42 }")
		assert 42 == cast(callable, _interpreter.LastValue)()

	[Test]
	def EvaluateVoidFunctionSetsValueToNull():

		_interpreter.RememberLastValue = true
		Eval("""
def dummy():
	pass
42
dummy()""")
		assert _interpreter.LastValue is null

	[Test]
	def Closures():

		Eval("x2 = { v as int | return v*2 }")

		x2 as callable = _interpreter.GetValue("x2")
		assert x2 is not null
		assert 4 == x2(2)

	[Test]
	def EvalSimpleReferenceGetsItsValue():

		_interpreter.RememberLastValue = true

		Eval("name")
		assert "boo" == _interpreter.LastValue

		Eval("age")
		assert 3 == _interpreter.LastValue

	[Test]
	def GeneratorExpressions():

		_interpreter.SetValue("value", 3)
		Eval("e = i*2 for i in range(value)")
		assert array(_interpreter.GetValue("e")) == (0, 2, 4)

	[Test]
	def LoopEval():

		value = ""
		_interpreter.RememberLastValue = true
		_interpreter.Print = { item | value = item }
		_interpreter.LoopEval("3+3")
		assert "6" == value
		assert 6 == _interpreter.GetValue("_")

		_interpreter.LoopEval("'42'*3")
		assert "'424242'" == value
		assert "424242" == _interpreter.GetValue("_")

	class Customer:

		[property(FirstName)]
		_fname as string

		public LastName as string

		event Changed as System.EventHandler

		def constructor(name as string):
			_fname = name

		def constructor():
			pass

	[Test]
	def Help():

		buffer = System.IO.StringWriter()
		buffer.WriteLine()
		_interpreter.Print = { item | buffer.WriteLine(item) }
		_interpreter.help(Customer)

		expected = """
class Customer(object):

    def constructor(name as string)

    def constructor()

    public LastName as string

    FirstName as string:
        get
        set

    def Equals(obj as object) as bool

    def GetHashCode() as int

    def GetType() as System.Type

    def ToString() as string

    event Changed as System.EventHandler

"""
		actual = buffer.ToString().Replace("\r\n", "\n")

		# mono compatibility fix
		# object.Equals arg on mono is called o
		actual = actual.Replace("o as object", "obj as object")

		Assert.AreEqual(expected, actual)

	[Test]
	def Loop():

		# let's loop
		Eval("""
l = []
for i in range(3):
	l.Add(i*2)
""")
		Assert.AreEqual([0, 2, 4], _interpreter.GetValue("l"))

	[Test]
	def UnpackingLoop():
		Eval("""
l = []
for i, j in ((1, 2), (3, 4)):
	l.Add((j, i))
""")

		Assert.AreEqual([(2, 1), (4, 3)], _interpreter.GetValue("l"))

	[Test]
	def SuggestCodeCompletion():
		Eval("a = 3")
		suggestion = _interpreter.SuggestCodeCompletion("a.__codecomplete__")
		assert suggestion isa ExternalType
		assert int is (suggestion as ExternalType).ActualType

		suggestion = _interpreter.SuggestCodeCompletion("'foo'.ToUpper().__codecomplete__")
		assert suggestion isa ExternalType
		assert string is (suggestion as ExternalType).ActualType

	[Test]
	def SuggestNamespaceCodeCompletion():
		suggestion = _interpreter.SuggestCodeCompletion("System.__codecomplete__")
		assert suggestion is not null
		assert suggestion.EntityType == EntityType.Namespace

	[Test]
	def SuggestCodeCompletionReferencesAreInSync():
		_interpreter.References.Add(System.Reflection.Assembly.GetExecutingAssembly())
		suggestion = _interpreter.SuggestCodeCompletion("Boo.Lang.Interpreter.Tests.__codecomplete__")
		assert suggestion is not null
		assert suggestion.EntityType == EntityType.Namespace

	[Test]
	def SuggestCodeCompletionFromImportedNamespaces():
		_interpreter.Eval("import System")
		suggestion = _interpreter.SuggestCodeCompletion("Console.__codecomplete__")
		assert suggestion is not null
		assert System.Console is (suggestion as ExternalType).ActualType

	[Test]
	def SuggestCodeCompletionForImportStatement():
		suggestion = _interpreter.SuggestCodeCompletion("import    System.__codecomplete__")
		assert suggestion is not null
		assert suggestion.EntityType == EntityType.Namespace

	def Eval(code as string):
		result = _interpreter.Eval(code)
		assert 0 == len(result.Errors), result.Errors.ToString(true)