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


Several fixes to the simplification of the AST

namespace BooJs.Compiler.Steps

import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.Steps

import Boo.Lang.Environments
import Boo.Lang.Compiler.TypeSystem.Services.RuntimeMethodCache as BooRuntimeMethodCache
import BooJs.Compiler.TypeSystem(RuntimeMethodCache)
import BooJs.Lang.Extensions

class PrepareAst(AbstractTransformerCompilerStep):
"""
    Prepare the AST for its conversion to Javascript

        - Remove unneeded nodes
        - Convert builtins and runtime references

"""
    [getter(MethodCache)]
    private _methodCache as RuntimeMethodCache

    [getter(BooMethodCache)]
    private _booMethodCache as BooRuntimeMethodCache

    protected def GetAttribute[of T(System.Attribute)](node as Node) as T:
        entity = node.Entity as TypeSystem.IExternalEntity
        return (GetAttribute[of T](entity) if entity else null as T)

    protected def GetAttribute[of T(System.Attribute)](entity as TypeSystem.IExternalEntity) as T:
        return System.Attribute.GetCustomAttribute(entity.MemberInfo, typeof(T))

    protected def IsBuiltin(node as Node) as bool:
        entity = node.Entity as TypeSystem.Reflection.ExternalType
        if not entity and centity = node.Entity as TypeSystem.ExternalMethod:
            entity = centity.DeclaringType

        return false if not entity
        return true if entity.Type is TypeSystemServices.BuiltinsType
        return entity.DeclaringType == TypeSystemServices.BuiltinsType

    protected def IsGlobal(node as Node) as bool:
        if entity = node.Entity as TypeSystem.Reflection.ExternalType:
            return entity.ActualType.Namespace == 'BooJs.Lang.Globals'

        return false

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

        _methodCache = EnvironmentProvision[of RuntimeMethodCache]()
        _booMethodCache = EnvironmentProvision[of BooRuntimeMethodCache]()

    def EnterModule(node as Module):
        if node.Name == 'CompilerGenerated':
            RemoveCurrentNode()
            return false

        return true

    def EnterClassDefinition(node as ClassDefinition):
        if node.Name == 'CompilerGeneratedExtensions':
            RemoveCurrentNode()
            return false
        return true

    def OnExpressionStatement(node as ExpressionStatement):
        # Ignore the assignment of locals produced by closures instrumentation
        be = node.Expression as BinaryExpression
        if be and be.Operator == BinaryOperatorType.Assign and be.Left.ToString() == '$locals':
            RemoveCurrentNode
            return

        super(node)

    protected def ProcessReference(node as ReferenceExpression) as Node:
        if TransformAttribute.HasAttribute(node):
            result = TransformAttribute.Resolve(node, null)
            Visit result
            return result

        # Check for builtins references
        if IsBuiltin(node):
            name = node.Name.Split(char('.'))[-1]
            refe = [| Boo.$(ReferenceExpression(Name: name)) |].withLexicalInfoFrom(node)
            refe.Entity = node.Entity
            return refe

        # Entities in the Global namespace are never prefixed
        if IsGlobal(node):
            node.Name = node.Name.Split(char('.'))[-1]
            return node

        # Members of the module are placed in the top scope
        ientity = node.Entity as TypeSystem.IMember
        if ientity and ientity.DeclaringType and ientity.DeclaringType.IsClass and ientity.DeclaringType.IsFinal:
            name = node.Name.Split(char('.'))[-1]
            return [| $(ReferenceExpression(Name: name)) |].withLexicalInfoFrom(node)

        return node

    def OnReferenceExpression(node as ReferenceExpression):
        ReplaceCurrentNode ProcessReference(node)

    def OnMemberReferenceExpression(node as MemberReferenceExpression):
        if TransformAttribute.HasAttribute(node):
            result = TransformAttribute.Resolve(node, null)
            Visit result
            ReplaceCurrentNode result
            return

        # Convert from `$locals.$variable` to `variable`
        if node.Target.NodeType == NodeType.ReferenceExpression:
            if (node.Target as ReferenceExpression).Name == '$locals':
                refexp = ReferenceExpression(node.Name[1:], LexicalInfo: node.LexicalInfo)
                ReplaceCurrentNode refexp
                return

        # Members of the module are placed in the top scope
        ientity = node.Target.Entity as TypeSystem.Internal.AbstractInternalType
        if node.Target.IsSynthetic and ientity and ientity.IsClass and ientity.IsFinal:
            refexp = ReferenceExpression(node.Name, LexicalInfo: node.LexicalInfo)
            ReplaceCurrentNode refexp
            return

        # Check for builtins references
        if IsBuiltin(node.Target):
            node.Target = [| Boo |].withLexicalInfoFrom(node.Target)

        super(node)

    def OnMethodInvocationExpression(node as MethodInvocationExpression):
        # Convert Transform attribute metadata to an AST annotation
        if TransformAttribute.HasAttribute(node.Target):
            result = TransformAttribute.Resolve(node.Target, node.Arguments)
            result = VisitNode(result)
            ReplaceCurrentNode result
            return

        # Eval calls take the form: @( stmt1, stmt2, ...., return_stmt )
        # We convert them to a self invoking anonymous function that returns the last argument.
        #
        # TODO: Isn't this equivalent to Js ( expr1, expr2, expr3 ) ???
        #
        # Note: variables are declared in the enclosing scope, not wrapped in the function.
        if false and '@' == node.Target.ToString():
            bexpr = BlockExpression(LexicalInfo: node.Target.LexicalInfo)
            st as Statement
            for i, arg as Expression in enumerate(node.Arguments):

                if i == len(node.Arguments)-1:
                    st = ReturnStatement(LexicalInfo: arg.LexicalInfo, Expression: arg)
                else:
                    st = ExpressionStatement(LexicalInfo: arg.LexicalInfo, Expression: arg)

                bexpr.Body.Add(st)

            mie = MethodInvocationExpression(LexicalInfo: node.LexicalInfo)
            mie.Target = bexpr
            ReplaceCurrentNode mie
            Visit mie
            return

        super(node)

    def OnMethod(node as Method):
    """ Process locals and detect the Main method to move its statements into the Module globals
    """
        # Skip compiler generated methods
        if node.IsSynthetic and node.IsInternal:
            RemoveCurrentNode
            return

        # Convert locals back to simple declaration statements
        found = ['$locals']
        for local in node.Locals:
            # Avoid duplicates
            if local.Name is null or local.Name in found:
                continue

            found.Push(local.Name)

            decl = Declaration(LexicalInfo: local.LexicalInfo)
            decl.Name = local.Name

            # Detect local type
            initializer as Expression = NullLiteralExpression()
            entity = local.Entity as TypeSystem.Internal.InternalLocal
            if entity:
                if entity.OriginalDeclaration:
                    # Skip declarations for those flagged as global
                    continue if entity.OriginalDeclaration.ContainsAnnotation('global')
                    decl.Type = entity.OriginalDeclaration.Type
                else:
                    # TODO: Can we generate proper type annotations with this?
                    decl.Type = SimpleTypeReference(Entity: entity)

                # Initialize with a default value
                if TypeSystemServices.IsNumber(entity.Type):
                    initializer = IntegerLiteralExpression(0)
                elif entity.Type == TypeSystemServices.TimeSpanType:
                    initializer = IntegerLiteralExpression(0)
                elif entity.Type == TypeSystemServices.BoolType:
                    initializer = BoolLiteralExpression(false)
                elif entity.Type == TypeSystemServices.StringType:
                    initializer = StringLiteralExpression('')

            st = DeclarationStatement(LexicalInfo: local.LexicalInfo, Declaration: decl, Initializer: initializer)
            node.Body.Insert(0, st)

        # Detect the Main method and move its statements to the Module globals
        if ContextAnnotations.GetEntryPoint(Context) is node:
            Visit node.Body
            node.EnclosingModule.Globals = node.Body
            RemoveCurrentNode
            return

        super(node)

    def OnCastExpression(node as CastExpression):
        mie = [| Boo.$(ReferenceExpression('cast'))() |]
        mie.Arguments.Add(node.Target)
        mie.Arguments.Add(node.Type as Expression)
        ReplaceCurrentNode mie

    def OnTryCastExpression(node as TryCastExpression):
        mie = [| Boo.trycast() |]
        mie.Arguments.Add(node.Target)
        mie.Arguments.Add(node.Type as Expression)
        ReplaceCurrentNode mie

    def OnCharLiteralExpression(node as CharLiteralExpression):
    """ There is no char type in BooJs. char('c') and char(int) are converted to a string of length 1
    """
        ReplaceCurrentNode StringLiteralExpression(LexicalInfo: node.LexicalInfo, Value: node.Value.ToString())

    def OnRELiteralExpression(node as RELiteralExpression):
    """ Almost a direct translation to Javascript regexp literals. The only modification
        is that we ignore whitespace to allow the regexps to be somehow more readable while
        Boo's handling is to escape it as part of the match.

            / foo | bar /i  ->  /foo|bar/i
            @/ foo | bar /  ->  /foo|bar/i

        TODO: The ones prefixed with @ should be preprocessed to support new lines
    """
        re = node.Value
        start = re.IndexOf('/')
        stop = re.LastIndexOf('/')

        modifier = re[stop:]
        re = re[start:stop]

        # Ignore white space
        re = /\s+/.Replace(re, '')

        # Javascript does not support the single-line modifier (dot matches new lines).
        # We emulate it by converting dots to the [\s\S] expression in the regex.
        # NOTE: The algorithm will break when a dot is preceeded by an escaped back slash (\\.)
        if modifier.Contains('s'):
            re = /(?<!\\)\./.Replace(re, '[\\s\\S]')
            modifier = modifier.Replace('s', '')

        node.Value = re + modifier

    def OnArrayLiteralExpression(node as ArrayLiteralExpression):
    """ In BooJs arrays are muttable (Boo's Lists)
    """
        list = ListLiteralExpression(LexicalInfo: node.LexicalInfo, Items: node.Items)
        Visit list
        ReplaceCurrentNode list

    def OnUnlessStatement(node as UnlessStatement):
    """ Negate the condition to convert it into a simple if statement
    """
        ifst = IfStatement(LexicalInfo: node.LexicalInfo)
        ifst.Condition = UnaryExpression(
            Operator: UnaryOperatorType.LogicalNot,
            Operand: node.Condition,
            LexicalInfo: node.LexicalInfo
        )
        ifst.TrueBlock = node.Block

        ReplaceCurrentNode ifst

    def OnBinaryExpression(node as BinaryExpression):
        if node.Operator == BinaryOperatorType.TypeTest:
            mie = MethodInvocationExpression(node.LexicalInfo)
            mie.Target = ReferenceExpression(node.LexicalInfo, 'Boo.isa')
            mie.Arguments.Add(node.Left)
            mie.Arguments.Add(node.Right)
            Visit mie.Arguments
            ReplaceCurrentNode mie
            return

        super(node)

    def OnTypeofExpression(node as TypeofExpression):
        if st = node.Type as SimpleTypeReference:

            # Primitives are just enclosed as strings
            if TypeSystemServices.IsPrimitive(st.Name):
                ReplaceCurrentNode StringLiteralExpression(node.LexicalInfo, st.Name)
                return

            # If we have an entity use it
            if exent = st.Entity as TypeSystem.Reflection.ExternalType:
                refe = CodeBuilder.CreateReference(node.LexicalInfo, exent.ActualType)
                ReplaceCurrentNode ProcessReference(refe)
                return

            # Just rely on the type name
            refe = ReferenceExpression(node.LexicalInfo, st.Name)
            ReplaceCurrentNode ProcessReference(refe)

        else:
            raise 'Unsupported TypeReference: ' + node.Type + ' (' + node.Type.NodeType + ')'

