use newly introduced property macro to simplify InteractiveInterpreterConsole a bit

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

namespace Boo.Lang.Interpreter

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

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)

		LoadHistory()

	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 (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 _suggestions:
			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
			if s isa IEntity:
				Console.Write(Boo.Lang.Interpreter.Builtins.DescribeEntity(s as IEntity))
			else:
				Console.Write(s)
			i++

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

	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]
			_suggestions = _interpreter.SuggestCodeCompletion(codeToComplete+"__codecomplete__")
			_filter = query[query.LastIndexOf('.')+1:]
			_suggestions = array(e for e in _suggestions as (IEntity)
								unless not e.Name.StartsWith(_filter)) as (IEntity)

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

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

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

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

		_selectedSuggestionIndex = null
		_suggestions = null

	private _beforeHistory = string.Empty

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

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

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

			newLine = keyChar in Environment.NewLine

			if char.IsControl(keyChar):
				control = true
				if keyChar == char('\t'):
					if LineLen > 0 and (char.IsLetterOrDigit(LineLastChar) or LineLastChar == char('.')):
						_selectedSuggIdx = 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 _selectedSuggIdx > 0:
							_selectedSuggIdx--
						else:
							_selectedSuggIdx = len(_suggestions) - 1
						DisplaySuggestions()
					elif key == ConsoleKey.RightArrow:
						if _selectedSuggIdx < len(_suggestions) - 1:
							_selectedSuggIdx++
						else:
							_selectedSuggIdx = 0
						DisplaySuggestions()
					if newLine:
						AutoComplete()
						continue
				if not newLine:
					continue

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

			lastChar = keyChar

		SaveHistory()
		DisplayGoodbye()

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

		cmd = line.Split()

		if len(cmd) == 1:
			if cmd[0] == "/q" or cmd[0] == "/quit":
				Quit()
			elif cmd[0] == "/?" or cmd[0] == "/h" or cmd[0] == "/help":
				DisplayHelp()
			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 :
    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"):
			DisplayResults(_interpreter.EvalCompilerInput(Boo.Lang.Compiler.IO.FileInput(path)))
		else:
			try:
				_interpreter.References.Add(System.Reflection.Assembly.LoadFrom(path))
			except e:
				print e.Message

	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 = ("WARNING", "ERROR")[problem isa CompilerError]
				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.Text
import System.Text.RegularExpressions
import Boo.Lang.Compiler
import Boo.Lang.Compiler.TypeSystem

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)

		LoadHistory()

	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)]
	_defaultPrompt = ">>>"

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

	[property(IndentChars)]
	_indentChars = "    "

	[property(DisableColors)]
	_disableColors = false

	[property(DisableAutocompletion)]
	_disableAutocompletion = false

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

	[property(PromptColor)]
	_promptColor = ConsoleColor.DarkGreen

	[property(ExceptionColor)]
	_exceptionColor = ConsoleColor.DarkRed

	[property(WarningColor)]
	_warning = ConsoleColor.Yellow

	[property(ErrorColor)]
	_errorColor = ConsoleColor.Red

	[property(SuggestionsColor)]
	_suggestionsColor = ConsoleColor.DarkYellow

	[property(SelectedSuggestionColor)]
	_selectedSuggestionColor = ConsoleColor.DarkMagenta

	[property(SelectedSuggestionIndex)]
	_selectedSuggIdx as int?

	[property(Suggestions)]
	_suggestions as (object)

	CanAutoComplete as bool:
		get: return _selectedSuggIdx is not null

	private _builtins as (IEntity)
	private _filter as string

	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

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

		i = 0

		for s in _suggestions:
			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 == _selectedSuggIdx:
				Console.ForegroundColor = _selectedSuggestionColor if not _disableColors
			if s isa IEntity:
				Console.Write(Boo.Lang.Interpreter.Builtins.DescribeEntity(s as IEntity))
			else:
				Console.Write(s)
			i++

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

	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]
			_suggestions = _interpreter.SuggestCodeCompletion(codeToComplete+"__codecomplete__")
			_filter = query[query.LastIndexOf('.')+1:]
			_suggestions = array(e for e in _suggestions as (IEntity)
								unless not e.Name.StartsWith(_filter)) as (IEntity)

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

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

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

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

		_selectedSuggIdx = null
		_suggestions = null

	private _beforeHistory = string.Empty

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

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

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

			newLine = keyChar in Environment.NewLine

			if char.IsControl(keyChar):
				control = true
				if keyChar == char('\t'):
					if LineLen > 0 and (char.IsLetterOrDigit(LineLastChar) or LineLastChar == char('.')):
						_selectedSuggIdx = 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 _selectedSuggIdx > 0:
							_selectedSuggIdx--
						else:
							_selectedSuggIdx = len(_suggestions) - 1
						DisplaySuggestions()
					elif key == ConsoleKey.RightArrow:
						if _selectedSuggIdx < len(_suggestions) - 1:
							_selectedSuggIdx++
						else:
							_selectedSuggIdx = 0
						DisplaySuggestions()
					if newLine:
						AutoComplete()
						continue
				if not newLine:
					continue

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

			lastChar = keyChar

		SaveHistory()
		DisplayGoodbye()

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

		cmd = line.Split()

		if len(cmd) == 1:
			if cmd[0] == "/q" or cmd[0] == "/quit":
				Quit()
			elif cmd[0] == "/?" or cmd[0] == "/h" or cmd[0] == "/help":
				DisplayHelp()
			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 :
    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"):
			DisplayResults(_interpreter.EvalCompilerInput(Boo.Lang.Compiler.IO.FileInput(path)))
		else:
			try:
				_interpreter.References.Add(System.Reflection.Assembly.LoadFrom(path))
			except e:
				print e.Message

	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 = ("WARNING", "ERROR")[problem isa CompilerError]
				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


