booish2: fix for <enter>

booish2: fix for <enter>

git-svn-id: fcce7d6ce0ac15bf24f68871b5145d46fc068279@2416 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.IO
import System.Text
import System.Text.RegularExpressions
import System.Reflection
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 = ":", "\\"

	[getter(LastValue)]
	_lastValue

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

	def constructor():
		super()
		InitializeStandardReferences()

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

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

	private _buffer = StringBuilder()

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

	[property(PromptColor)]
	_promptColor = ConsoleColor.Green

	[property(ExceptionColor)]
	_exceptionColor = ConsoleColor.Red

	[property(SuggestionsColor)]
	_suggestionsColor = ConsoleColor.Yellow

	[property(SelectedSuggestionColor)]
	_selectedSuggestionColor = ConsoleColor.Magenta

	[property(SelectedSuggestionIndex)]
	_selectedSuggestionIndex = -1

	[property(Suggestions)]
	_suggestions as (object)

	_builtins as (IEntity)

	_filter as string

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

	private def ConsolePrintPrompt():
		Console.ForegroundColor = _promptColor
		if _indent == 0:
			Console.Write(">>>")
		else:
			Console.Write("...")
			#TODO: automatic indentation option ?
			#for i in range(_indent):
			#	_buffer.Append("\t")
			#	Console.Write("\t")
		Console.ResetColor()

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

	protected def ConsolePrintSuggestions():
		cursorLeft = Console.CursorLeft
		cursorTop = Console.CursorTop
		Console.Write("\n")

		i = 0

		if _suggestions isa (string):
			for l in _suggestions as (string):
				Console.ForegroundColor = _suggestionsColor
				if i > 0:
					Console.Write(", ")
				if i == _selectedSuggestionIndex:
					Console.ForegroundColor = _selectedSuggestionColor
				Console.Write(l)
				i++

		elif _suggestions isa (IEntity):
			for e in _suggestions as (IEntity):
				Console.ForegroundColor = _suggestionsColor
				if i > 0:
					Console.Write(", ")
				if i == _selectedSuggestionIndex:
					Console.ForegroundColor = _selectedSuggestionColor
				Console.Write(DescribeEntity(e))
				i++

		Console.ResetColor()
		Console.CursorLeft = cursorLeft
		Console.CursorTop = cursorTop
		Console.Write("\n")
		ConsolePrintPrompt()
		Console.Write(_buffer.ToString())

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

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

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

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

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

		s = _suggestions[_selectedSuggestionIndex] as IEntity
		if s:
			if s.EntityType == EntityType.Method:
				m = s as IMethod
				completion = s.Name[len(_filter):]
				Console.Write(completion)
				_buffer.Append(completion)
				if not m.GetParameters() or m.GetParameters().Length == 0:
					Console.Write('()')
					_buffer.Append('()')
				else:
					Console.Write('(')
					_buffer.Append('(')

			else: # not a Method
				completion = s.Name[len(_filter):]
				Console.Write(completion)
				_buffer.Append(completion)
		else:
			completion = (_suggestions[_selectedSuggestionIndex] as string)[len(_filter):]
			Console.Write(completion)
			_buffer.Append(completion)

		_selectedSuggestionIndex = -1
		_suggestions = null

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

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

			newLine = keyChar in Environment.NewLine #FIXME: win32 is "\r\n"

			if char.IsControl(keyChar):
				control = true
				if keyChar == char('\t'):
					test = char(' ')
					test = _buffer.ToString()[_buffer.Length-1] if _buffer.Length > 0
					if char.IsLetterOrDigit(test) or test == char('.'):
						_selectedSuggestionIndex = 0
						DisplaySuggestions(_buffer.ToString())
					else:
						_buffer.Append("\t")
						Console.Write("\t")
				if key == ConsoleKey.Backspace:
					if Console.CursorLeft > 3 and _buffer.Length > 0:
						Console.CursorLeft--
						Console.Write(" ")
						Console.CursorLeft--
						_buffer.Length--
					#else:	#TODO: flash background?
					#	Console.Beep()
				if CanAutoComplete:
					if key == ConsoleKey.LeftArrow:
						if _selectedSuggestionIndex > 0:
							_selectedSuggestionIndex--
						else:
							_selectedSuggestionIndex = len(_suggestions)
						DisplaySuggestions(_buffer.ToString())
					if key == ConsoleKey.RightArrow:
						if _selectedSuggestionIndex+1 < len(_suggestions):
							_selectedSuggestionIndex++
						else:
							_selectedSuggestionIndex = 0
						DisplaySuggestions(_buffer.ToString())
					if newLine:
						AutoComplete()
						continue
				if not newLine:
					continue

			_selectedSuggestionIndex = -1

			_buffer.Append(keyChar) if not newLine and not control
			Console.Write(keyChar) if not newLine
			Console.Write(Environment.NewLine) if newLine
			if newLine:
				line = _buffer.ToString()

				#TODO: refactor this into runCommand(line)
				if line == "/q":
					break
				if line == "/?":
					DisplayHelp()
					_selectedSuggestionIndex = -1
					_buffer.Length = 0
					ConsolePrintPrompt()
					continue

				_buffer.Append(Environment.NewLine)
				line = _buffer.ToString()

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

			lastChar = keyChar

		DisplayGoodbye()

	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()
		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
		for problem as duck in problems:
			markLocation(problem.LexicalInfo)
			type = ("WARNING", "ERROR")[problem isa CompilerError]
			_print("${type}: ${problem.Message}")
		Console.ResetColor()
		if problems.Count > 0:
			return true
		return false

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

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

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

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

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

	def DisplayGoodbye():	// booish is friendly
		Console.ForegroundColor = _interpreterColor
		print "\nHave a nice day!" if date.Now.Hour < 16
		print "\nHave a nice evening!" if date.Now.Hour >= 16
		Console.ResetColor()

	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"):
			result = EvalCompilerInput(FileInput(path))
			if ShowWarnings:
				DisplayProblems(result.Warnings)
			if not DisplayProblems(result.Errors):
				ProcessLastValue()
		else:
			try:
				References.Add(System.Reflection.Assembly.LoadFrom(path))
			except e:
				print e.Message

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

	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!"