namespace BooJs.Compiler.Steps

import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.Steps

import Boo.Lang.Environments
import Boo.Lang.Compiler.TypeSystem.Services.RuntimeMethodCache as BooRuntimeMethodCache
import BooJs.Compiler.TypeSystem(RuntimeMethodCache)
import BooJs.Lang.Extensions

class PrepareAst(AbstractTransformerCompilerStep):
"""
    Prepare the AST for its conversion to Javascript

        - Remove unneeded nodes
        - Convert builtins and runtime references

"""
    [getter(MethodCache)]
    private _methodCache as RuntimeMethodCache

    [getter(BooMethodCache)]
    private _booMethodCache as BooRuntimeMethodCache

    protected def GetAttribute[of T(System.Attribute)](node as Node) as T:
        entity = node.Entity as TypeSystem.IExternalEntity
        return (GetAttribute[of T](entity) if entity else null as T)

    protected def GetAttribute[of T(System.Attribute)](entity as TypeSystem.IExternalEntity) as T:
        return System.Attribute.GetCustomAttribute(entity.MemberInfo, typeof(T))

    protected def IsBuiltin(node as Node) as bool:
        entity = node.Entity as TypeSystem.Reflection.ExternalType
        return false if not entity
        return true if entity.Type is TypeSystemServices.BuiltinsType
        return entity.DeclaringType == TypeSystemServices.BuiltinsType

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

        _methodCache = EnvironmentProvision[of RuntimeMethodCache]()
        _booMethodCache = EnvironmentProvision[of BooRuntimeMethodCache]()

    def EnterModule(node as Module):
        if node.Name == 'CompilerGenerated':
            RemoveCurrentNode()
            return false

        return true

    def EnterClassDefinition(node as ClassDefinition):
        if node.Name == 'CompilerGeneratedExtensions':
            RemoveCurrentNode()
            return false
        return true

    def OnExpressionStatement(node as ExpressionStatement):
        # Ignore the assignment of locals produced by closures instrumentation
        be = node.Expression as BinaryExpression
        if be and be.Operator == BinaryOperatorType.Assign and be.Left.ToString() == '$locals':
            RemoveCurrentNode
            return

        Visit node.Expression

    def OnReferenceExpression(node as ReferenceExpression):
        if TransformAttribute.HasAttribute(node):
            ReplaceCurrentNode TransformAttribute.Resolve(node, null)
            return

        # Check for builtins references
        if IsBuiltin(node):
            name = node.Name.Split(char('.'))[-1]
            ReplaceCurrentNode [| Boo.$(ReferenceExpression(Name: name)) |].withLexicalInfoFrom(node)
            return

        # Members of the module are placed in the top scope
        ientity = node.Entity as TypeSystem.IMember
        if ientity and ientity.DeclaringType and ientity.DeclaringType.IsClass and ientity.DeclaringType.IsFinal:
            name = node.Name.Split(char('.'))[-1]
            ReplaceCurrentNode [| $(ReferenceExpression(Name: name)) |].withLexicalInfoFrom(node)
            return

    def OnMemberReferenceExpression(node as MemberReferenceExpression):
        if TransformAttribute.HasAttribute(node):
            ReplaceCurrentNode TransformAttribute.Resolve(node, null)
            return

        # Convert from `$locals.$variable` to `variable`
        if node.Target.NodeType == NodeType.ReferenceExpression:
            if (node.Target as ReferenceExpression).Name == '$locals':
                refexp = ReferenceExpression(node.Name[1:], LexicalInfo: node.LexicalInfo)
                Visit refexp
                ReplaceCurrentNode refexp
                return

        # Members of the module are placed in the top scope
        ientity = node.Target.Entity as TypeSystem.Internal.AbstractInternalType
        if node.Target.IsSynthetic and ientity and ientity.IsClass and ientity.IsFinal:
            refexp = ReferenceExpression(node.Name, LexicalInfo: node.LexicalInfo)
            Visit refexp
            ReplaceCurrentNode refexp
            return

        # Check for builtins references
        if IsBuiltin(node.Target):
            node.Target = [| Boo |].withLexicalInfoFrom(node.Target)

        super(node)

    def OnMethodInvocationExpression(node as MethodInvocationExpression):

        # TODO: Create custom step (or use the AST attribute) to apply the transformation.
        #       They should be given as a quasi-quotation, replacing the placeholders by
        #       traversing the node.
        #       The result should be a Mozilla AST serialized as JSON in the assembly
        #       attribute. This will allow to easily transform even when referencing
        #       types already compiled.

        # Convert Transform attribute metadata to an AST annotation
        #if not node.ContainsAnnotation('Transform') and TransformAttribute.HasAttribute(node.Target):
        if TransformAttribute.HasAttribute(node.Target):
            #node.Annotate('Transform', ast)
            ReplaceCurrentNode TransformAttribute.Resolve(node.Target, node.Arguments)
            return

        # Eval calls take the form: @( stmt1, stmt2, ...., return_stmt )
        # We convert them to a self invoking anonymous function that returns the last argument.
        #
        # Note: variables are declared in the enclosing scope, not wrapped in the function.
        if '@' == node.Target.ToString():
            bexpr = BlockExpression(LexicalInfo: node.Target.LexicalInfo)
            st as Statement
            for i, arg as Expression in enumerate(node.Arguments):

                if i == len(node.Arguments)-1:
                    st = ReturnStatement(LexicalInfo: arg.LexicalInfo, Expression: arg)
                else:
                    st = ExpressionStatement(LexicalInfo: arg.LexicalInfo, Expression: arg)

                bexpr.Body.Add(st)

            mie = MethodInvocationExpression(LexicalInfo: node.LexicalInfo)
            mie.Target = bexpr
            Visit mie
            ReplaceCurrentNode mie
            return

        super(node)

    def OnMethod(node as Method):
    """ Detect the Main method and move its statements to the Module globals
    """
        # Skip compiler generated methods
        if node.IsSynthetic and node.IsInternal:
            RemoveCurrentNode
            return

        # Filter locals to remove duplicates
        found = ['$locals']
        remove = List[of Local]()
        for local in node.Locals:
            if local.Name in found:
                remove.Push(local)
            else:
                found.Push(local.Name)

        for local in remove:
            node.Locals.Remove(local)

        # Detect the Main method and move its statements to the Module globals
        if ContextAnnotations.GetEntryPoint(Context) is node:

            Visit node.Body
            node.EnclosingModule.Globals = node.Body

            for local in node.Locals:
                decl = Declaration(LexicalInfo: local.LexicalInfo)
                decl.Name = local.Name

                # Detect local type
                initializer as Expression = NullLiteralExpression()
                entity = local.Entity as TypeSystem.Internal.InternalLocal
                if entity:
                    if entity.OriginalDeclaration:
                        # Skip declarations for those flagged as global
                        continue if entity.OriginalDeclaration.ContainsAnnotation('global')
                        decl.Type = entity.OriginalDeclaration.Type
                    else:
                        # TODO: Can we generate proper type annotations with this?
                        decl.Type = SimpleTypeReference(Entity: entity)

                    # Initialize with a default value
                    if TypeSystemServices.IsNumber(entity.Type):
                        initializer = IntegerLiteralExpression(0)
                    elif entity.Type == TypeSystemServices.TimeSpanType:
                        initializer = IntegerLiteralExpression(0)
                    elif entity.Type == TypeSystemServices.BoolType:
                        initializer = BoolLiteralExpression(false)
                    elif entity.Type == TypeSystemServices.StringType:
                        initializer = StringLiteralExpression('')

                st = DeclarationStatement(LexicalInfo: local.LexicalInfo, Declaration: decl, Initializer: initializer)
                node.EnclosingModule.Globals.Insert(0, st)

                Visit node.EnclosingModule.Globals

            RemoveCurrentNode
        else:
            super(node)

    def OnCastExpression(node as CastExpression):
        mie = [| Boo.$(ReferenceExpression('cast'))() |]
        mie.Arguments.Add(node.Target)
        mie.Arguments.Add(node.Type as Expression)
        Visit mie
        ReplaceCurrentNode mie

    def OnTryCastExpression(node as TryCastExpression):
        mie = [| Boo.trycast() |]
        mie.Arguments.Add(node.Target)
        mie.Arguments.Add(node.Type as Expression)
        Visit mie
        ReplaceCurrentNode mie

    def OnCharLiteralExpression(node as CharLiteralExpression):
    """ There is no char type in BooJs. char('c') and char(int) are converted to a string of length 1
    """
        ReplaceCurrentNode StringLiteralExpression(LexicalInfo: node.LexicalInfo, Value: node.Value.ToString())

    def OnRELiteralExpression(node as RELiteralExpression):
    """ Almost a direct translation to Javascript regexp literals. The only modification
        is that we ignore whitespace to allow the regexps to be somehow more readable while
        Boo's handling is to escape it as part of the match.

            / foo | bar /i  ->  /foo|bar/i
            @/ foo | bar /  ->  /foo|bar/i

        TODO: The ones prefixed with @ should be preprocessed to support new lines
    """
        re = node.Value
        start = re.IndexOf('/')
        stop = re.LastIndexOf('/')

        modifier = re[stop:]
        re = re[start:stop]

        # Ignore white space
        re = /\s+/.Replace(re, '')

        # Javascript does not support the single-line modifier (dot matches new lines).
        # We emulate it by converting dots to the [\s\S] expression in the regex.
        # NOTE: The algorithm will break when a dot is preceeded by an escaped back slash (\\.)
        if modifier.Contains('s'):
            re = /(?<!\\)\./.Replace(re, '[\\s\\S]')
            modifier = modifier.Replace('s', '')

        node.Value = re + modifier

    def OnArrayLiteralExpression(node as ArrayLiteralExpression):
    """ In BooJs arrays are muttable (Boo's Lists)
    """
        list = ListLiteralExpression(LexicalInfo: node.LexicalInfo, Items: node.Items)
        Visit list
        ReplaceCurrentNode list

    def OnUnlessStatement(node as UnlessStatement):
    """ Negate the condition to convert it into a simple if statement
    """
        ifst = IfStatement(LexicalInfo: node.LexicalInfo)
        ifst.Condition = UnaryExpression(
            Operator: UnaryOperatorType.LogicalNot,
            Operand: node.Condition,
            LexicalInfo: node.LexicalInfo
        )
        ifst.TrueBlock = node.Block

        Visit ifst
        ReplaceCurrentNode ifst