document default behaviour

document default behaviour

namespace Boo.Adt

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

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

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

Usage:

	data Expression = Const(Value as int) | Add(Left, Right) // omitted types assumed to be Expression

	data SingleValue(Value as int)

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

	DataMacroExpansion(data)

class DataMacroExpansion:

	_module as TypeDefinition
	_superType as TypeDefinition

	def constructor(node as MacroStatement):

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

		match node.Arguments[0]:
			case [| $left = $right |]:
				assert node.Body.IsEmpty
				_superType = createBaseType(left)
				expandDataConstructors(right)

			case [| $(ctor=MethodInvocationExpression()) < $superType |]:
				_superType = [|
					class $superType:
						pass
				|]
				expandDataConstructorWithBody(ctor, node.Body)

			case ctor=MethodInvocationExpression():
				_superType = [|
					class object:
						pass
				|]
				expandDataConstructorWithBody(ctor, node.Body)

	def expandDataConstructorWithBody(ctor as Expression, body as Block):
		expandDataConstructor(ctor).Members.Extend(TypeMember.Lift(body))

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

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

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

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

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

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

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

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

		ctor = constructorForFields(fields)
		addSuperInvocationTo ctor

		type.Members.Add(ctor)
		for ctor in constructorsWithDefaultValues(fields):
			type.Members.Add(ctor)

		registerType(type)
		return type

	def addSuperInvocationTo(ctor as Constructor):
		superInvocation = [| super() |]
		i = 0
		for field as Field in fieldsOf(_superType):
			ctor.Parameters.Insert(i++,
				ParameterDeclaration(Name: field.Name, Type: field.Type))
			superInvocation.Arguments.Add(ReferenceExpression(field.Name))
		ctor.Body.Insert(0, superInvocation)

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

	def equalsForType(type as TypeDefinition):

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

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

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

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

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

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

			comma = true

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

	def toStringForField(field as Field):
		return [| Boo.Adt.adtFieldToString(self.$(field.Name)) |]

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

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

	def constructorForFields(fields as (Field)):
		ctor = constructorDeclarationFor(fields)
		for p in ctor.Parameters:
			ctor.Body.Add([| self.$(p.Name) = $p |])
		return ctor

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

	def constructorsWithDefaultValues(fields as (Field)):

		for i in range(len(fields)-1, -1):
			if defaultValueOf(fields[i]) is null:
				break

			requiredFields = fields[:i]
			optionalFields = fields[i:]

			ctor = constructorDeclarationFor(requiredFields)
			baseCtorInvocation = [| self() |]
			for p in ctor.Parameters:
				baseCtorInvocation.Arguments.Add([| $p |])
			for f in optionalFields:
				baseCtorInvocation.Arguments.Add(defaultValueOf(f))
			ctor.Body.Add(baseCtorInvocation)
			yield ctor

	def defaultValueOf(f as Field) as Expression:
		return f["DefaultValue"]

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

	def fieldFrom(node as Expression) as Field:
		match node:
			case [| *$(ReferenceExpression(Name: name)) as $(arrayType = ArrayTypeReference()) |]:
				return arrayField(name, arrayType)
			case [| *$(ReferenceExpression(Name: name)) |]:
				return arrayField(name, ArrayTypeReference(superTypeRef()))
			case [| $(ReferenceExpression(Name: name)) as $type |]:
				return fieldWith(name, type)
			case [| $declaration = $defaultValue |]:
				f = fieldFrom(declaration)
				f.Annotate("DefaultValue", defaultValue)
				return f
			case ReferenceExpression(Name: name):
				return fieldWith(name, superTypeRef())

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

	def superTypeRef():
		return TypeReference.Lift(_superType)

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

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

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

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