#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.IO
import System.Text
import System.Text.RegularExpressions
import System.Reflection
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 = ":", "\\"

	[getter(LastValue)]
	_lastValue

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

	def constructor():
		super()
		InitializeStandardReferences()

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

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

	private _buffer = StringBuilder()

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

	[property(PromptColor)]
	_promptColor = ConsoleColor.Green

	[property(ExceptionColor)]
	_exceptionColor = ConsoleColor.Red

	[property(SuggestionsColor)]
	_suggestionsColor = ConsoleColor.Yellow

	[property(SelectedSuggestionColor)]
	_selectedSuggestionColor = ConsoleColor.Magenta

	[property(SelectedSuggestionIndex)]
	_selectedSuggestionIndex = -1

	[property(Suggestions)]
	_suggestions as (object)

	_builtins as (IEntity)

	_filter as string

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

	private def ConsolePrintPrompt():
		Console.ForegroundColor = _promptColor
		if _indent == 0:
			Console.Write(">>>")
		else:
			Console.Write("...")
			#TODO: automatic indentation option ?
			#for i in range(_indent):
			#	_buffer.Append("\t")
			#	Console.Write("\t")
		Console.ResetColor()

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

	protected def ConsolePrintSuggestions():
		cursorLeft = Console.CursorLeft
		cursorTop = Console.CursorTop
		Console.Write("\n")

		i = 0

		if _suggestions isa (string):
			for l in _suggestions as (string):
				Console.ForegroundColor = _suggestionsColor
				if i > 0:
					Console.Write(", ")
				if i == _selectedSuggestionIndex:
					Console.ForegroundColor = _selectedSuggestionColor
				Console.Write(l)
				i++

		elif _suggestions isa (IEntity):
			for e in _suggestions as (IEntity):
				Console.ForegroundColor = _suggestionsColor
				if i > 0:
					Console.Write(", ")
				if i == _selectedSuggestionIndex:
					Console.ForegroundColor = _selectedSuggestionColor
				Console.Write(DescribeEntity(e))
				i++

		Console.ResetColor()
		Console.CursorLeft = cursorLeft
		Console.CursorTop = cursorTop
		Console.Write("\n")
		ConsolePrintPrompt()
		Console.Write(_buffer.ToString())

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

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

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

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

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

		s = _suggestions[_selectedSuggestionIndex] as IEntity
		if s:
			if s.EntityType == EntityType.Method:
				m = s as IMethod
				completion = s.Name[len(_filter):]
				Console.Write(completion)
				_buffer.Append(completion)
				if not m.GetParameters() or m.GetParameters().Length == 0:
					Console.Write('()')
					_buffer.Append('()')
				else:
					Console.Write('(')
					_buffer.Append('(')

			else: # not a Method
				completion = s.Name[len(_filter):]
				Console.Write(completion)
				_buffer.Append(completion)
		else:
			completion = (_suggestions[_selectedSuggestionIndex] as string)[len(_filter):]
			Console.Write(completion)
			_buffer.Append(completion)

		_selectedSuggestionIndex = -1
		_suggestions = null

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

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

			newLine = keyChar in Environment.NewLine #FIXME: win32 is "\r\n"

			if char.IsControl(keyChar):
				control = true
				if keyChar == char('\t'):
					test = char(' ')
					test = _buffer.ToString()[_buffer.Length-1] if _buffer.Length > 0
					if char.IsLetterOrDigit(test) or test == char('.'):
						_selectedSuggestionIndex = 0
						DisplaySuggestions(_buffer.ToString())
					else:
						_buffer.Append("\t")
						Console.Write("\t")
				if key == ConsoleKey.Backspace:
					if Console.CursorLeft > 3 and _buffer.Length > 0:
						Console.CursorLeft--
						Console.Write(" ")
						Console.CursorLeft--
						_buffer.Length--
					#else:	#TODO: flash background?
					#	Console.Beep()
				if CanAutoComplete:
					if key == ConsoleKey.LeftArrow:
						if _selectedSuggestionIndex > 0:
							_selectedSuggestionIndex--
						else:
							_selectedSuggestionIndex = len(_suggestions)
						DisplaySuggestions(_buffer.ToString())
					if key == ConsoleKey.RightArrow:
						if _selectedSuggestionIndex+1 < len(_suggestions):
							_selectedSuggestionIndex++
						else:
							_selectedSuggestionIndex = 0
						DisplaySuggestions(_buffer.ToString())
					if newLine:
						AutoComplete()
						continue
				if not newLine:
					continue

			_selectedSuggestionIndex = -1

			_buffer.Append(keyChar) if not newLine and not control
			Console.Write(keyChar)
			if newLine:
				line = _buffer.ToString()

				#TODO: refactor this into runCommand(line)
				if line == "/q":
					break
				if line == "/?":
					DisplayHelp()
					_selectedSuggestionIndex = -1
					_buffer.Length = 0
					ConsolePrintPrompt()
					continue

				_buffer.Append(keyChar)
				line = _buffer.ToString()

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

			lastChar = keyChar

		DisplayGoodbye()

	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()
		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
		for problem as duck in problems:
			markLocation(problem.LexicalInfo)
			type = ("WARNING", "ERROR")[problem isa CompilerError]
			_print("${type}: ${problem.Message}")
		Console.ResetColor()
		if problems.Count > 0:
			return true
		return false

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

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

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

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

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

	def DisplayGoodbye():	// booish is friendly
		Console.ForegroundColor = _interpreterColor
		print "\nHave a nice day!" if date.Now.Hour < 16
		print "\nHave a nice evening!" if date.Now.Hour >= 16
		Console.ResetColor()

	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"):
			result = EvalCompilerInput(FileInput(path))
			if ShowWarnings:
				DisplayProblems(result.Warnings)
			if not DisplayProblems(result.Errors):
				ProcessLastValue()
		else:
			try:
				References.Add(System.Reflection.Assembly.LoadFrom(path))
			except e:
				print e.Message

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

	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!"


Protect against mutual dependencies

Protect against mutual dependencies

namespace booi

from System import AppDomain, DateTime
from System.IO import Path, Directory, File
from System.Reflection import Assembly, AssemblyName
from System.Diagnostics import Stopwatch

from Boo.Lang.Compiler import CompilerContext, \
                              CompilerParameters as BooCompilerParameters
from Boo.Lang.Compiler.IO import FileInput, StringInput
from Boo.Lang.Compiler.Pipelines import CompileToMemory, CompileToFile
from Boo.Lang.Compiler.TypeSystem.Reflection import IAssemblyReference

class CompilerParameters(BooCompilerParameters):

    Context as CompilerContext:
        get: return CompilerContext.Current

    property Cache as bool

    # Keeps track namespaces already mapped to a directory to
    # detect mutual/cyclic dependencies
    property MappedNamespaces = []

    def constructor():
        super()

    def constructor(loadDefault as bool):
        super(loadDefault)

    virtual protected def GetCacheFilePath(path as string) as string:
        return path + '.booc'

    virtual protected def GetNamespacePaths(ns as string) as string*:
    """ Builds a list of possible directories to map an assembly name. The
        algorithm starts checking dirs separated by dots and generates
        variants with sub directories.
        ie: foo.bar.baz, foo.bar/baz, foo/bar/baz
    """
        paths = []
        parts = ns.Split(char('.'))
        for i in range(len(parts), 0):
            paths.Add(Path.Combine(
                join(parts[:i], '.'),
                Path.Combine(*parts[i:])
            ))

        for libpath in LibPaths:
            Context.TraceInfo('Looking for namespace {0} in "{1}"', ns, libpath)
            for dirpath in paths:
                path = Path.Combine(libpath, dirpath)
                if Directory.Exists(path):
                    yield path

    virtual protected def ForkCompiler(asmname as string, path as string) as CompilerContext:
    """ Forks the current compiler into a new one to compile the source files referenced
        in an import.
    """
        # Create new params copying settings from current one
        params = CompilerParameters(false)
        params.Cache = self.Cache
        params.Strict = self.Strict
        params.Debug = self.Debug
        params.References = self.References
        params.MappedNamespaces = self.MappedNamespaces

        # Copy configured lib paths
        for path in self.LibPaths:
            params.LibPaths.AddUnique(path)

        # If we are generating an assembly on disk do so for this namespace too
        if self.OutputAssembly:
            params.OutputAssembly = Path.Combine(
                Path.GetDirectoryName(self.OutputAssembly),
                asmname + '.dll'
            )
            params.Pipeline = CompileToFile()
        else:
            # TODO: When compiling to memory we cannot use compilation cache.
            #       Unless we define a temp path for the assemblies to be generated
            params.Pipeline = CompileToMemory()

        return CompilerContext(params)

    override def ForName(asmname as string, throwOnError as bool) as Assembly:
        # Loading default assemblies is performed normally
        if not Context:
            return super(asmname, throwOnError)

        # Try to load the assembly using Boo's default mechanism
        try:
            return super(asmname, true)
        except ex as System.ApplicationException:
            pass

        # Try to compile from source files
        asm = FromSources(asmname)
        if not asm and throwOnError:
            raise System.ApplicationException("Namespace $asmname not found")

        return asm

    virtual def FromSources(asmname as string) as Assembly:

        Context.TraceEnter('FromSources {0}', asmname)

        watch = Stopwatch()
        watch.Start()

        # Check every possible variation of paths for a namespace
        for path in GetNamespacePaths(asmname):
            # Collect source files, at least one must be present
            files = Directory.GetFiles(path, '*.boo')
            continue if not len(files)

            if asmname in MappedNamespaces:
                Context.TraceLeave('Namespace {0} already mapped. Mutual reference?', asmname)
                return null

            MappedNamespaces.AddUnique(asmname)

            Context.TraceInfo("Mapping namespace {0} to '{1}'", asmname, path)

            cachepath = GetCacheFilePath(path)
            if Cache and File.Exists(cachepath):
                try:
                    cache = CompilationCache.Load(cachepath)
                    if cache.CheckValidity(Context):
                        # Make sure we copy the .dll into the output directory
                        if self.OutputAssembly:
                            asmpath = Path.GetDirectoryName(self.OutputAssembly)
                            cache.DumpAssembly(asmpath, asmname)
                            Context.TraceInfo('Dumped cached assembly to "{0}"', asmpath)

                        asm = cache.GetAssembly()
                        AddAssembly(asm)
                        Context.TraceLeave('Using cached compilation for {0}', asmname)
                        return asm
                    else:
                        Context.TraceInfo('Cached compilation is out of date for {0}', asmname)
                except ex:
                    Context.TraceError('Error loading compilation cache for "{0}": {1}', asmname, ex)

            context = Context

            # Create new compiler context
            ctxt = ForkCompiler(asmname, path)

            # HACK: Dynamic assemblies produce different module version ids depending on how they
            #       are loaded. So we use a different mechanism to signal changes in them, using a
            #       custom assembly attribute recording the compilation time we can later check
            #       if they have changed correlating this value with the one stored in cache
            #       references.
            ctxt.Parameters.Input.Add(
                StringInput(
                    'BooiAssemblyInfo.boo',
                    '[assembly: booi.CompilationTimeAttribute({0})]' % (DateTime.UtcNow.ToFileTimeUtc(),)
                )
            )

            for fname in files:
                ctxt.Parameters.Input.Add(FileInput(fname))

            # Run the compiler
            ctxt.Parameters.Pipeline.Run(ctxt)
            # Inject warnings and errors into the parent context
            Context.Warnings.AddRange(ctxt.Warnings)
            Context.Errors.AddRange(ctxt.Errors)

            if len(ctxt.Errors):
                Context.TraceLeave('Errors compiling {0} from {1}', asmname, path)
                return null
                # return AppDomain.CurrentDomain.DefineDynamicAssembly(
                #     AssemblyName('ImportError'),
                #     System.Reflection.Emit.AssemblyBuilderAccess.Run
                # )

            if Cache:
                try:
                    cache = CompilationCache()
                    cache.BasePath = path
                    for fpath in files:
                        fname = Path.GetFileName(fpath)
                        cache.Sources[fname] = File.GetLastWriteTimeUtc(fpath).ToFileTimeUtc()

                    for rf as IAssemblyReference in ctxt.Parameters.References:
                        asm = rf.Assembly
                        attrs = asm.GetCustomAttributes(CompilationTimeAttribute, false)
                        if len(attrs):
                            dt = (attrs[0] as CompilationTimeAttribute).GetDateTime()
                            cache.References[rf.Name] = dt.ToFileTimeUtc()
                        else:
                            cache.References[rf.Name] = asm.ManifestModule.ModuleVersionId

                    cache.Save(cachepath, ctxt.Parameters.OutputAssembly)
                    Context.TraceInfo('Saved cached compilation into {0}', cachepath)
                except ex:
                    Context.TraceError('Error saving cached compilation: {0}', ex)

            watch.Stop()
            Context.TraceLeave('Compilation for {0} took {1}ms', asmname, watch.ElapsedMilliseconds)

            return ctxt.GeneratedAssembly

        Context.TraceLeave('No directory found to map {0}', asmname)
        return null