prefer property macro over property attribute

namespace UnityScript

import Boo.Lang.Environments
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Services
import Boo.Lang.Compiler.TypeSystem
import Boo.Lang.Compiler.TypeSystem.Services

import UnityScript.TypeSystem

class UnityScriptCompilerParameters(CompilerParameters):

	property ScriptBaseType as System.Type

	property ScriptMainMethod as string

	property Imports = List of string()

	property Expando = false

	property GlobalVariablesBecomeFields = true

	property DisableEval as string

	def constructor():
		self.OutputType = CompilerOutputType.Library
		self.References.Add(typeof(UnityScript.Lang.Array).Assembly)
		self.References.Add(GetType().Assembly)
		self.Environment = DeferredEnvironment() {
			EntityFormatter: { UnityScriptEntityFormatter() },
			TypeSystemServices: { UnityScriptTypeSystem() },
			CallableResolutionService: { UnityCallableResolutionService() },
			DowncastPermissions: { UnityDowncastPermissions() },
			LanguageAmbiance: { UnityScriptAmbiance() }
		}

	override Ducky:
		get: return not Strict
		set: raise "Ducky is always equals not Strict. Set Strict instead."

namespace UnityScript

import Boo.Lang.Environments
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Services
import Boo.Lang.Compiler.TypeSystem
import Boo.Lang.Compiler.TypeSystem.Services
import UnityScript.TypeSystem