def adtFieldToString(v):
	if v is null: return "null"
	if v isa System.Array: return "[$(join(v, ', '))]"
	return v.ToString()

namespace Boo.Adt

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

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

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

Usage:

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

	data SingleValue(Value as int)

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

	DataMacroExpansion(data)

class DataMacroExpansion:

	_module as TypeDefinition
	_superType as TypeDefinition

	def constructor(node as MacroStatement):

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

		match node.Arguments[0]:
			case [| $left = $right |]:
				assert node.Body.IsEmpty
				_superType = createBaseType(left)
				expandDataConstructors(right)

			case [| $(ctor=MethodInvocationExpression()) < $superType |]:
				_superType = [|
					class $superType:
						pass
				|]
				expandDataConstructorWithBody(ctor, node.Body)

			case ctor=MethodInvocationExpression():
				_superType = [|
					class object:
						pass
				|]
				expandDataConstructorWithBody(ctor, node.Body)

	def expandDataConstructorWithBody(ctor as Expression, body as Block):
		expandDataConstructor(ctor).Members.Extend(TypeMember.Lift(body))

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

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

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

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

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

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

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

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

		ctor = constructorForFields(fields)
		addSuperInvocationTo ctor

		type.Members.Add(ctor)
		for ctor in constructorsWithDefaultValues(fields):
			type.Members.Add(ctor)

		registerType(type)
		return type

	def addSuperInvocationTo(ctor as Constructor):
		superInvocation = [| super() |]
		i = 0
		for field as Field in fieldsOf(_superType):
			ctor.Parameters.Insert(i++,
				ParameterDeclaration(Name: field.Name, Type: field.Type))
			superInvocation.Arguments.Add(ReferenceExpression(field.Name))
		ctor.Body.Insert(0, superInvocation)

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

	def equalsForType(type as TypeDefinition):

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

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

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

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

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

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

			comma = true

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

	def toStringForField(field as Field):
		return [| Boo.Adt.adtFieldToString(self.$(field.Name)) |]

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

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

	def constructorForFields(fields as (Field)):
		ctor = constructorDeclarationFor(fields)
		for p in ctor.Parameters:
			ctor.Body.Add([| self.$(p.Name) = $p |])
		return ctor

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

	def constructorsWithDefaultValues(fields as (Field)):

		for i in range(len(fields)-1, -1):
			if defaultValueOf(fields[i]) is null:
				break

			requiredFields = fields[:i]
			optionalFields = fields[i:]

			ctor = constructorDeclarationFor(requiredFields)
			baseCtorInvocation = [| self() |]
			for p in ctor.Parameters:
				baseCtorInvocation.Arguments.Add([| $p |])
			for f in optionalFields:
				baseCtorInvocation.Arguments.Add(defaultValueOf(f))
			ctor.Body.Add(baseCtorInvocation)
			yield ctor

	def defaultValueOf(f as Field) as Expression:
		return f["DefaultValue"]

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

	def fieldFrom(node as Expression) as Field:
		match node:
			case [| *$(ReferenceExpression(Name: name)) as $(arrayType = ArrayTypeReference()) |]:
				return arrayField(name, arrayType)
			case [| *$(ReferenceExpression(Name: name)) |]:
				return arrayField(name, ArrayTypeReference(superTypeRef()))
			case [| $(ReferenceExpression(Name: name)) as $type |]:
				return fieldWith(name, type)
			case [| $declaration = $defaultValue |]:
				f = fieldFrom(declaration)
				f.Annotate("DefaultValue", defaultValue)
				return f
			case ReferenceExpression(Name: name):
				return fieldWith(name, superTypeRef())

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

	def superTypeRef():
		return TypeReference.Lift(_superType)

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

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

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

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

def adtFieldToString(v):
	if v is null: return "null"
	if v isa System.Array: return "[$(join(v, ', '))]"
	return v.ToString()


do not repeat run-time exceptions.

do not repeat run-time exceptions.

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

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

namespace Boo.Lang.Interpreter

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