namespace booi

from System import AppDomain, DateTime
from System.IO import Path, Directory, File
from System.Reflection import Assembly, AssemblyName
from System.Diagnostics import Stopwatch

from Boo.Lang.Compiler import CompilerContext, \
                              CompilerParameters as BooCompilerParameters
from Boo.Lang.Compiler.IO import FileInput, StringInput
from Boo.Lang.Compiler.Pipelines import CompileToMemory, CompileToFile
from Boo.Lang.Compiler.TypeSystem.Reflection import IAssemblyReference

class CompilerParameters(BooCompilerParameters):

    Context as CompilerContext:
        get: return CompilerContext.Current

    property Cache as bool

    def constructor():
        super()

    def constructor(loadDefault as bool):
        super(loadDefault)

    virtual protected def GetCacheFilePath(path as string) as string:
        return path + '.booc'

    virtual protected def GetNamespacePaths(ns as string) as string*:
    """ Builds a list of possible directories to map an assembly name. The
        algorithm starts checking dirs separated by dots and generates
        variants with sub directories.
        ie: foo.bar.baz, foo.bar/baz, foo/bar/baz
    """
        paths = []
        parts = ns.Split(char('.'))
        for i in range(len(parts), 0):
            paths.Add(Path.Combine(
                join(parts[:i], '.'),
                Path.Combine(*parts[i:])
            ))

        for libpath in LibPaths:
            Context.TraceInfo('Looking for namespace {0} in "{1}"', ns, libpath)
            for dirpath in paths:
                path = Path.Combine(libpath, dirpath)
                if Directory.Exists(path):
                    yield path

    virtual protected def ForkCompiler(asmname as string, path as string) as CompilerContext:
    """ Forks the current compiler into a new one to compile the source files referenced
        in an import.
    """
        # Create new params copying settings from current one
        params = CompilerParameters(false)
        params.Cache = self.Cache
        params.Strict = self.Strict
        params.Debug = self.Debug
        params.References = self.References

        # Copy configured lib paths
        for path in self.LibPaths:
            params.LibPaths.AddUnique(path)

        # If we are generating an assembly on disk do so for this namespace too
        if self.OutputAssembly:
            params.OutputAssembly = Path.Combine(
                Path.GetDirectoryName(self.OutputAssembly),
                asmname + '.dll'
            )
            params.Pipeline = CompileToFile()
        else:
            # TODO: When compiling to memory we cannot use compilation cache.
            #       Unless we define a temp path for the assemblies to be generated
            params.Pipeline = CompileToMemory()

        return CompilerContext(params)

    override def ForName(asmname as string, throwOnError as bool) as Assembly:
        # Loading default assemblies is performed normally
        if not Context:
            return super(asmname, throwOnError)

        # Try to load the assembly using Boo's default mechanism
        try:
            return super(asmname, true)
        except ex as System.ApplicationException:
            pass

        # Try to compile from source files
        asm = FromSources(asmname)

        if not asm and throwOnError:
            raise System.ApplicationException("Namespace $asmname not found")

        return asm

    virtual def FromSources(asmname as string) as Assembly:

        Context.TraceEnter('FromSources {0}', asmname)

        watch = Stopwatch()
        watch.Start()

        # Check every possible variation of paths for a namespace
        for path in GetNamespacePaths(asmname):
            # Collect source files, at least one must be present
            files = Directory.GetFiles(path, '*.boo')
            continue if not len(files)

            Context.TraceInfo("Mapping namespace {0} to '{1}'", asmname, path)

            cachepath = GetCacheFilePath(path)
            if Cache and File.Exists(cachepath):
                try:
                    cache = CompilationCache.Load(cachepath)
                    if cache.CheckValidity(Context):
                        # Make sure we copy the .dll into the output directory
                        if self.OutputAssembly:
                            asmpath = Path.GetDirectoryName(self.OutputAssembly)
                            cache.DumpAssembly(asmpath, asmname)
                            Context.TraceInfo('Dumped cached assembly to "{0}"', asmpath)

                        asm = cache.GetAssembly()
                        AddAssembly(asm)
                        Context.TraceLeave('Using cached compilation for {0}', asmname)
                        return asm
                    else:
                        Context.TraceInfo('Cached compilation is out of date for {0}', asmname)
                except ex:
                    Context.TraceError('Error loading compilation cache for "{0}": {1}', asmname, ex)

            # Create new compiler context
            ctxt = ForkCompiler(asmname, path)

            # HACK: Dynamic assemblies produce different module version ids depending on how they
            #       are loaded. So we use a different mechanism to signal changes in them, using a
            #       custom assembly attribute recording the compilation time we can later check
            #       if they have changed correlating this value with the one stored in cache
            #       references.
            ctxt.Parameters.Input.Add(
                StringInput(
                    'BooiAssemblyInfo.boo',
                    '[assembly: booi.CompilationTimeAttribute({0})]' % (DateTime.UtcNow.ToFileTimeUtc(),)
                )
            )

            for fname in files:
                ctxt.Parameters.Input.Add(FileInput(fname))

            # Run the compiler
            ctxt.Parameters.Pipeline.Run(ctxt)
            # Inject warnings and errors into the parent context
            Context.Warnings.AddRange(ctxt.Warnings)
            Context.Errors.AddRange(ctxt.Errors)

            if len(ctxt.Errors):
                # return AppDomain.CurrentDomain.DefineDynamicAssembly(
                #     AssemblyName('ImportError'),
                #     AssemblyBuilderAccess.Run
                # )
                break

            if Cache:
                try:
                    cache = CompilationCache()
                    cache.BasePath = path
                    for fpath in files:
                        fname = Path.GetFileName(fpath)
                        cache.Sources[fname] = File.GetLastWriteTimeUtc(fpath).ToFileTimeUtc()

                    for rf as IAssemblyReference in ctxt.Parameters.References:
                        asm = rf.Assembly
                        attrs = asm.GetCustomAttributes(CompilationTimeAttribute, false)
                        if len(attrs):
                            dt = (attrs[0] as CompilationTimeAttribute).GetDateTime()
                            cache.References[rf.Name] = dt.ToFileTimeUtc()
                        else:
                            cache.References[rf.Name] = asm.ManifestModule.ModuleVersionId

                    cache.Save(cachepath, ctxt.Parameters.OutputAssembly)
                    Context.TraceInfo('Saved cached compilation into {0}', cachepath)
                except ex:
                    Context.TraceError('Error saving cached compilation: {0}', ex)

            watch.Stop()
            Context.TraceLeave('Compilation for {0} took {1}ms', asmname, watch.ElapsedMilliseconds)

            return ctxt.GeneratedAssembly

        Context.TraceLeave('No directory found to map {0}', asmname)
        return null