class UnityScriptCompilerParameters(CompilerParameters):

	[property(ScriptBaseType)]
	_baseType as System.Type

	[property(ScriptMainMethod)]
	_mainMethod as string

	[property(Imports)]
	_imports = []

	[property(Expando)]
	_expando = false

	[property(GlobalVariablesBecomeFields)]
	_globalVariablesBecomeFields = true

	[property(DisableEval)]
	_disableEval as string

	def constructor():
		self.OutputType = CompilerOutputType.Library
		self.References.Add(typeof(UnityScript.Lang.Array).Assembly)
		self.References.Add(GetType().Assembly)
		self.Environment = DeferredEnvironment() {
			EntityFormatter: { UnityScriptEntityFormatter() },
			TypeSystemServices: { UnityScriptTypeSystem() },
			CallableResolutionService: { UnityCallableResolutionService() },
			DowncastPermissions: { UnityDowncastPermissions() },
			LanguageAmbiance: { UnityScriptAmbiance() }
		}

	override Ducky:
		get: return not Strict
		set: raise "Ducky is always equals not Strict. Set Strict instead."


BodyRegion for fields

BodyRegion for fields

namespace Boo.MonoDevelop.ProjectModel

import Boo.Lang.Compiler.Ast
import MonoDevelop.Projects.Dom as MD
import Boo.Lang.PatternMatching