class InteractiveInterpreter2(AbstractInterpreter):

	_values = {}

	_declarations = {}

	_representers = []

	[property(ShowWarnings)]
	_showWarnings = false

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

	[getter(LastValue)]
	_lastValue

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

	_entityNameComparer = EntityNameComparer()

	def constructor():
		super()
		Initialize()

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

	def Initialize():
		_disableColors = true if Environment.GetEnvironmentVariable("BOOISH_DISABLE_COLORS") is not null
		if not _disableColors: #make sure setting color does not throw an exception
			try:
				Console.ForegroundColor = ConsoleColor.DarkGray
			except:
				_disableColors = true
		_disableAutocompletion = true if Environment.GetEnvironmentVariable("BOOISH_DISABLE_AUTOCOMPLETION") is not null
		InitializeStandardReferences()
		LoadHistory()

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

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

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

	Line as string:
		get:
			return _line.ToString()

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

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

	LineIndentLen as int:
		get:
			return IndentChars.Length * _indent

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

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

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

	[property(IndentChars)]
	_indentChars = "    "

	[property(DisableColors)]
	_disableColors = false

	[property(DisableAutocompletion)]
	_disableAutocompletion = false

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

	[property(PromptColor)]
	_promptColor = ConsoleColor.DarkGreen

	[property(ExceptionColor)]
	_exceptionColor = ConsoleColor.DarkRed

	[property(SuggestionsColor)]
	_suggestionsColor = ConsoleColor.DarkYellow

	[property(SelectedSuggestionColor)]
	_selectedSuggestionColor = ConsoleColor.DarkMagenta

	[property(SelectedSuggestionIndex)]
	_selectedSuggIdx as int?

	[property(Suggestions)]
	_suggestions as (object)

	CanAutoComplete as bool:
		get:
			return _selectedSuggIdx is not null

	private _builtins as (IEntity)
	private _filter as string

	private def ConsolePrintPrompt():
		ConsolePrintPrompt(true)

	private def ConsolePrintPrompt(autoIndent as bool):
		return if _quit
		Console.ForegroundColor = _promptColor if not _disableColors
		Console.Write(CurrentPrompt)
		Console.ResetColor() if not _disableColors
		if autoIndent and CurrentPrompt == BlockPrompt:
			for i in range(_indent):
				WriteIndent()

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

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

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

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

		i = 0

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

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

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

	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]
			_suggestions = SuggestCodeCompletion(codeToComplete+"__codecomplete__")
			_filter = query[query.LastIndexOf('.')+1:]
			_suggestions = array(e for e in _suggestions as (IEntity)
								unless not e.Name.StartsWith(_filter)) as (IEntity)

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

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

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

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

		_selectedSuggIdx = null
		_suggestions = null

	private _beforeHistory = string.Empty

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

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

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

			newLine = keyChar in Environment.NewLine

			if char.IsControl(keyChar):
				control = true
				if keyChar == char('\t'):
					if LineLen > 0 and (char.IsLetterOrDigit(LineLastChar) or LineLastChar == char('.')):
						_selectedSuggIdx = 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 _selectedSuggIdx > 0:
							_selectedSuggIdx--
						else:
							_selectedSuggIdx = len(_suggestions)
						DisplaySuggestions()
					elif key == ConsoleKey.RightArrow:
						if _selectedSuggIdx <= len(_suggestions):
							_selectedSuggIdx++
						else:
							_selectedSuggIdx = 0
						DisplaySuggestions()
					if newLine:
						AutoComplete()
						continue
				if not newLine:
					continue

			_selectedSuggIdx = 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
					_indent-- if Line.EndsWith(IndentChars+"pass")

					if _indent == 0:
						try:
							InternalLoopEval(_buffer.ToString())
						except x as System.Reflection.TargetInvocationException:
							ConsolePrintException(x.InnerException)
						except x:
							ConsolePrintException(x)
						ensure:
							_buffer.Length = 0 #truncate buffer

				LineLen = 0 #truncate line
				ConsolePrintPrompt()

			lastChar = keyChar

		SaveHistory()
		DisplayGoodbye()

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

		cmd = line.Split()

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

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

		else:
			return false
		return true

	private _indent as int = 0

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

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

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

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

	override def SetLastValue(value):
		_lastValue = value

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

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

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

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

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

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

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

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

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

	def DisplayHelp():
		Console.ForegroundColor = _interpreterColor	if not _disableColors
		print """The following builtin functions are available :
    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"""
		Console.ResetColor() if not _disableColors

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	callable Representer(value, writer as TextWriter)

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

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

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

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

namespace Boo.Lang.Interpreter

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