preserve source information

preserve source information

git-svn-id: fcce7d6ce0ac15bf24f68871b5145d46fc068279@2692 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.IO
import Boo.Lang
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.TypeSystem
import Boo.Lang.Compiler.IO

class AbstractInterpreter:

	_compiler as BooCompiler

	_parser as BooCompiler

	_imports as ImportCollection

	_referenceProcessor as ProcessInterpreterReferences

	[property(RememberLastValue)]
	_rememberLastValue as bool

	_inputId as int

	_suggestionCompiler as BooCompiler

	Ducky:
		get:
			return _compiler.Parameters.Ducky

		set:
			_compiler.Parameters.Ducky = value
			if _suggestionCompiler is not null:
				_suggestionCompiler.Parameters.Ducky = value

	def constructor([required] parser as ICompilerStep):

		_compiler = BooCompiler()
		_parser = BooCompiler()
		_imports = ImportCollection()
		_referenceProcessor = ProcessInterpreterReferences(self)

		pipeline = Pipelines.CompileToMemory()
		pipeline.RemoveAt(0)

		pipeline.Replace(Steps.ProcessMethodBodiesWithDuckTyping,
						ProcessVariableDeclarations(self))
		pipeline.InsertBefore(Steps.EmitAssembly, _referenceProcessor)

		index = pipeline.Find(Steps.IntroduceModuleClasses)
		cast(Steps.IntroduceModuleClasses, pipeline[index]).ForceModuleClass = true

		// avoid InvalidCastExceptions by always
		// defining callable types only once per run
		pipeline.Replace(Steps.InitializeTypeSystemServices, InitializeTypeSystemServices())
		pipeline.Add(CacheCallableTypes())

		_compiler.Parameters.Pipeline = pipeline
		_compiler.Parameters.Ducky = true
		_parser.Parameters.Pipeline = CompilerPipeline().Add(parser)

	def constructor():
		self(Pipelines.Parse.NewParserStep())

	abstract def Declare(name as string, type as System.Type):
		pass

	abstract def Lookup(name as string) as System.Type:
		pass

	virtual def SetLastValue(value):
		pass

	abstract def SetValue(name as string, value) as object:
		pass

	abstract def GetValue(name as string) as object:
		pass

	private def GetSuggestionCompiler():
		if _suggestionCompiler is null:
			pipeline = Pipelines.ResolveExpressions(BreakOnErrors: false)
			pipeline.Insert(1, AddRecordedImports(_imports))
			pipeline.Replace(
				Steps.ProcessMethodBodiesWithDuckTyping,
				ProcessExpressionsWithInterpreterNamespace(self))
			pipeline.Replace(
				Steps.InitializeTypeSystemServices,
				_compiler.Parameters.Pipeline.Get(InitializeTypeSystemServices))
			pipeline.Add(FindCodeCompleteSuggestion())
			_suggestionCompiler = BooCompiler()
			_suggestionCompiler.Parameters.Ducky = self.Ducky
			_suggestionCompiler.Parameters.Pipeline = pipeline
			// keep the references in sync
			_suggestionCompiler.Parameters.References = self.References

		return _suggestionCompiler

	Pipeline:
		get:
			return _compiler.Parameters.Pipeline

	References:
		get:
			return _compiler.Parameters.References

	def SuggestCodeCompletion(code as string) as (IEntity):
	"""
	The code must contain a __codecomplete__ member reference as a placeholder
	to the suggestion.

	The return value is a an array of possible members or namespaces to be inserted
	at the __codecomplete__.
	"""
		return FilterSuggestions(code, ResolveEntity(code))

	def ResolveEntity(code as string) as IEntity:
		compiler = GetSuggestionCompiler()
		try:
			compiler.Parameters.Input.Add(StringInput("<code>", PreProcessImportLine(code)))
			result = compiler.Run()
			return result["suggestion"]
		ensure:
			compiler.Parameters.Input.Clear()

	def FilterSuggestions(code as string, entity as IEntity):
		ns = entity as INamespace
		return array(IEntity, 0) if ns is null
		return GetChildNamespaces(ns) if code.StartsWith("import ")
		return FilteredMembers(TypeSystemServices.GetAllMembers(ns))

	def FilteredMembers(members as (IEntity)):
		return array(
				item
				for item in members
				unless IsSpecial(item) or not IsPublic(item))

	def IsSpecial(entity as IEntity):
		for prefix in ".", "___", "add_", "remove_", "raise_", "get_", "set_":
			return true if entity.Name.StartsWith(prefix)

	def IsPublic(entity as IEntity):
		member = entity as IMember
		return member is null or member.IsPublic

	private def GetChildNamespaces(parent as INamespace):
		return array(member
					for member in parent.GetMembers()
					if member.EntityType == EntityType.Namespace)

	private def PreProcessImportLine(code as string):
		match = @/^\s*import\s+((\w|\.)+)\s*$/.Match(code)
		if match.Success:
			return match.Groups[1].Value
		return code

	def Eval([required] code as string):
		return CompilerContext(false) if 0 == len(code)
		return EvalCompilerInput(StringInput("input${++_inputId}", code))

	def EvalCompilerInput(input as ICompilerInput):
		result = Parse(input)
		return result if len(result.Errors)
		return EvalCompileUnit(result.CompileUnit)

	def EvalCompileUnit(cu as CompileUnit):
		assert 1 == len(cu.Modules)

		module = cu.Modules[0]

		# remember the state of the module as of after parsing
		hasStatements = module.Globals.Statements.Count > 0
		hasMembers = module.Members.Count > 0

		module.Imports.Reject() do (item as Import):
			for existing in _imports:
				return true if existing.Namespace == item.Namespace

		if ((not hasStatements) and
			(not hasMembers) and
			0 == len(module.Imports)):
			return CompilerContext(cu)

		savedImports = module.Imports.Clone()
		module.Imports.ExtendWithClones(_imports)

		if hasStatements:
			if IsSingleEmptyMacroStatement(module):
				# simple references will be parsed as macros
				# but we want them to be evaluated
				# as references...
				ms = module.Globals.Statements[0] as MacroStatement
				if ms is not null:
					module.Globals.Statements.ReplaceAt(0,
						ExpressionStatement(
							ReferenceExpression(ms.LexicalInfo, ms.Name)))
			_compiler.Parameters.OutputType = CompilerOutputType.ConsoleApplication
		else:
			_compiler.Parameters.OutputType = CompilerOutputType.Library

		SetLastValue(null) if _rememberLastValue
		result = _compiler.Run(cu)
		return result if len(result.Errors)

		RecordImports(savedImports)

		asm = result.GeneratedAssembly
		_compiler.Parameters.References.Add(asm) if hasMembers

		InitializeModuleInterpreter(asm, module)

		ExecuteEntryPoint(asm) if hasStatements

		return result

	def ExecuteEntryPoint(asm as System.Reflection.Assembly):
		AppDomain.CurrentDomain.AssemblyResolve += AppDomain_AssemblyResolve
		try:
			asm.EntryPoint.Invoke(null, (null,))
		ensure:
			AppDomain.CurrentDomain.AssemblyResolve -= AppDomain_AssemblyResolve

	def AppDomain_AssemblyResolve(sender, args as ResolveEventArgs) as System.Reflection.Assembly:
		for reference in _compiler.Parameters.References:
			if reference.FullName == args.Name:
				return reference
		return null

	def Parse(input as ICompilerInput):
		_parser.Parameters.Input.Clear()
		_parser.Parameters.Input.Add(input)
		return _parser.Run()

	private def InitializeModuleInterpreter(asm as System.Reflection.Assembly,
										module as Module):
		moduleType = GetGeneratedType(asm, GetEntity(GetModuleEntity(module).ModuleClass))
		moduleType.GetField("ParentInterpreter").SetValue(null, self)

	private static def GetModuleEntity(module as Module) as ModuleEntity:
		return GetEntity(module)

	private static def GetGeneratedType(asm as System.Reflection.Assembly, type as IType):
		return asm.GetType(type.FullName)

	static def GetEntity(node as Node):
		return TypeSystemServices.GetEntity(node)

	static def GetOptionalEntity(node as Node):
		return TypeSystemServices.GetOptionalEntity(node)

	private def RecordImports(imports as ImportCollection):
		for imp in imports:
			imp.AssemblyReference = null
			_imports.Add(imp)

	private def IsSingleEmptyMacroStatement(m as Module):
		if 1 == len(m.Globals.Statements):
			macro = m.Globals.Statements[0] as MacroStatement
			if macro is not null:
				return 0 == len(macro.Arguments) and 0 == len(macro.Block.Statements)

	class InterpreterEntity(ITypedEntity):

		[getter(Name)]
		[getter(FullName)]
		_name as string

		[getter(Type)]
		_type as IType

		def constructor(name, type):
			_name = name
			_type = type

		EntityType:
			get:
				return TypeSystem.EntityType.Custom

		static def IsInterpreterEntity(entity as IEntity):
			return entity is not null and TypeSystem.EntityType.Custom == entity.EntityType

	class InterpreterNamespace(INamespace):

		[getter(ParentNamespace)]
		_parent as INamespace

		_tss as TypeSystemServices

		_interpreter as AbstractInterpreter

		_declarations = {}

		def constructor(interpreter, tss, parent):
			_interpreter = interpreter
			_tss = tss
			_parent = parent

		def Declare(name as string, type as IType):
			entity = InterpreterEntity(name, type)
			_declarations.Add(name, entity)
			return entity

		def Resolve(targetList as List, name as string, flags as EntityType) as bool:
			return false unless flags == EntityType.Any

			entity as IEntity = _declarations[name]
			if entity is null:
				type = _interpreter.Lookup(name)
				if type is not null:
					entity = Declare(name, _tss.Map(type))

			if entity is not null:
				targetList.Add(entity)
				return true

			return false

		def GetMembers():
			return array(IEntity, 0)

	class InterpreterTypeSystemServices(TypeSystemServices):

		[getter(CachedCallableTypes)]
		_cachedCallableTypes as List

		[getter(GeneratedCallableTypes)]
		_generatedCallableTypes = []

		def constructor(context, cache):
			super(context)
			_cachedCallableTypes = cache

		override def GetConcreteCallableType(sourceNode as Node,
											signature as CallableSignature):

			cached = GetCachedCallableType(signature)
			if cached is not null: return cached

			return CacheCallableType(super(sourceNode, signature))

		override def GetConcreteCallableType(sourceNode as Node,
											anonymousType as AnonymousCallableType):

			cached = GetCachedCallableType(anonymousType.GetSignature())
			if cached is not null: return cached

			return CacheCallableType(super(sourceNode, anonymousType))

		private def CacheCallableType(type as IType):
			_generatedCallableTypes.Add(type)
			return type

		private def GetCachedCallableType(signature as CallableSignature):
			for type as System.Type in _cachedCallableTypes:
				cached = Map(type) as ExternalCallableType
				if signature == cached.GetSignature():
					return cached

	class InitializeTypeSystemServices(Steps.AbstractCompilerStep):

		_cachedCallableTypes = []

		override def Run():
			Context.TypeSystemServices = InterpreterTypeSystemServices(Context, _cachedCallableTypes)

	class CacheCallableTypes(Steps.AbstractCompilerStep):

		override def Run():
			return if len(Errors)

			services as InterpreterTypeSystemServices = self.TypeSystemServices
			types = services.GeneratedCallableTypes
			return unless len(types)

			debug "caching", len(types), "callable types"
			for type as InternalCallableType in types:
				debug type
				services.CachedCallableTypes.Add(GetGeneratedType(self.Context.GeneratedAssembly, type))

	class ProcessExpressionsWithInterpreterNamespace(Steps.ProcessMethodBodiesWithDuckTyping):

		_namespace as InterpreterNamespace
		_interpreter as AbstractInterpreter

		def constructor(interpreter):
			_interpreter = interpreter

		override def Initialize(context as CompilerContext):
			super(context)
			_namespace = InterpreterNamespace(
								_interpreter,
								TypeSystemServices,
								NameResolutionService.GlobalNamespace)
			NameResolutionService.GlobalNamespace = _namespace

		override def Dispose():
			_namespace = null

	class ProcessVariableDeclarations(ProcessExpressionsWithInterpreterNamespace):

		_entryPoint as Method

		def constructor(interpreter):
			super(interpreter)

		InEntryPoint:
			get:
				return _currentMethod.Method is _entryPoint

		override def Initialize(context as CompilerContext):
			super(context)
			_entryPoint = Steps.ContextAnnotations.GetEntryPoint(Context)

		override def LeaveExpressionStatement(node as ExpressionStatement):
			# force standalone method references types to be completely
			# resolved
			GetConcreteExpressionType(node.Expression) if InEntryPoint
			super(node)

		override def HasSideEffect(node as Expression):
			return true if _interpreter.RememberLastValue and InEntryPoint
			return super(node)

		override def AssertLValue(node as Node, entity as IEntity):
			# prevent 'Expression can't be assigned to' error
			return true if InterpreterEntity.IsInterpreterEntity(entity)
			return super(node, entity)

		override def DeclareLocal(sourceNode as Node, name as string, type as IType, privateScope as bool):
			return super(sourceNode, name, type, privateScope) if privateScope or not InEntryPoint

			external = type as ExternalType
			_interpreter.Declare(name, external.ActualType) if external

			return _namespace.Declare(name, type)

		override protected def CreateDefaultLocalInitializer(sourceNode as Node, local as IEntity):
			return super(sourceNode, local) unless InterpreterEntity.IsInterpreterEntity(local)

			entity as InterpreterEntity = local
			return CodeBuilder.CreateAssignment(
				sourceNode.LexicalInfo,
				CodeBuilder.CreateReference(entity),
				CreateInitializer(sourceNode, entity.Type))

		private def CreateInitializer(sourceNode as Node, type as IType):
			return InitTempValueType(sourceNode, type) if type.IsValueType
			return CodeBuilder.CreateNullLiteral()

		private def InitTempValueType(sourceNode as Node, type as IType):
			temp = DeclareTempLocal(type)
			eval = CodeBuilder.CreateEvalInvocation(sourceNode.LexicalInfo)
			eval.Arguments.Add(CodeBuilder.CreateInitValueType(sourceNode.LexicalInfo, temp))
			eval.Arguments.Add(CodeBuilder.CreateReference(temp))
			BindExpressionType(eval, type)
			return eval

	class ProcessInterpreterReferences(Steps.AbstractTransformerCompilerStep):

		static AbstractInterpreter_GetValue = typeof(AbstractInterpreter).GetMethod("GetValue")
		static AbstractInterpreter_SetValue = typeof(AbstractInterpreter).GetMethod("SetValue")
		static AbstractInterpreter_SetLastValue = typeof(AbstractInterpreter).GetMethod("SetLastValue")

		_interpreterField as Field
		_interpreter as AbstractInterpreter
		_isEntryPoint = false

		def constructor(interpreter):
			_interpreter = interpreter

		override def Run():
			Visit(CompileUnit) if 0 == len(Errors)

		override def EnterModule(node as Module):

			module = GetModuleEntity(node).ModuleClass
			return false unless module

			_interpreterField = CodeBuilder.CreateField("ParentInterpreter", TypeSystemServices.Map(AbstractInterpreter))
			_interpreterField.Modifiers = TypeMemberModifiers.Public | TypeMemberModifiers.Static
			module.Members.Add(_interpreterField)

			return true

		override def OnConstructor(node as Constructor):
			OnMethod(node)

		override def OnMethod(node as Method):
			_isEntryPoint = node is Steps.ContextAnnotations.GetEntryPoint(Context);
			super(node)

		override def OnReferenceExpression(node as ReferenceExpression):

			if (InterpreterEntity.IsInterpreterEntity(GetOptionalEntity(node)) and
					not AstUtil.IsLhsOfAssignment(node)):
				ReplaceCurrentNode(CreateGetValue(node))

		override def LeaveBinaryExpression(node as BinaryExpression):
			if InterpreterEntity.IsInterpreterEntity(GetOptionalEntity(node.Left)):
				ReplaceCurrentNode(CreateSetValue(node))

		override def LeaveExpressionStatement(node as ExpressionStatement):

			return unless _interpreter.RememberLastValue and _isEntryPoint

			if node.Expression.ExpressionType is not TypeSystemServices.VoidType:
				node.Expression = CreateSetLastValue(	node.Expression)
			else:
				eval = CodeBuilder.CreateEvalInvocation(node.LexicalInfo)
				eval.Arguments.Add(node.Expression)
				eval.Arguments.Add(CreateSetLastValue(CodeBuilder.CreateNullLiteral()))
				node.Expression = eval

		def CreateInterpreterInvocation(method as System.Reflection.MethodInfo,
										name as string,
										value as Expression):
			mie = CreateInterpreterInvocation(method, name)
			mie.LexicalInfo = value.LexicalInfo
			mie.Arguments.Add(value)
			return mie

		def CreateInterpreterReference():
			return CodeBuilder.CreateReference(_interpreterField)

		def CreateInterpreterInvocation(method as System.Reflection.MethodInfo,
										name as string):
			return CodeBuilder.CreateMethodInvocation(
						CreateInterpreterReference(),
						TypeSystemServices.Map(method),
						CodeBuilder.CreateStringLiteral(name))

		def CreateSetLastValue(value as Expression):
			return CodeBuilder.CreateMethodInvocation(
							value.LexicalInfo,
							CreateInterpreterReference(),
							TypeSystemServices.Map(AbstractInterpreter_SetLastValue),
							value)

		def CreateGetValue(node as ReferenceExpression):
			return CastIfNeeded(
					node,
					CreateInterpreterInvocation(
						AbstractInterpreter_GetValue,
						node.Name))

		def CreateSetValue(node as BinaryExpression):
			return CastIfNeeded(
					node,
					CreateInterpreterInvocation(
						AbstractInterpreter_SetValue,
						cast(ReferenceExpression, node.Left).Name,
						node.Right))

		def CastIfNeeded(srcNode as Expression, expression as Expression):
			if NodeType.ExpressionStatement == srcNode.ParentNode.NodeType:
				return expression

			return CodeBuilder.CreateCast(srcNode.ExpressionType, expression)

	class AddRecordedImports(Steps.AbstractCompilerStep):

		_imports as ImportCollection

		def constructor(imports):
			_imports = imports

		override def Run():
			CompileUnit.Modules[0].Imports.ExtendWithClones(_imports)

	class FindCodeCompleteSuggestion(Steps.AbstractVisitorCompilerStep):

		override def Run():
			Visit(CompileUnit)

		override def LeaveMemberReferenceExpression(node as MemberReferenceExpression):
			if "__codecomplete__" == node.Name:
				suggestion as IEntity
				target = node.Target
				if target.ExpressionType is not null:
					suggestion = target.ExpressionType
				else:
					suggestion = GetOptionalEntity(target)
				if suggestion is not null and suggestion.EntityType != EntityType.Error:
					_context["suggestion"] = suggestion
					// TODO: use target to display static members only for type reference expressions
					_context["target"] = target