class DomConversionVisitor(DepthFirstVisitor):

	_result as MD.CompilationUnit
	_currentType as MD.DomType
	_namespace as string

	def constructor(result as MD.CompilationUnit):
		_result = result

	override def OnModule(node as Module):
		_namespace = null
		Visit(node.Namespace)
		Visit(node.Members)

	override def OnNamespaceDeclaration(node as NamespaceDeclaration):
		_namespace = node.Name
		region = BodyRegionOf(node.ParentNode)
		domUsing = MD.DomUsing(IsFromNamespace: true, Region: region)
		domUsing.Add(_namespace)
		_result.Add(domUsing)

	override def OnImport(node as Import):
		region = BodyRegionOf(node)
		domUsing = MD.DomUsing(Region: region)
		domUsing.Add(node.Namespace)
		_result.Add(domUsing)

	override def OnClassDefinition(node as ClassDefinition):
		OnTypeDefinition(node, MD.ClassType.Class)

	override def OnInterfaceDefinition(node as InterfaceDefinition):
		OnTypeDefinition(node, MD.ClassType.Interface)

	override def OnStructDefinition(node as StructDefinition):
		OnTypeDefinition(node, MD.ClassType.Struct)

	override def OnEnumDefinition(node as EnumDefinition):
		OnTypeDefinition(node, MD.ClassType.Enum)

	def OnTypeDefinition(node as TypeDefinition, classType as MD.ClassType):
		converted = MD.DomType(
						Name: node.Name,
						ClassType: classType,
						Location: LocationOf(node),
						BodyRegion: BodyRegionOf(node),
						DeclaringType: _currentType,
						Modifiers: ModifiersFrom(node))

		WithCurrentType converted:
			Visit(node.Members)

		AddType(converted)

	override def OnCallableDefinition(node as CallableDefinition):
		parameters = System.Collections.Generic.List[of MD.IParameter](ParameterFrom(null, p) for p in node.Parameters)
		converted = MD.DomType.CreateDelegate(_result, node.Name, LocationOf(node), ReturnTypeFrom(node.ReturnType), parameters)
		converted.Modifiers = ModifiersFrom(node)
		converted.DeclaringType = _currentType
		for p in parameters: p.DeclaringMember = converted
		AddType(converted)

	override def OnField(node as Field):
		if _currentType is null: return

		_currentType.Add(MD.DomField(
							Name: node.Name,
							ReturnType: ParameterTypeFrom(node.Type),
							Location: LocationOf(node),
							BodyRegion: BodyRegionOf(node),
							DeclaringType: _currentType,
							Modifiers: ModifiersFrom(node)))

	override def OnProperty(node as Property):
		if _currentType is null: return

		converted = MD.DomProperty(
							Name: node.Name,
							ReturnType: ParameterTypeFrom(node.Type),
							Location: LocationOf(node),
							BodyRegion: BodyRegionOf(node),
							DeclaringType: _currentType,
							Modifiers: ModifiersFrom(node))
		if node.Getter is not null:
			converted.PropertyModifier |= MD.PropertyModifier.HasGet
			converted.GetRegion = BodyRegionOf(node.Getter)
		if node.Setter is not null:
			converted.PropertyModifier |= MD.PropertyModifier.HasSet
			converted.SetRegion = BodyRegionOf(node.Setter)

		_currentType.Add(converted)

	override def OnEnumMember(node as EnumMember):
		if _currentType is null: return

		_currentType.Add(MD.DomField(
							Name: node.Name,
							ReturnType: MD.DomReturnType(_currentType),
							Location: LocationOf(node),
							DeclaringType: _currentType,
							Modifiers: MD.Modifiers.Public | MD.Modifiers.Static | MD.Modifiers.Final))

	override def OnConstructor(node as Constructor):
		OnMethod(node, MD.MethodModifier.IsConstructor)

	override def OnDestructor(node as Destructor):
		OnMethod(node, MD.MethodModifier.IsFinalizer)

	override def OnMethod(node as Method):
		OnMethod(node, MD.MethodModifier.None)

	def OnMethod(node as Method, methodModifier as MD.MethodModifier):
		if _currentType is null: return

		converted = MD.DomMethod(
							Name: node.Name,
							Location: LocationOf(node),
							BodyRegion: BodyRegionOf(node),
							DeclaringType: _currentType,
							ReturnType: (MethodReturnTypeFrom(node) if IsRegularMethod(methodModifier) else null),
							Modifiers: ModifiersFrom(node),
							MethodModifier: methodModifier)

		for parameter in node.Parameters:
			converted.Add(ParameterFrom(converted, parameter))

		_currentType.Add(converted)

	def IsRegularMethod(modifier as MD.MethodModifier):
		match modifier:
			case MD.MethodModifier.IsConstructor | MD.MethodModifier.IsFinalizer:
				return false
			otherwise:
				return true

	def ModifiersFrom(node as TypeMember):
		modifiers = MD.Modifiers.None
		modifiers |= MD.Modifiers.Public if node.IsPublic
		modifiers |= MD.Modifiers.Private if node.IsPrivate
		modifiers |= MD.Modifiers.Protected if node.IsProtected
		modifiers |= MD.Modifiers.Internal if node.IsInternal
		modifiers |= MD.Modifiers.Static if node.IsStatic
		modifiers |= MD.Modifiers.Virtual if node.IsVirtual
		modifiers |= MD.Modifiers.Abstract if node.IsAbstract
		modifiers |= MD.Modifiers.Override if node.IsOverride
		modifiers |= MD.Modifiers.Final if node.IsFinal
		return modifiers

	def ParameterFrom(declaringMember as MD.IMember, parameter as ParameterDeclaration):
		return MD.DomParameter(
					DeclaringMember: declaringMember,
					Name: parameter.Name,
					ReturnType: ParameterTypeFrom(parameter.Type),
					Location: LocationOf(parameter))

	virtual def MethodReturnTypeFrom(method as Method):
		if method.ReturnType is not null:
			return ReturnTypeFrom(method.ReturnType)

		match ReturnTypeDetector().Detect(method):
			case ReturnTypeDetector.Result.Yields:
				return MD.DomReturnType("System.Collections.IEnumerator")
			case ReturnTypeDetector.Result.Returns:
				return MD.DomReturnType("object")
			otherwise:
				return MD.DomReturnType("void")

	class ReturnTypeDetector(DepthFirstVisitor):
		enum Result:
			Returns
			Yields
			None

		_result = Result.None

		def Detect(node as Method):
			VisitAllowingCancellation(node)
			return _result

		override def OnBlockExpression(node as BlockExpression):
			pass // skip over closures

		override def OnReturnStatement(node as ReturnStatement):
			if node.Expression is null: return
			_result = Result.Returns
			Cancel()

		override def OnYieldStatement(node as YieldStatement):
			_result = Result.Yields
			Cancel()

	virtual def ParameterTypeFrom(typeRef as TypeReference):
		if typeRef is null: return MD.DomReturnType("object")
		return ReturnTypeFrom(typeRef)

	virtual def ReturnTypeFrom(typeRef as TypeReference) as MD.DomReturnType:
		match typeRef:
			case SimpleTypeReference(Name: name):
				return MD.DomReturnType(name)
			case ArrayTypeReference(ElementType: elementType):
				type = ReturnTypeFrom(elementType)
				type.ArrayDimensions = 1
				type.SetDimension(0, 1)
				return type
			otherwise:
				return MD.DomReturnType("void")

	def AddType(type as MD.DomType):
		if _currentType is not null:
			_currentType.Add(type)
		else:
			type.Namespace = _namespace
			_result.Add(type)

	def WithCurrentType(type as MD.DomType, block as callable()):
		saved = _currentType
		_currentType = type
		try:
			block()
		ensure:
			_currentType = saved

	def BodyRegionOf(node as Node):
		return MD.DomRegion(DomLocationFrom(node.LexicalInfo), DomLocationFrom(node.EndSourceLocation))

	def LocationOf(node as Node):
		location = node.LexicalInfo
		return DomLocationFrom(location)

	def DomLocationFrom(location as SourceLocation):
		return MD.DomLocation(location.Line, location.Column)

namespace Boo.MonoDevelop.ProjectModel

import Boo.Lang.Compiler.Ast
import MonoDevelop.Projects.Dom as MD
import Boo.Lang.PatternMatching