class InteractiveInterpreter2(AbstractInterpreter):

	_values = {}

	_declarations = {}

	_representers = []

	[property(ShowWarnings)]
	_showWarnings = false

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

	[getter(LastValue)]
	_lastValue

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

	_entityNameComparer = EntityNameComparer()

	def constructor():
		super()
		Initialize()

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

	def Initialize():
		_disableColors = true if Environment.GetEnvironmentVariable("BOOISH_DISABLE_COLORS") is not null
		if not _disableColors: #make sure setting color does not throw an exception
			try:
				Console.ForegroundColor = ConsoleColor.DarkGray
			except:
				_disableColors = true
		_disableAutocompletion = true if Environment.GetEnvironmentVariable("BOOISH_DISABLE_AUTOCOMPLETION") is not null
		InitializeStandardReferences()
		LoadHistory()

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

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

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

	Line as string:
		get:
			return _line.ToString()

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

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

	LineIndentLen as int:
		get:
			return IndentChars.Length * _indent

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

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

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

	[property(IndentChars)]
	_indentChars = "    "

	[property(DisableColors)]
	_disableColors = false

	[property(DisableAutocompletion)]
	_disableAutocompletion = false

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

	[property(PromptColor)]
	_promptColor = ConsoleColor.DarkGreen

	[property(ExceptionColor)]
	_exceptionColor = ConsoleColor.DarkRed

	[property(SuggestionsColor)]
	_suggestionsColor = ConsoleColor.DarkYellow

	[property(SelectedSuggestionColor)]
	_selectedSuggestionColor = ConsoleColor.DarkMagenta

	[property(SelectedSuggestionIndex)]
	_selectedSuggIdx as int?

	[property(Suggestions)]
	_suggestions as (object)

	CanAutoComplete as bool:
		get:
			return _selectedSuggIdx is not null

	private _builtins as (IEntity)
	private _filter as string

	private def ConsolePrintPrompt():
		ConsolePrintPrompt(true)

	private def ConsolePrintPrompt(autoIndent as bool):
		return if _quit
		Console.ForegroundColor = _promptColor if not _disableColors
		Console.Write(CurrentPrompt)
		Console.ResetColor() if not _disableColors
		if autoIndent and CurrentPrompt == BlockPrompt:
			for i in range(_indent):
				WriteIndent()

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

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

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

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

		i = 0

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

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

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

	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]
			_suggestions = SuggestCodeCompletion(codeToComplete+"__codecomplete__")
			_filter = query[query.LastIndexOf('.')+1:]
			_suggestions = array(e for e in _suggestions as (IEntity)
								unless not e.Name.StartsWith(_filter)) as (IEntity)

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

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

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

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

		_selectedSuggIdx = null
		_suggestions = null

	private _beforeHistory = string.Empty

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

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

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

			newLine = keyChar in Environment.NewLine

			if char.IsControl(keyChar):
				control = true
				if keyChar == char('\t'):
					if LineLen > 0 and (char.IsLetterOrDigit(LineLastChar) or LineLastChar == char('.')):
						_selectedSuggIdx = 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 _selectedSuggIdx > 0:
							_selectedSuggIdx--
						else:
							_selectedSuggIdx = len(_suggestions)
						DisplaySuggestions()
					elif key == ConsoleKey.RightArrow:
						if _selectedSuggIdx <= len(_suggestions):
							_selectedSuggIdx++
						else:
							_selectedSuggIdx = 0
						DisplaySuggestions()
					if newLine:
						AutoComplete()
						continue
				if not newLine:
					continue

			_selectedSuggIdx = 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
					_indent-- if Line.EndsWith(IndentChars+"pass")

					if _indent == 0:
						try:
							InternalLoopEval(_buffer.ToString())
							_buffer.Length = 0 #truncate buffer
						except x as System.Reflection.TargetInvocationException:
							ConsolePrintException(x.InnerException)
						except x:
							ConsolePrintException(x)

				LineLen = 0 #truncate line
				ConsolePrintPrompt()

			lastChar = keyChar

		SaveHistory()
		DisplayGoodbye()

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

		cmd = line.Split()

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

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

		else:
			return false
		return true

	private _indent as int = 0

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

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

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

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

	override def SetLastValue(value):
		_lastValue = value

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

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

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

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

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

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

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

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

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

	def DisplayHelp():
		Console.ForegroundColor = _interpreterColor	if not _disableColors
		print """The following builtin functions are available :
    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"""
		Console.ResetColor() if not _disableColors

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	callable Representer(value, writer as TextWriter)

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

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

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