#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.IO
import Boo.Lang
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.TypeSystem
import Boo.Lang.Compiler.IO

class AbstractInterpreter:

	_compiler as BooCompiler

	_parser as BooCompiler

	_imports as ImportCollection

	_referenceProcessor as ProcessInterpreterReferences

	[property(RememberLastValue)]
	_rememberLastValue as bool

	_inputId as int

	_suggestionCompiler as BooCompiler

	Ducky:
		get:
			return _compiler.Parameters.Ducky

		set:
			_compiler.Parameters.Ducky = value
			if _suggestionCompiler is not null:
				_suggestionCompiler.Parameters.Ducky = value

	def constructor([required] parser as ICompilerStep):

		_compiler = BooCompiler()
		_parser = BooCompiler()
		_imports = ImportCollection()
		_referenceProcessor = ProcessInterpreterReferences(self)

		pipeline = Pipelines.CompileToMemory()
		pipeline.RemoveAt(0)

		pipeline.Replace(Steps.ProcessMethodBodiesWithDuckTyping,
						ProcessVariableDeclarations(self))
		pipeline.InsertBefore(Steps.EmitAssembly, _referenceProcessor)

		index = pipeline.Find(Steps.IntroduceModuleClasses)
		cast(Steps.IntroduceModuleClasses, pipeline[index]).ForceModuleClass = true

		// avoid InvalidCastExceptions by always
		// defining callable types only once per run
		pipeline.Replace(Steps.InitializeTypeSystemServices, InitializeTypeSystemServices())
		pipeline.Add(CacheCallableTypes())

		_compiler.Parameters.Pipeline = pipeline
		_compiler.Parameters.Ducky = true
		_parser.Parameters.Pipeline = CompilerPipeline().Add(parser)

	def constructor():
		self(Pipelines.Parse.NewParserStep())

	abstract def Declare(name as string, type as System.Type):
		pass

	abstract def Lookup(name as string) as System.Type:
		pass

	virtual def SetLastValue(value):
		pass

	abstract def SetValue(name as string, value) as object:
		pass

	abstract def GetValue(name as string) as object:
		pass

	private def GetSuggestionCompiler():
		if _suggestionCompiler is null:
			pipeline = Pipelines.ResolveExpressions(BreakOnErrors: false)
			pipeline.Insert(1, AddRecordedImports(_imports))
			pipeline.Replace(
				Steps.ProcessMethodBodiesWithDuckTyping,
				ProcessExpressionsWithInterpreterNamespace(self))
			pipeline.Replace(
				Steps.InitializeTypeSystemServices,
				_compiler.Parameters.Pipeline.Get(InitializeTypeSystemServices))
			pipeline.Add(FindCodeCompleteSuggestion())
			_suggestionCompiler = BooCompiler()
			_suggestionCompiler.Parameters.Ducky = self.Ducky
			_suggestionCompiler.Parameters.Pipeline = pipeline
			// keep the references in sync
			_suggestionCompiler.Parameters.References = self.References

		return _suggestionCompiler

	Pipeline:
		get:
			return _compiler.Parameters.Pipeline

	References:
		get:
			return _compiler.Parameters.References

	def SuggestCodeCompletion(code as string) as (IEntity):
	"""
	The code must contain a __codecomplete__ member reference as a placeholder
	to the suggestion.

	The return value is a an array of possible members or namespaces to be inserted
	at the __codecomplete__.
	"""
		return FilterSuggestions(code, ResolveEntity(code))

	def ResolveEntity(code as string) as IEntity:
		compiler = GetSuggestionCompiler()
		try:
			compiler.Parameters.Input.Add(StringInput("<code>", PreProcessImportLine(code)))
			result = compiler.Run()
			return result["suggestion"]
		ensure:
			compiler.Parameters.Input.Clear()

	def FilterSuggestions(code as string, entity as IEntity):
		ns = entity as INamespace
		return array(IEntity, 0) if ns is null
		return GetChildNamespaces(ns) if code.StartsWith("import ")
		return FilteredMembers(TypeSystemServices.GetAllMembers(ns))

	def FilteredMembers(members as (IEntity)):
		return array(
				item
				for item in members
				unless IsSpecial(item) or not IsPublic(item))

	def IsSpecial(entity as IEntity):
		for prefix in ".", "___", "add_", "remove_", "raise_", "get_", "set_":
			return true if entity.Name.StartsWith(prefix)

	def IsPublic(entity as IEntity):
		member = entity as IMember
		return member is null or member.IsPublic

	private def GetChildNamespaces(parent as INamespace):
		return array(member
					for member in parent.GetMembers()
					if member.EntityType == EntityType.Namespace)

	private def PreProcessImportLine(code as string):
		match = @/^\s*import\s+((\w|\.)+)\s*$/.Match(code)
		if match.Success:
			return match.Groups[1].Value
		return code

	def Eval([required] code as string):
		return CompilerContext(false) if 0 == len(code)
		return EvalCompilerInput(StringInput("input${++_inputId}", code))

	def EvalCompilerInput(input as ICompilerInput):
		result = Parse(input)
		return result if len(result.Errors)
		return EvalCompileUnit(result.CompileUnit)

	def EvalCompileUnit(cu as CompileUnit):
		assert 1 == len(cu.Modules)

		module = cu.Modules[0]

		# remember the state of the module as of after parsing
		hasStatements = module.Globals.Statements.Count > 0
		hasMembers = module.Members.Count > 0

		module.Imports.Reject() do (item as Import):
			for existing in _imports:
				return true if existing.Namespace == item.Namespace

		if ((not hasStatements) and
			(not hasMembers) and
			0 == len(module.Imports)):
			return CompilerContext(cu)

		savedImports = module.Imports.Clone()
		module.Imports.ExtendWithClones(_imports)

		if hasStatements:
			if IsSingleEmptyMacroStatement(module):
				# simple references will be parsed as macros
				# but we want them to be evaluated
				# as references...
				ms = module.Globals.Statements[0] as MacroStatement
				if ms is not null:
					module.Globals.Statements.ReplaceAt(0,
						ExpressionStatement(
							ReferenceExpression(ms.LexicalInfo, ms.Name)))
			_compiler.Parameters.OutputType = CompilerOutputType.ConsoleApplication
		else:
			_compiler.Parameters.OutputType = CompilerOutputType.Library

		SetLastValue(null) if _rememberLastValue
		result = _compiler.Run(cu)
		return result if len(result.Errors)

		RecordImports(savedImports)

		asm = result.GeneratedAssembly
		_compiler.Parameters.References.Add(asm) if hasMembers

		InitializeModuleInterpreter(asm, module)

		ExecuteEntryPoint(asm) if hasStatements

		return result

	def ExecuteEntryPoint(asm as System.Reflection.Assembly):
		AppDomain.CurrentDomain.AssemblyResolve += AppDomain_AssemblyResolve
		try:
			asm.EntryPoint.Invoke(null, (null,))
		ensure:
			AppDomain.CurrentDomain.AssemblyResolve -= AppDomain_AssemblyResolve

	def AppDomain_AssemblyResolve(sender, args as ResolveEventArgs) as System.Reflection.Assembly:
		for reference in _compiler.Parameters.References:
			if reference.FullName == args.Name:
				return reference
		return null

	def Parse(input as ICompilerInput):
		_parser.Parameters.Input.Clear()
		_parser.Parameters.Input.Add(input)
		return _parser.Run()

	private def InitializeModuleInterpreter(asm as System.Reflection.Assembly,
										module as Module):
		moduleType = GetGeneratedType(asm, GetEntity(GetModuleEntity(module).ModuleClass))
		moduleType.GetField("ParentInterpreter").SetValue(null, self)

	private static def GetModuleEntity(module as Module) as ModuleEntity:
		return GetEntity(module)

	private static def GetGeneratedType(asm as System.Reflection.Assembly, type as IType):
		return asm.GetType(type.FullName)

	static def GetEntity(node as Node):
		return TypeSystemServices.GetEntity(node)

	static def GetOptionalEntity(node as Node):
		return TypeSystemServices.GetOptionalEntity(node)

	private def RecordImports(imports as ImportCollection):
		for imp in imports:
			imp.AssemblyReference = null
			_imports.Add(imp)

	private def IsSingleEmptyMacroStatement(m as Module):
		if 1 == len(m.Globals.Statements):
			macro = m.Globals.Statements[0] as MacroStatement
			if macro is not null:
				return 0 == len(macro.Arguments) and 0 == len(macro.Block.Statements)

	class InterpreterEntity(ITypedEntity):

		[getter(Name)]
		[getter(FullName)]
		_name as string

		[getter(Type)]
		_type as IType

		def constructor(name, type):
			_name = name
			_type = type

		EntityType:
			get:
				return TypeSystem.EntityType.Custom

		static def IsInterpreterEntity(entity as IEntity):
			return entity is not null and TypeSystem.EntityType.Custom == entity.EntityType

	class InterpreterNamespace(INamespace):

		[getter(ParentNamespace)]
		_parent as INamespace

		_tss as TypeSystemServices

		_interpreter as AbstractInterpreter

		_declarations = {}

		def constructor(interpreter, tss, parent):
			_interpreter = interpreter
			_tss = tss
			_parent = parent

		def Declare(name as string, type as IType):
			entity = InterpreterEntity(name, type)
			_declarations.Add(name, entity)
			return entity

		def Resolve(targetList as List, name as string, flags as EntityType) as bool:
			return false unless flags == EntityType.Any

			entity as IEntity = _declarations[name]
			if entity is null:
				type = _interpreter.Lookup(name)
				if type is not null:
					entity = Declare(name, _tss.Map(type))

			if entity is not null:
				targetList.Add(entity)
				return true

			return false

		def GetMembers():
			return array(IEntity, 0)

	class InterpreterTypeSystemServices(TypeSystemServices):

		[getter(CachedCallableTypes)]
		_cachedCallableTypes as List

		[getter(GeneratedCallableTypes)]
		_generatedCallableTypes = []

		def constructor(context, cache):
			super(context)
			_cachedCallableTypes = cache

		override def GetConcreteCallableType(sourceNode as Node,
											signature as CallableSignature):

			cached = GetCachedCallableType(signature)
			if cached is not null: return cached

			return CacheCallableType(super(sourceNode, signature))

		override def GetConcreteCallableType(sourceNode as Node,
											anonymousType as AnonymousCallableType):

			cached = GetCachedCallableType(anonymousType.GetSignature())
			if cached is not null: return cached

			return CacheCallableType(super(sourceNode, anonymousType))

		private def CacheCallableType(type as IType):
			_generatedCallableTypes.Add(type)
			return type

		private def GetCachedCallableType(signature as CallableSignature):
			for type as System.Type in _cachedCallableTypes:
				cached = Map(type) as ExternalCallableType
				if signature == cached.GetSignature():
					return cached

	class InitializeTypeSystemServices(Steps.AbstractCompilerStep):

		_cachedCallableTypes = []

		override def Run():
			Context.TypeSystemServices = InterpreterTypeSystemServices(Context, _cachedCallableTypes)

	class CacheCallableTypes(Steps.AbstractCompilerStep):

		override def Run():
			return if len(Errors)

			services as InterpreterTypeSystemServices = self.TypeSystemServices
			types = services.GeneratedCallableTypes
			return unless len(types)

			debug "caching", len(types), "callable types"
			for type as InternalCallableType in types:
				debug type
				services.CachedCallableTypes.Add(GetGeneratedType(self.Context.GeneratedAssembly, type))

	class ProcessExpressionsWithInterpreterNamespace(Steps.ProcessMethodBodiesWithDuckTyping):

		_namespace as InterpreterNamespace
		_interpreter as AbstractInterpreter

		def constructor(interpreter):
			_interpreter = interpreter

		override def Initialize(context as CompilerContext):
			super(context)
			_namespace = InterpreterNamespace(
								_interpreter,
								TypeSystemServices,
								NameResolutionService.GlobalNamespace)
			NameResolutionService.GlobalNamespace = _namespace

		override def Dispose():
			_namespace = null

	class ProcessVariableDeclarations(ProcessExpressionsWithInterpreterNamespace):

		_entryPoint as Method

		def constructor(interpreter):
			super(interpreter)

		InEntryPoint:
			get:
				return _currentMethod.Method is _entryPoint

		override def Initialize(context as CompilerContext):
			super(context)
			_entryPoint = Steps.ContextAnnotations.GetEntryPoint(Context)

		override def LeaveExpressionStatement(node as ExpressionStatement):
			# force standalone method references types to be completely
			# resolved
			GetConcreteExpressionType(node.Expression) if InEntryPoint
			super(node)

		override def HasSideEffect(node as Expression):
			return true if _interpreter.RememberLastValue and InEntryPoint
			return super(node)

		override def AssertLValue(node as Node, entity as IEntity):
			# prevent 'Expression can't be assigned to' error
			return true if InterpreterEntity.IsInterpreterEntity(entity)
			return super(node, entity)

		override def DeclareLocal(sourceNode as Node, name as string, type as IType, privateScope as bool):
			return super(sourceNode, name, type, privateScope) if privateScope or not InEntryPoint

			external = type as ExternalType
			_interpreter.Declare(name, external.ActualType) if external

			return _namespace.Declare(name, type)

		override protected def CreateDefaultLocalInitializer(sourceNode as Node, local as IEntity):
			return super(sourceNode, local) unless InterpreterEntity.IsInterpreterEntity(local)

			entity as InterpreterEntity = local
			return CodeBuilder.CreateAssignment(
				sourceNode.LexicalInfo,
				CodeBuilder.CreateReference(entity),
				CreateInitializer(sourceNode, entity.Type))

		private def CreateInitializer(sourceNode as Node, type as IType):
			return InitTempValueType(sourceNode, type) if type.IsValueType
			return CodeBuilder.CreateNullLiteral()

		private def InitTempValueType(sourceNode as Node, type as IType):
			temp = DeclareTempLocal(type)
			eval = CodeBuilder.CreateEvalInvocation(sourceNode.LexicalInfo)
			eval.Arguments.Add(CodeBuilder.CreateInitValueType(sourceNode.LexicalInfo, temp))
			eval.Arguments.Add(CodeBuilder.CreateReference(temp))
			BindExpressionType(eval, type)
			return eval

	class ProcessInterpreterReferences(Steps.AbstractTransformerCompilerStep):

		static AbstractInterpreter_GetValue = typeof(AbstractInterpreter).GetMethod("GetValue")
		static AbstractInterpreter_SetValue = typeof(AbstractInterpreter).GetMethod("SetValue")
		static AbstractInterpreter_SetLastValue = typeof(AbstractInterpreter).GetMethod("SetLastValue")

		_interpreterField as Field
		_interpreter as AbstractInterpreter
		_isEntryPoint = false

		def constructor(interpreter):
			_interpreter = interpreter

		override def Run():
			Visit(CompileUnit) if 0 == len(Errors)

		override def EnterModule(node as Module):

			module = GetModuleEntity(node).ModuleClass
			return false unless module

			_interpreterField = CodeBuilder.CreateField("ParentInterpreter", TypeSystemServices.Map(AbstractInterpreter))
			_interpreterField.Modifiers = TypeMemberModifiers.Public | TypeMemberModifiers.Static
			module.Members.Add(_interpreterField)

			return true

		override def OnConstructor(node as Constructor):
			OnMethod(node)

		override def OnMethod(node as Method):
			_isEntryPoint = node is Steps.ContextAnnotations.GetEntryPoint(Context);
			super(node)

		override def OnReferenceExpression(node as ReferenceExpression):

			if (InterpreterEntity.IsInterpreterEntity(GetOptionalEntity(node)) and
					not AstUtil.IsLhsOfAssignment(node)):
				ReplaceCurrentNode(CreateGetValue(node))

		override def LeaveBinaryExpression(node as BinaryExpression):
			if InterpreterEntity.IsInterpreterEntity(GetOptionalEntity(node.Left)):
				ReplaceCurrentNode(CreateSetValue(node))

		override def LeaveExpressionStatement(node as ExpressionStatement):

			return unless _interpreter.RememberLastValue and _isEntryPoint

			if node.Expression.ExpressionType is not TypeSystemServices.VoidType:
				node.Expression = CreateSetLastValue(	node.Expression)
			else:
				eval = CodeBuilder.CreateEvalInvocation(node.LexicalInfo)
				eval.Arguments.Add(node.Expression)
				eval.Arguments.Add(CreateSetLastValue(CodeBuilder.CreateNullLiteral()))
				node.Expression = eval

		def CreateInterpreterInvocation(method as System.Reflection.MethodInfo,
										name as string,
										value as Expression):
			mie = CreateInterpreterInvocation(method, name)
			mie.Arguments.Add(value)
			return mie

		def CreateInterpreterReference():
			return CodeBuilder.CreateReference(_interpreterField)

		def CreateInterpreterInvocation(method as System.Reflection.MethodInfo,
										name as string):
			return CodeBuilder.CreateMethodInvocation(
						CreateInterpreterReference(),
						TypeSystemServices.Map(method),
						CodeBuilder.CreateStringLiteral(name))

		def CreateSetLastValue(value as Expression):
			return CodeBuilder.CreateMethodInvocation(
							CreateInterpreterReference(),
							TypeSystemServices.Map(AbstractInterpreter_SetLastValue),
							value)

		def CreateGetValue(node as ReferenceExpression):
			return CastIfNeeded(
					node,
					CreateInterpreterInvocation(
						AbstractInterpreter_GetValue,
						node.Name))

		def CreateSetValue(node as BinaryExpression):
			return CastIfNeeded(
					node,
					CreateInterpreterInvocation(
						AbstractInterpreter_SetValue,
						cast(ReferenceExpression, node.Left).Name,
						node.Right))

		def CastIfNeeded(srcNode as Expression, expression as Expression):
			if NodeType.ExpressionStatement == srcNode.ParentNode.NodeType:
				return expression

			return CodeBuilder.CreateCast(srcNode.ExpressionType, expression)

	class AddRecordedImports(Steps.AbstractCompilerStep):

		_imports as ImportCollection

		def constructor(imports):
			_imports = imports

		override def Run():
			CompileUnit.Modules[0].Imports.ExtendWithClones(_imports)

	class FindCodeCompleteSuggestion(Steps.AbstractVisitorCompilerStep):

		override def Run():
			Visit(CompileUnit)

		override def LeaveMemberReferenceExpression(node as MemberReferenceExpression):
			if "__codecomplete__" == node.Name:
				suggestion as IEntity
				target = node.Target
				if target.ExpressionType is not null:
					suggestion = target.ExpressionType
				else:
					suggestion = GetOptionalEntity(target)
				if suggestion is not null and suggestion.EntityType != EntityType.Error:
					_context["suggestion"] = suggestion
					// TODO: use target to display static members only for type reference expressions
					_context["target"] = target


Fix for WSABooParserTestFixture.ast_literal_enum and other 9 tests.

namespace Boo.OMeta.Parser

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

ometa WSABooParser < BooParser:
	scanner = (empty_lines ^ makeToken("eol")) | ((--whitespace, tokens >> t) ^ t)

	keywords = "end" | super

	begin_block = COLON, eol
	end_block = keyword["end"], eol
	empty_block = (begin_block, end_block) ^ Block()

	INDENT = eol | ""
	DEDENT = eol | ""