class DomConversionVisitor(DepthFirstVisitor):

	_result as MD.CompilationUnit
	_currentType as MD.DomType
	_namespace as string

	def constructor(result as MD.CompilationUnit):
		_result = result

	override def OnModule(node as Module):
		_namespace = null
		Visit(node.Namespace)
		Visit(node.Members)

	override def OnNamespaceDeclaration(node as NamespaceDeclaration):
		_namespace = node.Name
		region = BodyRegionOf(node.ParentNode)
		domUsing = MD.DomUsing(IsFromNamespace: true, Region: region)
		domUsing.Add(_namespace)
		_result.Add(domUsing)

	override def OnImport(node as Import):
		region = BodyRegionOf(node)
		domUsing = MD.DomUsing(Region: region)
		domUsing.Add(node.Namespace)
		_result.Add(domUsing)

	override def OnClassDefinition(node as ClassDefinition):
		OnTypeDefinition(node, MD.ClassType.Class)

	override def OnInterfaceDefinition(node as InterfaceDefinition):
		OnTypeDefinition(node, MD.ClassType.Interface)

	override def OnStructDefinition(node as StructDefinition):
		OnTypeDefinition(node, MD.ClassType.Struct)

	override def OnEnumDefinition(node as EnumDefinition):
		OnTypeDefinition(node, MD.ClassType.Enum)

	def OnTypeDefinition(node as TypeDefinition, classType as MD.ClassType):
		converted = MD.DomType(
						Name: node.Name,
						ClassType: classType,
						Location: LocationOf(node),
						BodyRegion: BodyRegionOf(node),
						DeclaringType: _currentType,
						Modifiers: ModifiersFrom(node))

		WithCurrentType converted:
			Visit(node.Members)

		AddType(converted)

	override def OnCallableDefinition(node as CallableDefinition):
		parameters = System.Collections.Generic.List[of MD.IParameter](ParameterFrom(null, p) for p in node.Parameters)
		converted = MD.DomType.CreateDelegate(_result, node.Name, LocationOf(node), ReturnTypeFrom(node.ReturnType), parameters)
		converted.Modifiers = ModifiersFrom(node)
		converted.DeclaringType = _currentType
		for p in parameters: p.DeclaringMember = converted
		AddType(converted)

	override def OnField(node as Field):
		if _currentType is null: return

		_currentType.Add(MD.DomField(
							Name: node.Name,
							ReturnType: ParameterTypeFrom(node.Type),
							Location: LocationOf(node),
							DeclaringType: _currentType,
							Modifiers: ModifiersFrom(node)))

	override def OnProperty(node as Property):
		if _currentType is null: return

		converted = MD.DomProperty(
							Name: node.Name,
							ReturnType: ParameterTypeFrom(node.Type),
							Location: LocationOf(node),
							BodyRegion: BodyRegionOf(node),
							DeclaringType: _currentType,
							Modifiers: ModifiersFrom(node))
		if node.Getter is not null:
			converted.PropertyModifier |= MD.PropertyModifier.HasGet
			converted.GetRegion = BodyRegionOf(node.Getter)
		if node.Setter is not null:
			converted.PropertyModifier |= MD.PropertyModifier.HasSet
			converted.SetRegion = BodyRegionOf(node.Setter)

		_currentType.Add(converted)

	override def OnEnumMember(node as EnumMember):
		if _currentType is null: return

		_currentType.Add(MD.DomField(
							Name: node.Name,
							ReturnType: MD.DomReturnType(_currentType),
							Location: LocationOf(node),
							DeclaringType: _currentType,
							Modifiers: MD.Modifiers.Public | MD.Modifiers.Static | MD.Modifiers.Final))

	override def OnConstructor(node as Constructor):
		OnMethod(node, MD.MethodModifier.IsConstructor)

	override def OnDestructor(node as Destructor):
		OnMethod(node, MD.MethodModifier.IsFinalizer)

	override def OnMethod(node as Method):
		OnMethod(node, MD.MethodModifier.None)

	def OnMethod(node as Method, methodModifier as MD.MethodModifier):
		if _currentType is null: return

		converted = MD.DomMethod(
							Name: node.Name,
							Location: LocationOf(node),
							BodyRegion: BodyRegionOf(node),
							DeclaringType: _currentType,
							ReturnType: (MethodReturnTypeFrom(node) if IsRegularMethod(methodModifier) else null),
							Modifiers: ModifiersFrom(node),
							MethodModifier: methodModifier)

		for parameter in node.Parameters:
			converted.Add(ParameterFrom(converted, parameter))

		_currentType.Add(converted)

	def IsRegularMethod(modifier as MD.MethodModifier):
		match modifier:
			case MD.MethodModifier.IsConstructor | MD.MethodModifier.IsFinalizer:
				return false
			otherwise:
				return true

	def ModifiersFrom(node as TypeMember):
		modifiers = MD.Modifiers.None
		modifiers |= MD.Modifiers.Public if node.IsPublic
		modifiers |= MD.Modifiers.Private if node.IsPrivate
		modifiers |= MD.Modifiers.Protected if node.IsProtected
		modifiers |= MD.Modifiers.Internal if node.IsInternal
		modifiers |= MD.Modifiers.Static if node.IsStatic
		modifiers |= MD.Modifiers.Virtual if node.IsVirtual
		modifiers |= MD.Modifiers.Abstract if node.IsAbstract
		modifiers |= MD.Modifiers.Override if node.IsOverride
		modifiers |= MD.Modifiers.Final if node.IsFinal
		return modifiers

	def ParameterFrom(declaringMember as MD.IMember, parameter as ParameterDeclaration):
		return MD.DomParameter(
					DeclaringMember: declaringMember,
					Name: parameter.Name,
					ReturnType: ParameterTypeFrom(parameter.Type),
					Location: LocationOf(parameter))

	virtual def MethodReturnTypeFrom(method as Method):
		if method.ReturnType is not null:
			return ReturnTypeFrom(method.ReturnType)

		match ReturnTypeDetector().Detect(method):
			case ReturnTypeDetector.Result.Yields:
				return MD.DomReturnType("System.Collections.IEnumerator")
			case ReturnTypeDetector.Result.Returns:
				return MD.DomReturnType("object")
			otherwise:
				return MD.DomReturnType("void")

	class ReturnTypeDetector(DepthFirstVisitor):
		enum Result:
			Returns
			Yields
			None

		_result = Result.None

		def Detect(node as Method):
			VisitAllowingCancellation(node)
			return _result

		override def OnBlockExpression(node as BlockExpression):
			pass // skip over closures

		override def OnReturnStatement(node as ReturnStatement):
			if node.Expression is null: return
			_result = Result.Returns
			Cancel()

		override def OnYieldStatement(node as YieldStatement):
			_result = Result.Yields
			Cancel()

	virtual def ParameterTypeFrom(typeRef as TypeReference):
		if typeRef is null: return MD.DomReturnType("object")
		return ReturnTypeFrom(typeRef)

	virtual def ReturnTypeFrom(typeRef as TypeReference) as MD.DomReturnType:
		match typeRef:
			case SimpleTypeReference(Name: name):
				return MD.DomReturnType(name)
			case ArrayTypeReference(ElementType: elementType):
				type = ReturnTypeFrom(elementType)
				type.ArrayDimensions = 1
				type.SetDimension(0, 1)
				return type
			otherwise:
				return MD.DomReturnType("void")

	def AddType(type as MD.DomType):
		if _currentType is not null:
			_currentType.Add(type)
		else:
			type.Namespace = _namespace
			_result.Add(type)

	def WithCurrentType(type as MD.DomType, block as callable()):
		saved = _currentType
		_currentType = type
		try:
			block()
		ensure:
			_currentType = saved

	def BodyRegionOf(node as Node):
		return MD.DomRegion(DomLocationFrom(node.LexicalInfo), DomLocationFrom(node.EndSourceLocation))

	def LocationOf(node as Node):
		location = node.LexicalInfo
		return DomLocationFrom(location)

	def DomLocationFrom(location as SourceLocation):
		return MD.DomLocation(location.Line, location.Column)


Don't prevent application from being closed when the machine is being shut down

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

#region license
// Copyright (c) 2003, 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

"""
This example shows how to use db4o 3.0 (http://www.db4o.com), a great
oodbm infrastructure, to implement a simple time tracking application.
"""

namespace TimeTracker

import System
import System.ComponentModel
import System.IO
import System.Drawing
import System.Windows.Forms
import TimeTracker.Model

class MainForm(Form):

	_system as TimeTrackerSystem

	_components = System.ComponentModel.Container()

	_current as Activity

	_tabs = TabControl(Dock: DockStyle.Fill)

	_view = ListView(View: View.Details, Dock: DockStyle.Fill)

	_timer = System.Windows.Forms.Timer(_components,
										Tick: UpdateNotifyText,
										Interval: 30000)

	_notify as NotifyIcon

	def constructor():
		self.Text = "Boo Time Tracker (powered by db4o)"
		self.ShowInTaskbar = false
		self.MinimizeBox = false
		Minimize()

		_notify = NotifyIcon(_components,
				Text: self.Text,
				Icon: self.Icon,
				Visible: true,
				ContextMenu: CreateContextMenu(),
				DoubleClick: { self.WindowState = WindowState.Normal })

		_system = TimeTrackerSystem(
						Path.Combine(
							Application.UserAppDataPath,
							"timetracker.yap"))

		activitiesPage = TabPage(Text: "Activities")
		activitiesPage.Controls.Add(_view)

		prompt = booish.gui.PromptBox(
						Font: System.Drawing.Font("Lucida Console", 11))
		prompt.Interpreter.SetValue("system", _system)
		prompt.Interpreter.SetValue("MainForm", self)
		prompt.Interpreter.References.Add(typeof(Project).Assembly)
		promptPage = TabPage(Text: "Console")
		promptPage.Controls.Add(prompt)
		_tabs.TabPages.AddRange((activitiesPage, promptPage))

		self.Controls.Add(_tabs)

		_timer.Start()

	def UpdateNotifyText():
		if _current is null:
			_notify.Text = self.Text
		else:
			_current.Finished = date.Now
			_notify.Text = _current.ToString()

	override def OnClosing(args as CancelEventArgs):
		unless Environment.HasShutdownStarted:
			Minimize()
			args.Cancel = true
		super(args)

	def Minimize():
		self.WindowState = WindowState.Minimized

	def _contextMenu_Popup(sender as ContextMenu):

		sender.MenuItems.Clear()

		for project in _system.Projects:
			sender.MenuItems.Add(CreateProjectMenuItem(project))

		sender.MenuItems.Add(MenuItem("-"))
		sender.MenuItems.Add(
			MenuItem(Text: "New Project",
					Click: NewProject))
		sender.MenuItems.Add(MenuItem("-"))
		sender.MenuItems.Add(
			MenuItem(Text: "Quit",
					Click: Application.Exit))

	def CreateProjectMenuItem(project as Project):
		menu = MenuItem(Text: project.Name)
		for t in _system.QueryTasks(project):
			menu.MenuItems.Add(
				MenuItem(Text: t.Name, Click: { StartTask(t) }))

		menu.MenuItems.Add(MenuItem("-"))
		menu.MenuItems.Add(
			MenuItem(Text: "New Task",
					Click: { NewTask(project) }))
		return menu

	def CreateContextMenu():
		return System.Windows.Forms.ContextMenu(Popup: _contextMenu_Popup)

	def FlushCurrentActivity():
		 return if _current is null

		 _current.Finished = date.Now
		 _system.AddActivity(_current)

		 _current = null

	def StartTask(task as Task):
		FlushCurrentActivity()
		_current = Activity(Task: task, Started: date.Now)
		UpdateNotifyText()

	def NewTask(project as Project):
		name = Prompt("New Task", "Task Name: ")
		if name is not null:
			_system.AddTask(Task(Project: project, Name: name))

	def NewProject():
		name = Prompt("New Project", "Project's Name: ")
		if name is not null:
			_system.AddProject(Project(Name: name))

	def Prompt(title as string, message as string):
		dlg = PromptDialog(Text: title,
						Message: message,
						Font: self.Font,
						StartPosition: FormStartPosition.CenterScreen)
		if DialogResult.OK == dlg.ShowDialog():
			return dlg.Value

	override def Dispose(disposing as bool):
		FlushCurrentActivity()
		_components.Dispose()
		_system.Dispose()
		super(disposing)

class PromptDialog(Form):

	_value as TextBox
	_message as Label

	def constructor():

		_message = Label(Location: Point(2, 2),
						Size: System.Drawing.Size(200, 18))
		_value = TextBox(
						Location: Point(2, 20),
						Size: System.Drawing.Size(290, 18))

		ok = Button(Text: "OK",
					Location: Point(50, 45),
					DialogResult: DialogResult.OK)

		cancel = Button(Text: "Cancel",
					Location: Point(150, 45),
					DialogResult: DialogResult.Cancel)

		SuspendLayout()
		self.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog
		self.StartPosition = FormStartPosition.CenterParent
		self.Size = System.Drawing.Size(300, 120)
		self.AcceptButton = ok
		self.CancelButton = cancel
		Controls.Add(_message)
		Controls.Add(_value)
		Controls.Add(ok)
		Controls.Add(cancel)
		ResumeLayout(false)

	Message as string:
		set:
			_message.Text = value

	Value:
		get:
			return _value.Text
		set:
			_value.Text = value

Application.Run(MainForm())

#region license
// Copyright (c) 2003, 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

"""
This example shows how to use db4o 3.0 (http://www.db4o.com), a great
oodbm infrastructure, to implement a simple time tracking application.
"""

namespace TimeTracker

import System
import System.ComponentModel
import System.IO
import System.Drawing
import System.Windows.Forms
import TimeTracker.Model

class MainForm(Form):

	_system as TimeTrackerSystem

	_components = System.ComponentModel.Container()

	_current as Activity

	_tabs = TabControl(Dock: DockStyle.Fill)

	_view = ListView(View: View.Details, Dock: DockStyle.Fill)

	_timer = System.Windows.Forms.Timer(_components,
										Tick: UpdateNotifyText,
										Interval: 30000)

	_notify as NotifyIcon

	def constructor():
		self.Text = "Boo Time Tracker (powered by db4o)"
		self.ShowInTaskbar = false
		self.MinimizeBox = false
		Minimize()

		_notify = NotifyIcon(_components,
				Text: self.Text,
				Icon: self.Icon,
				Visible: true,
				ContextMenu: CreateContextMenu(),
				DoubleClick: { self.WindowState = WindowState.Normal })

		_system = TimeTrackerSystem(
						Path.Combine(
							Application.UserAppDataPath,
							"timetracker.yap"))

		activitiesPage = TabPage(Text: "Activities")
		activitiesPage.Controls.Add(_view)

		prompt = booish.gui.PromptBox(
						Font: System.Drawing.Font("Lucida Console", 11))
		prompt.Interpreter.SetValue("system", _system)
		prompt.Interpreter.SetValue("MainForm", self)
		prompt.Interpreter.References.Add(typeof(Project).Assembly)
		promptPage = TabPage(Text: "Console")
		promptPage.Controls.Add(prompt)
		_tabs.TabPages.AddRange((activitiesPage, promptPage))

		self.Controls.Add(_tabs)

		_timer.Start()

	def UpdateNotifyText():
		if _current is null:
			_notify.Text = self.Text
		else:
			_current.Finished = date.Now
			_notify.Text = _current.ToString()

	override def OnClosing(args as CancelEventArgs):
		Minimize()
		args.Cancel = true
		super(args)

	def Minimize():
		self.WindowState = WindowState.Minimized

	def _contextMenu_Popup(sender as ContextMenu):

		sender.MenuItems.Clear()

		for project in _system.Projects:
			sender.MenuItems.Add(CreateProjectMenuItem(project))

		sender.MenuItems.Add(MenuItem("-"))
		sender.MenuItems.Add(
			MenuItem(Text: "New Project",
					Click: NewProject))
		sender.MenuItems.Add(MenuItem("-"))
		sender.MenuItems.Add(
			MenuItem(Text: "Quit",
					Click: Application.Exit))

	def CreateProjectMenuItem(project as Project):
		menu = MenuItem(Text: project.Name)
		for t in _system.QueryTasks(project):
			menu.MenuItems.Add(
				MenuItem(Text: t.Name, Click: { StartTask(t) }))

		menu.MenuItems.Add(MenuItem("-"))
		menu.MenuItems.Add(
			MenuItem(Text: "New Task",
					Click: { NewTask(project) }))
		return menu

	def CreateContextMenu():
		return System.Windows.Forms.ContextMenu(Popup: _contextMenu_Popup)

	def FlushCurrentActivity():
		 return if _current is null

		 _current.Finished = date.Now
		 _system.AddActivity(_current)

		 _current = null

	def StartTask(task as Task):
		FlushCurrentActivity()
		_current = Activity(Task: task, Started: date.Now)
		UpdateNotifyText()

	def NewTask(project as Project):
		name = Prompt("New Task", "Task Name: ")
		if name is not null:
			_system.AddTask(Task(Project: project, Name: name))

	def NewProject():
		name = Prompt("New Project", "Project's Name: ")
		if name is not null:
			_system.AddProject(Project(Name: name))

	def Prompt(title as string, message as string):
		dlg = PromptDialog(Text: title,
						Message: message,
						Font: self.Font,
						StartPosition: FormStartPosition.CenterScreen)
		if DialogResult.OK == dlg.ShowDialog():
			return dlg.Value

	override def Dispose(disposing as bool):
		FlushCurrentActivity()
		_components.Dispose()
		_system.Dispose()
		super(disposing)

class PromptDialog(Form):

	_value as TextBox
	_message as Label

	def constructor():

		_message = Label(Location: Point(2, 2),
						Size: System.Drawing.Size(200, 18))
		_value = TextBox(
						Location: Point(2, 20),
						Size: System.Drawing.Size(290, 18))

		ok = Button(Text: "OK",
					Location: Point(50, 45),
					DialogResult: DialogResult.OK)

		cancel = Button(Text: "Cancel",
					Location: Point(150, 45),
					DialogResult: DialogResult.Cancel)

		SuspendLayout()
		self.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog
		self.StartPosition = FormStartPosition.CenterParent
		self.Size = System.Drawing.Size(300, 120)
		self.AcceptButton = ok
		self.CancelButton = cancel
		Controls.Add(_message)
		Controls.Add(_value)
		Controls.Add(ok)
		Controls.Add(cancel)
		ResumeLayout(false)

	Message as string:
		set:
			_message.Text = value

	Value:
		get:
			return _value.Text
		set:
			_value.Text = value

Application.Run(MainForm())


Fixed unpacking step to retain type information

namespace BooJs.Compiler.Steps

import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.Steps

class NormalizeUnpack(AbstractTransformerCompilerStep):
"""
    Boo allows to unpack an enumerable into local variables using the following construct:

        a, b, c = (10, 20, 30)

    In this step we convert that operation to the following statements:

        @(upk = (10, 20, 30), a = upk[0], b = upk[1], c = upk[2])

    TODO: Shall we allow to unpack generators too?
"""
    static final REFERENCE_NAME = '__upk'

    _method as Method = null

    def OnMethod(node as Method):
        _method = node
        super(node)

    def OnConstructor(node as Constructor):
        _method = node
        super(node)

    def OnUnpackStatement(node as UnpackStatement):

        seq = CodeBuilder.CreateEvalInvocation(node.LexicalInfo)

        # If the target to unpack is a reference just use it
        upkref as ReferenceExpression
        if node.Expression.NodeType == NodeType.ReferenceExpression:
            upkref = node.Expression
        else:
            upkref = ReferenceExpression(Name: REFERENCE_NAME)
            seq.Arguments.Add( [| $upkref = $(node.Expression) |] )

        for i as int, decl as Declaration in enumerate(node.Declarations):
            seq.Arguments.Add( [| $(ReferenceExpression(decl.Name)) = $(upkref)[$i] |] )

        ReplaceCurrentNode ExpressionStatement(node.LexicalInfo, Expression: seq)

namespace BooJs.Compiler.Steps

import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.Steps

class NormalizeUnpack(AbstractTransformerCompilerStep):
"""
    Boo allows to unpack an enumerable into local variables using the following construct:

        a, b, c = (10, 20, 30)

    In this step we convert that operation to the following statements:

        @(upk = (10, 20, 30), a = upk[0], b = upk[1], c = upk[2])
"""
    static final REFERENCE_NAME = '__upk'

    _method as Method = null

    def OnMethod(node as Method):
        _method = node
        super(node)

    def OnConstructor(node as Constructor):
        _method = node
        super(node)

    def OnUnpackStatement(node as UnpackStatement):
        # Create a local for the unpack holder and flag it as used
        local = CodeBuilder.DeclareLocal(_method, REFERENCE_NAME, TypeSystemServices.ListType)
        local.IsUsed = true

        # Build the sequence to unpack the values
        upkref = CodeBuilder.CreateLocalReference(REFERENCE_NAME, local)
        seq = CodeBuilder.CreateEvalInvocation(node.LexicalInfo)
        be = CodeBuilder.CreateAssignment(node.LexicalInfo, upkref, node.Expression)
        seq.Arguments.Add(be)
        for i as int, decl as Declaration in enumerate(node.Declarations):
            refe = ReferenceExpression(decl.LexicalInfo, decl.Name)
            slice = CodeBuilder.CreateSlicing(upkref, i)
            be = CodeBuilder.CreateAssignment(decl.LexicalInfo, refe, slice)
            seq.Arguments.Add(be)

        ReplaceCurrentNode ExpressionStatement(node.LexicalInfo, seq)