booish2: do not block at load either (need refactoring  printprompt(bool) to reset line...)

git-svn-id: fcce7d6ce0ac15bf24f68871b5145d46fc068279@2506 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 = (char(':'), char('\\'),)

	[getter(LastValue)]
	_lastValue

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

	def constructor():
		super()
		InitializeStandardReferences()
		LoadHistory()

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

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

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

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

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

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

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

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

	[property(PromptColor)]
	_promptColor = ConsoleColor.Green

	[property(ExceptionColor)]
	_exceptionColor = ConsoleColor.Red

	[property(SuggestionsColor)]
	_suggestionsColor = ConsoleColor.Yellow

	[property(SelectedSuggestionColor)]
	_selectedSuggestionColor = ConsoleColor.Magenta

	[property(SelectedSuggestionIndex)]
	_selectedSuggestionIndex = -1

	[property(Suggestions)]
	_suggestions as (object)

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

	private _builtins as (IEntity)
	private _filter as string

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

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

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

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

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

		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(Environment.NewLine)
		ConsolePrintPrompt()
		Console.Write(_line.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) #FIXME: insert at cursor!
				_line.Append(completion)
				if not m.GetParameters() or m.GetParameters().Length == 0:
					Console.Write('()') #FIXME: insert at cursor!
					_line.Append('()')
				else:
					Console.Write('(') #FIXME: insert at cursor!
					_line.Append('(')

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

		_selectedSuggestionIndex = -1
		_suggestions = null

	private _beforeHistory = string.Empty

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

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

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

			newLine = keyChar in Environment.NewLine

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

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

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

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

			_selectedSuggestionIndex = -1

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

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

			line = _line.ToString()

			if newLine:
				Console.Write(Environment.NewLine)

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

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

					_line.Length = 0
					ConsolePrintPrompt()

			lastChar = keyChar

		SaveHistory()
		DisplayGoodbye()

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

		cmd = line.Split()

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

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

		else:
			return false
		return true

	private _indent as int = 0

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

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

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

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

	override def SetLastValue(value):
		_lastValue = value

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

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

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

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

	def DisplayProblems(problems as ICollection):
		return if problems is null or problems.Count == 0
		Console.ForegroundColor = _exceptionColor
		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
    /s[ave] file : writes your current booish session into file
    /g[lobals] : returns names of all variables known to interpreter
    /n[ames] : namespace navigation
    /q[uit] : exits the interpreter

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	callable Representer(value, writer as TextWriter)

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

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

#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 = (char(':'), char('\\'),)

	[getter(LastValue)]
	_lastValue

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

	def constructor():
		super()
		InitializeStandardReferences()
		LoadHistory()

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

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

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

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

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

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

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

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

	[property(PromptColor)]
	_promptColor = ConsoleColor.Green

	[property(ExceptionColor)]
	_exceptionColor = ConsoleColor.Red

	[property(SuggestionsColor)]
	_suggestionsColor = ConsoleColor.Yellow

	[property(SelectedSuggestionColor)]
	_selectedSuggestionColor = ConsoleColor.Magenta

	[property(SelectedSuggestionIndex)]
	_selectedSuggestionIndex = -1

	[property(Suggestions)]
	_suggestions as (object)

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

	private _builtins as (IEntity)
	private _filter as string

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

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

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

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

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

		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(Environment.NewLine)
		ConsolePrintPrompt()
		Console.Write(_line.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) #FIXME: insert at cursor!
				_line.Append(completion)
				if not m.GetParameters() or m.GetParameters().Length == 0:
					Console.Write('()') #FIXME: insert at cursor!
					_line.Append('()')
				else:
					Console.Write('(') #FIXME: insert at cursor!
					_line.Append('(')

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

		_selectedSuggestionIndex = -1
		_suggestions = null

	private _beforeHistory = string.Empty

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

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

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

			newLine = keyChar in Environment.NewLine

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

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

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

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

			_selectedSuggestionIndex = -1

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

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

			line = _line.ToString()

			if newLine:
				Console.Write(Environment.NewLine)

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

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

					_line.Length = 0
					ConsolePrintPrompt()

			lastChar = keyChar

		SaveHistory()
		DisplayGoodbye()

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

		cmd = line.Split()

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

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

		else:
			return false
		return true

	private _indent as int = 0

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

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

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

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

	override def SetLastValue(value):
		_lastValue = value

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

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

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

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

	def DisplayProblems(problems as ICollection):
		return if problems is null or problems.Count == 0
		Console.ForegroundColor = _exceptionColor
		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
    /s[ave] file : writes your current booish session into file
    /g[lobals] : returns names of all variables known to interpreter
    /n[ames] : namespace navigation
    /q[uit] : exits the interpreter

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

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

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

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

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

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

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

	def load([required] path as string):
		if path.EndsWith(".boo"):
			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 save([required] path as string):
		if path is string.Empty:
			path = "booish_session.boo"
		elif not path.EndsWith(".boo"):
			path = "${path}.boo"
		try:
			using sw = System.IO.File.CreateText(path):
				for line in _session:
					sw.Write(line)
			ConsolePrintMessage("Session saved to '${path}'.")
			_line.Length = 0
			ConsolePrintPrompt()
		except:
			ConsolePrintError("Cannot save to '${path}'. Check if path is valid and has correct permissions.")
			_line.Length = 0
			ConsolePrintPrompt()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	callable Representer(value, writer as TextWriter)

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

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


Copy referenced files to target dir

Copy referenced files to target dir

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

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

namespace BooBinding

import System
import System.Collections
import System.IO
import System.Diagnostics
import System.Text
import System.Text.RegularExpressions
import System.CodeDom.Compiler

import ICSharpCode.Core.Services

import ICSharpCode.SharpDevelop.Internal.Project
import ICSharpCode.SharpDevelop.Gui
import ICSharpCode.SharpDevelop.Services

class BooBindingCompilerManager:
	static _normalError  = /(?<file>.*)\((?<line>\d+),(?<column>\d+)\):\s+(?<number>[\d\w]+):\s+(?<message>.*)/
	static _generalError = /(?<error>.+)\s+(?<number>[\d\w]+):\s+(?<message>.*)/

	def GetCompiledOutputName(fileName as string) as string:
		return Path.ChangeExtension(fileName, ".exe")

	def GetCompiledOutputName(project as IProject) as string:
		p as BooProject = project;
		compilerparameters as BooCompilerParameters = p.ActiveConfiguration

		fileUtilityService as FileUtilityService = ServiceManager.Services.GetService(typeof(FileUtilityService))
		exe = fileUtilityService.GetDirectoryNameWithSeparator(compilerparameters.OutputDirectory) + compilerparameters.OutputAssembly
		if (compilerparameters.CompileTarget == CompileTarget.Library):
			return exe + ".dll"
		else:
			return exe + ".exe"

	def CanCompile(fileName as string) as bool:
		return Path.GetExtension(fileName).ToLower() == ".boo";

	static _compilerNotFound = "Boo compiler not found.\nYou have to specify the path where booc.exe is in the project options!\n"

	private def MakeError(text as string):
		cr = CompilerResults(TempFileCollection())
		cr.Errors.Add(CompilerError(ErrorText: text))
		return DefaultCompilerResult(cr, text)

	private def Compile(compilerparameters as BooCompilerParameters, fileNames as (string), outputFile as string, p as IProject) as ICompilerResult:
		fileUtilityService as FileUtilityService = ServiceManager.Services.GetService(typeof(FileUtilityService))
		messageService as IMessageService = ServiceManager.Services.GetService(typeof(IMessageService))

		if compilerparameters.BooPath == "":
			messageService.ShowError(_compilerNotFound)
			return MakeError(_compilerNotFound)
		booDir = fileUtilityService.GetDirectoryNameWithSeparator(compilerparameters.BooPath)
		compilerName = booDir + "booc.exe"

		if not File.Exists(compilerName):
			messageService.ShowError(_compilerNotFound)
			return MakeError(_compilerNotFound)

		cmd = StringBuilder()
		cmd.Append("\"${compilerName}\"")

		cmd.Append(" -o:\"${outputFile}\"")

		cmd.Append(" -vv") if compilerparameters.Verbose

		cmd.Append(" -t:")
		if (compilerparameters.CompileTarget == CompileTarget.WinExe):
			cmd.Append("winexe")
		else:
			if (compilerparameters.CompileTarget == CompileTarget.Library):
				cmd.Append("library");
			else:
				cmd.Append("exe");

		// Default references are already added by booc.rsp

		if p != null:
			// write references
			for lib as ProjectReference in p.ProjectReferences:
				cmd.Append(" \"-r:${lib.GetReferencedFileName(p)}\"");
			// write embedded resources
			for finfo as ProjectFile in p.ProjectFiles:
				if finfo.Subtype != Subtype.Directory and finfo.BuildAction == BuildAction.EmbedAsResource:
					cmd.Append(" \"-resource:${finfo.Name}\"")

		for fileName as string in fileNames:
			cmd.Append(" \"${Path.GetFullPath(fileName)}\"")

		outstr = cmd.ToString()
		tf = TempFileCollection()
		output = ""
		error = ""
		Executor.ExecWaitWithCapture(outstr, Path.GetFullPath(compilerparameters.OutputDirectory), tf, output, error)

		result = ParseOutput(tf, output)

		outputDirectory = Path.GetDirectoryName(outputFile)

		CopyToDirIgnoringErrors(Path.Combine(booDir, "Boo.dll"), outputDirectory)
		for lib as ProjectReference in p.ProjectReferences:
			if lib.LocalCopy:
				CopyToDirIgnoringErrors(
					lib.GetReferencedFileName(p),
					outputDirectory)

		File.Delete(output)
		File.Delete(error)
		return result

	def CopyToDirIgnoringErrors(fname as string, outputDirectory as string):
		try:
			File.Copy(fname, Path.Combine(outputDirectory, Path.GetFileName(fname)))
		except:
			pass

	def CompileFile(fileName as string, compilerparameters as BooCompilerParameters) as ICompilerResult:
		compilerparameters.OutputDirectory = Path.GetDirectoryName(fileName)
		compilerparameters.OutputAssembly  = Path.GetFileNameWithoutExtension(fileName)

		return Compile(compilerparameters, (fileName,), GetCompiledOutputName(fileName), null)

	def CompileProject(project as BooProject) as ICompilerResult:
		compilerparameters as BooCompilerParameters = project.ActiveConfiguration

		fileNames = ArrayList()

		for	finfo as ProjectFile in project.ProjectFiles:
			if finfo.Subtype != Subtype.Directory:
				if finfo.BuildAction == BuildAction.Compile:
						fileNames.Add(finfo.Name)

		exe = GetCompiledOutputName(project)
		if compilerparameters.CompileTarget == CompileTarget.WinExe:
			WriteManifestFile(exe)

		return Compile(compilerparameters, fileNames.ToArray(typeof(string)), exe, project)

	def WriteManifestFile(fileName as string):
		manifestFile = fileName + ".manifest"
		return if File.Exists(manifestFile)
		using sw = StreamWriter(manifestFile):
			sw.WriteLine("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>")
			sw.WriteLine("")
			sw.WriteLine("<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">")
			sw.WriteLine("	<dependency>")
			sw.WriteLine("		<dependentAssembly>")
			sw.WriteLine("			<assemblyIdentity")
			sw.WriteLine("				type=\"win32\"")
			sw.WriteLine("				name=\"Microsoft.Windows.Common-Controls\"")
			sw.WriteLine("				version=\"6.0.0.0\"")
			sw.WriteLine("				processorArchitecture=\"X86\"")
			sw.WriteLine("				publicKeyToken=\"6595b64144ccf1df\"")
			sw.WriteLine("				language=\"*\"")
			sw.WriteLine("			/>")
			sw.WriteLine("		</dependentAssembly>")
			sw.WriteLine("	</dependency>")
			sw.WriteLine("</assembly>")

	def ParseOutput(tf as TempFileCollection, file as string) as ICompilerResult:
		compilerOutput = StringBuilder();

		using sr = File.OpenText(file):
			cr = CompilerResults(tf)

			curLine = sr.ReadLine()
			while curLine != null:
				compilerOutput.Append(curLine)
				compilerOutput.Append("\n")
				curLine = curLine.Trim();
				if curLine.Length == 0:
					curLine = sr.ReadLine()
					continue

				error = CompilerError()

				// try to match standard errors
				match = _normalError.Match(curLine)
				if match.Success:
					error.Column      = Int32.Parse(match.Result('${column}'));
					error.Line        = Int32.Parse(match.Result('${line}'));
					error.FileName    = Path.GetFullPath(match.Result('${file}'));
					//error.IsWarning   = match.Result('${error}') == "warning";
					error.ErrorNumber = match.Result('${number}');
					error.ErrorText   = match.Result('${message}');
				else:
					match = _generalError.Match(curLine); // try to match general csc errors
					if match.Success:
						error.IsWarning   = match.Result('${error}') == "warning";
						error.ErrorNumber = match.Result('${number}');
						error.ErrorText   = match.Result('${message}');
					else:
						// give up and skip the line
						curLine = sr.ReadLine()
						continue

				cr.Errors.Add(error);

				curLine = sr.ReadLine()
		return DefaultCompilerResult(cr, compilerOutput.ToString())

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

namespace BooBinding

import System
import System.Collections
import System.IO
import System.Diagnostics
import System.Text
import System.Text.RegularExpressions
import System.CodeDom.Compiler

import ICSharpCode.Core.Services

import ICSharpCode.SharpDevelop.Internal.Project
import ICSharpCode.SharpDevelop.Gui
import ICSharpCode.SharpDevelop.Services

class BooBindingCompilerManager:
	static _normalError  = /(?<file>.*)\((?<line>\d+),(?<column>\d+)\):\s+(?<number>[\d\w]+):\s+(?<message>.*)/
	static _generalError = /(?<error>.+)\s+(?<number>[\d\w]+):\s+(?<message>.*)/

	def GetCompiledOutputName(fileName as string) as string:
		return Path.ChangeExtension(fileName, ".exe")

	def GetCompiledOutputName(project as IProject) as string:
		p as BooProject = project;
		compilerparameters as BooCompilerParameters = p.ActiveConfiguration

		fileUtilityService as FileUtilityService = ServiceManager.Services.GetService(typeof(FileUtilityService))
		exe = fileUtilityService.GetDirectoryNameWithSeparator(compilerparameters.OutputDirectory) + compilerparameters.OutputAssembly
		if (compilerparameters.CompileTarget == CompileTarget.Library):
			return exe + ".dll"
		else:
			return exe + ".exe"

	def CanCompile(fileName as string) as bool:
		return Path.GetExtension(fileName).ToLower() == ".boo";

	static _compilerNotFound = "Boo compiler not found.\nYou have to specify the path where booc.exe is in the project options!\n"

	private def MakeError(text as string):
		cr = CompilerResults(TempFileCollection())
		cr.Errors.Add(CompilerError(ErrorText: text))
		return DefaultCompilerResult(cr, text)

	private def Compile(compilerparameters as BooCompilerParameters, fileNames as (string), outputFile as string, p as IProject) as ICompilerResult:
		fileUtilityService as FileUtilityService = ServiceManager.Services.GetService(typeof(FileUtilityService))
		messageService as IMessageService = ServiceManager.Services.GetService(typeof(IMessageService))

		if compilerparameters.BooPath == "":
			messageService.ShowError(_compilerNotFound)
			return MakeError(_compilerNotFound)
		booDir = fileUtilityService.GetDirectoryNameWithSeparator(compilerparameters.BooPath)
		compilerName = booDir + "booc.exe"

		if not File.Exists(compilerName):
			messageService.ShowError(_compilerNotFound)
			return MakeError(_compilerNotFound)

		cmd = StringBuilder()
		cmd.Append("\"${compilerName}\"")

		cmd.Append(" -o:\"${outputFile}\"")

		cmd.Append(" -vv") if compilerparameters.Verbose

		cmd.Append(" -t:")
		if (compilerparameters.CompileTarget == CompileTarget.WinExe):
			cmd.Append("winexe")
		else:
			if (compilerparameters.CompileTarget == CompileTarget.Library):
				cmd.Append("library");
			else:
				cmd.Append("exe");

		// Default references are already added by booc.rsp

		if p != null:
			// write references
			for lib as ProjectReference in p.ProjectReferences:
				cmd.Append(" \"-r:${lib.GetReferencedFileName(p)}\"");
			// write embedded resources
			for finfo as ProjectFile in p.ProjectFiles:
				if finfo.Subtype != Subtype.Directory and finfo.BuildAction == BuildAction.EmbedAsResource:
					cmd.Append(" \"-resource:${finfo.Name}\"")

		for fileName as string in fileNames:
			cmd.Append(" \"${Path.GetFullPath(fileName)}\"")

		outstr = cmd.ToString()
		tf = TempFileCollection()
		output = ""
		error = ""
		Executor.ExecWaitWithCapture(outstr, Path.GetFullPath(compilerparameters.OutputDirectory), tf, output, error)

		result = ParseOutput(tf, output)

		try:
			File.Copy(booDir + "Boo.dll", Path.Combine(Path.GetDirectoryName(outputFile), "Boo.dll"))
		except:
			pass

		File.Delete(output)
		File.Delete(error)
		return result

	def CompileFile(fileName as string, compilerparameters as BooCompilerParameters) as ICompilerResult:
		compilerparameters.OutputDirectory = Path.GetDirectoryName(fileName)
		compilerparameters.OutputAssembly  = Path.GetFileNameWithoutExtension(fileName)

		return Compile(compilerparameters, (fileName,), GetCompiledOutputName(fileName), null)

	def CompileProject(project as BooProject) as ICompilerResult:
		compilerparameters as BooCompilerParameters = project.ActiveConfiguration

		fileNames = ArrayList()

		for	finfo as ProjectFile in project.ProjectFiles:
			if finfo.Subtype != Subtype.Directory:
				if finfo.BuildAction == BuildAction.Compile:
						fileNames.Add(finfo.Name)

		exe = GetCompiledOutputName(project)
		if compilerparameters.CompileTarget == CompileTarget.WinExe:
			WriteManifestFile(exe)

		return Compile(compilerparameters, fileNames.ToArray(typeof(string)), exe, project)

	def WriteManifestFile(fileName as string):
		manifestFile = fileName + ".manifest"
		return if File.Exists(manifestFile)
		using sw = StreamWriter(manifestFile):
			sw.WriteLine("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>")
			sw.WriteLine("")
			sw.WriteLine("<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">")
			sw.WriteLine("	<dependency>")
			sw.WriteLine("		<dependentAssembly>")
			sw.WriteLine("			<assemblyIdentity")
			sw.WriteLine("				type=\"win32\"")
			sw.WriteLine("				name=\"Microsoft.Windows.Common-Controls\"")
			sw.WriteLine("				version=\"6.0.0.0\"")
			sw.WriteLine("				processorArchitecture=\"X86\"")
			sw.WriteLine("				publicKeyToken=\"6595b64144ccf1df\"")
			sw.WriteLine("				language=\"*\"")
			sw.WriteLine("			/>")
			sw.WriteLine("		</dependentAssembly>")
			sw.WriteLine("	</dependency>")
			sw.WriteLine("</assembly>")

	def ParseOutput(tf as TempFileCollection, file as string) as ICompilerResult:
		compilerOutput = StringBuilder();

		using sr = File.OpenText(file):
			cr = CompilerResults(tf)

			curLine = sr.ReadLine()
			while curLine != null:
				compilerOutput.Append(curLine)
				compilerOutput.Append("\n")
				curLine = curLine.Trim();
				if curLine.Length == 0:
					curLine = sr.ReadLine()
					continue

				error = CompilerError()

				// try to match standard errors
				match = _normalError.Match(curLine)
				if match.Success:
					error.Column      = Int32.Parse(match.Result('${column}'));
					error.Line        = Int32.Parse(match.Result('${line}'));
					error.FileName    = Path.GetFullPath(match.Result('${file}'));
					//error.IsWarning   = match.Result('${error}') == "warning";
					error.ErrorNumber = match.Result('${number}');
					error.ErrorText   = match.Result('${message}');
				else:
					match = _generalError.Match(curLine); // try to match general csc errors
					if match.Success:
						error.IsWarning   = match.Result('${error}') == "warning";
						error.ErrorNumber = match.Result('${number}');
						error.ErrorText   = match.Result('${message}');
					else:
						// give up and skip the line
						curLine = sr.ReadLine()
						continue

				cr.Errors.Add(error);

				curLine = sr.ReadLine()
		return DefaultCompilerResult(cr, compilerOutput.ToString())


PropertySort.Alphabetical does not always work

git-svn-id: c3b8989232654601f1acea7403f771adaaa44a81@1237 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

"""
Interactive Forms-based Console
"""

namespace booish.gui

import System
import booish
import System.Windows.Forms
import System.Drawing

class PromptBox(TextBox):

	static Enter = chr(13)

	enum InputState:
		SingleLine = 0
		Block = 1

	_state = InputState.SingleLine

	_block = System.IO.StringWriter()

	[getter(Interpreter)]
	_interpreter = InteractiveInterpreter(
								RememberLastValue: true,
								Print: print)

	def constructor():
		self.Dock = DockStyle.Fill
		self.Multiline = true
		self.AcceptsTab = true
		self.ScrollBars = ScrollBars.Vertical
		_interpreter.References.Add(typeof(TextBox).Assembly)
		_interpreter.References.Add(typeof(Font).Assembly)
		_interpreter.SetValue("inspect", inspect)

		prompt()

	def GetCurrentLine():
		line = Lines[-1][4:]
		print("")
		return line

	def SingleLineInputState():
		code = GetCurrentLine()

		if code[-1:] in ":", "\\":
			_state = InputState.Block
			_block.GetStringBuilder().Length = 0
			_block.WriteLine(code)
		else:
			_interpreter.LoopEval(code)

	def BlockInputState():
		code = GetCurrentLine()
		if 0 == len(code):
			_interpreter.LoopEval(_block.ToString())
			_state = InputState.SingleLine
		else:
			_block.WriteLine(code)

	override def OnKeyPress(args as KeyPressEventArgs):
		if Enter == args.KeyChar:
			try:
				(SingleLineInputState, BlockInputState)[_state]()
			except x:
				print(x)
			prompt()
			args.Handled = true
		super(args)

	def print(msg):
		AppendText("${msg}\r\n")

	def inspect([required] obj):
		f = Form(Text: "Object Inspector [${obj}]")
		f.Controls.Add(PropertyGrid(
							Dock: DockStyle.Fill,
							SelectedObject: obj,
							Font: Font))
		f.Show()
		return f

	def prompt():
		AppendText((">>> ", "... ")[_state])

	static def chr(value as int):
		return cast(IConvertible, value).ToChar(null)

#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

"""
Interactive Forms-based Console
"""

namespace booish.gui

import System
import booish
import System.Windows.Forms
import System.Drawing

class PromptBox(TextBox):

	static Enter = chr(13)

	enum InputState:
		SingleLine = 0
		Block = 1

	_state = InputState.SingleLine

	_block = System.IO.StringWriter()

	[getter(Interpreter)]
	_interpreter = InteractiveInterpreter(
								RememberLastValue: true,
								Print: print)

	def constructor():
		self.Dock = DockStyle.Fill
		self.Multiline = true
		self.AcceptsTab = true
		self.ScrollBars = ScrollBars.Vertical
		_interpreter.References.Add(typeof(TextBox).Assembly)
		_interpreter.References.Add(typeof(Font).Assembly)
		_interpreter.SetValue("inspect", inspect)

		prompt()

	def GetCurrentLine():
		line = Lines[-1][4:]
		print("")
		return line

	def SingleLineInputState():
		code = GetCurrentLine()

		if code[-1:] in ":", "\\":
			_state = InputState.Block
			_block.GetStringBuilder().Length = 0
			_block.WriteLine(code)
		else:
			_interpreter.LoopEval(code)

	def BlockInputState():
		code = GetCurrentLine()
		if 0 == len(code):
			_interpreter.LoopEval(_block.ToString())
			_state = InputState.SingleLine
		else:
			_block.WriteLine(code)

	override def OnKeyPress(args as KeyPressEventArgs):
		if Enter == args.KeyChar:
			try:
				(SingleLineInputState, BlockInputState)[_state]()
			except x:
				print(x)
			prompt()
			args.Handled = true
		super(args)

	def print(msg):
		AppendText("${msg}\r\n")

	def inspect([required] obj):
		f = Form(Text: "Object Inspector [${obj}]")
		f.Controls.Add(PropertyGrid(
							Dock: DockStyle.Fill,
							SelectedObject: obj,
							Font: Font,
							PropertySort: PropertySort.Alphabetical))
		f.Show()
		return f

	def prompt():
		AppendText((">>> ", "... ")[_state])

	static def chr(value as int):
		return cast(IConvertible, value).ToChar(null)


add nullity check on macro's Expand* argument

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

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

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

class MacroMacro(AbstractAstMacro):

	override def Expand(macro as MacroStatement) as Statement:
		if len(macro.Arguments) != 1 or macro.Arguments[0].NodeType != NodeType.ReferenceExpression:
			raise System.ArgumentException("Usage: macro <reference>", "reference")
		klass = CreateMacroType(macro)
		klass.LexicalInfo = macro.LexicalInfo
		#TODO: create macro as a nested class of the current type
		#      if not at module-level ? (=> macro namespaces)
		EnclosingModule(macro).Members.Add(klass)
		return null

	private def PascalCase(name as string) as string:
		return char.ToUpper(name[0]) + name[1:]

	private def CreateMacroType(macro as MacroStatement) as ClassDefinition:
		name = (macro.Arguments[0] as ReferenceExpression).Name
		newStyle = YieldFinder(macro).Found
		return CreateNewStyleMacroType(name, macro) if newStyle
		return CreateOldStyleMacroType(name, macro)

	#BOO-1077 style
	private def CreateNewStyleMacroType(name as string, macro as MacroStatement) as ClassDefinition:
		return [|
				class $(PascalCase(name) + "Macro") (Boo.Lang.Compiler.LexicalInfoPreservingGeneratorMacro):
					def constructor():
						super()
					def constructor(context as Boo.Lang.Compiler.CompilerContext):
						raise System.ArgumentNullException("context") if not context
						super(context)
					override protected def ExpandGeneratorImpl($name as Boo.Lang.Compiler.Ast.MacroStatement) as Boo.Lang.Compiler.Ast.Node*:
						raise System.ArgumentNullException($name) if not $(macro.Arguments[0])
						$(macro.Block)
					[System.Runtime.CompilerServices.CompilerGeneratedAttribute]
					override protected def ExpandImpl($name as Boo.Lang.Compiler.Ast.MacroStatement) as Boo.Lang.Compiler.Ast.Statement:
						raise System.ArgumentNullException($name) if not $(macro.Arguments[0])
						raise System.NotImplementedException("Boo installed version is older than the new macro syntax '${$(name)}' uses. Read BOO-1077 for more info.")
			|]

	private def CreateOldStyleMacroType(name as string, macro as MacroStatement) as ClassDefinition:
		return [|
				class $(PascalCase(name) + "Macro") (Boo.Lang.Compiler.LexicalInfoPreservingMacro):
					def constructor():
						super()
					def constructor(context as Boo.Lang.Compiler.CompilerContext):
						raise System.ArgumentNullException("context") if not context
						super(context)
					override protected def ExpandImpl($name as Boo.Lang.Compiler.Ast.MacroStatement) as Boo.Lang.Compiler.Ast.Statement:
						raise System.ArgumentNullException($name) if not $(macro.Arguments[0])
						$(macro.Block)
			|]

	private def EnclosingModule(macro as Node) as Module:
		return macro.GetAncestor(NodeType.Module)

	private class YieldFinder(DepthFirstVisitor):
		Found as bool:
			get:
				return _found
		_found = false

		def constructor(macro as MacroStatement):
			super.OnMacroStatement(macro)

		override def OnYieldStatement(node as YieldStatement):
			_found = true if _macroDepth == 1

		override def EnterMacroStatement(node as MacroStatement) as bool:
			_macroDepth++
			return true

		override def LeaveMacroStatement(node as MacroStatement):
			_macroDepth--

		_macroDepth = 0

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

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

class MacroMacro(AbstractAstMacro):

	override def Expand(macro as MacroStatement) as Statement:
		if len(macro.Arguments) != 1 or macro.Arguments[0].NodeType != NodeType.ReferenceExpression:
			raise System.ArgumentException("Usage: macro <reference>", "reference")
		klass = CreateMacroType(macro)
		klass.LexicalInfo = macro.LexicalInfo
		#TODO: create macro as a nested class of the current type
		#      if not at module-level ? (=> macro namespaces)
		EnclosingModule(macro).Members.Add(klass)
		return null

	private def PascalCase(name as string) as string:
		return char.ToUpper(name[0]) + name[1:]

	private def CreateMacroType(macro as MacroStatement) as ClassDefinition:
		name = (macro.Arguments[0] as ReferenceExpression).Name
		newStyle = YieldFinder(macro).Found
		return CreateNewStyleMacroType(name, macro) if newStyle
		return CreateOldStyleMacroType(name, macro)

	#BOO-1077 style
	private def CreateNewStyleMacroType(name as string, macro as MacroStatement) as ClassDefinition:
		return [|
				class $(PascalCase(name) + "Macro") (Boo.Lang.Compiler.LexicalInfoPreservingGeneratorMacro):
					def constructor():
						super()
					def constructor(context as Boo.Lang.Compiler.CompilerContext):
						super(context)
					override protected def ExpandGeneratorImpl($name as Boo.Lang.Compiler.Ast.MacroStatement) as Boo.Lang.Compiler.Ast.Node*:
						$(macro.Block)
					[System.Runtime.CompilerServices.CompilerGeneratedAttribute]
					override protected def ExpandImpl($name as Boo.Lang.Compiler.Ast.MacroStatement) as Boo.Lang.Compiler.Ast.Statement:
						raise System.NotImplementedException("Boo installed version is older than the new macro syntax '${$(name)}' uses. Read BOO-1077 for more info.")
			|]

	private def CreateOldStyleMacroType(name as string, macro as MacroStatement) as ClassDefinition:
		return [|
				class $(PascalCase(name) + "Macro") (Boo.Lang.Compiler.LexicalInfoPreservingMacro):
					def constructor():
						super()
					def constructor(context as Boo.Lang.Compiler.CompilerContext):
						super(context)
					override protected def ExpandImpl($name as Boo.Lang.Compiler.Ast.MacroStatement) as Boo.Lang.Compiler.Ast.Statement:
						$(macro.Block)
			|]

	private def EnclosingModule(macro as Node) as Module:
		return macro.GetAncestor(NodeType.Module)

	private class YieldFinder(DepthFirstVisitor):
		Found as bool:
			get:
				return _found
		_found = false

		def constructor(macro as MacroStatement):
			super.OnMacroStatement(macro)

		override def OnYieldStatement(node as YieldStatement):
			_found = true if _macroDepth == 1

		override def EnterMacroStatement(node as MacroStatement) as bool:
			_macroDepth++
			return true

		override def LeaveMacroStatement(node as MacroStatement):
			_macroDepth--

		_macroDepth = 0


PromptDialog wasn't in the BooExplorer namespace

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

namespace BooExplorer

import System.Windows.Forms
import System.Drawing
import System

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, 100)
		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

def test():
	dlg = PromptDialog(Text: "Boo Explorer", Message: "Hello? ")
	result = dlg.ShowDialog()
	print(result)
	if DialogResult.OK == result:
		print(dlg.Value)

//test()


Slight modification so it only zips up the one dll

import System
import System.IO

# Project Information
title            = "Stitch"
company          = ""
version_major    = 0
version_minor    = 1
version_build    = 0
version_revision = 0
repository_url   = ""
repository_type  = "git"

# Build Information
solution_folder  = "Source"
solution_file    = "Stitch.sln"
configuration    = "Release"
dotnet_version   = "4.0"
build_directory  = DirectoryInfo("Build/${configuration}")

# Targets
desc "Default target"
target default:
  try:
    call clean
    call binaries
    call compile
    call package
  except e:
    print e

target clean:
  rmdir("Build")

target binaries:
  exec("Tools\\NuGet\\NuGet.exe install ${solution_folder}\\Stitch\\packages.config -o Libraries")

target compile, (assemblyInfo):
  print "Compiling ${solution_file}"
  with msbuild():
    .file = Path.Combine(solution_folder, solution_file)
    .configuration = configuration
    .version = dotnet_version
    .properties = { 'OutDir': build_directory.FullName+"/" }

target package:
  package_dir = "Build/Package"
  mkdir package_dir
  cp("Build/${configuration}/Stitch.dll", package_dir + "/Stitch.dll")
  zip(package_dir, String.Format("Build/{4}-{0}.{1}.{2}.{3}.zip", version_major, version_minor, version_build, version_revision, title))
  rmdir package_dir

target getRevision:
  int.TryParse(env("build.vcs.number"), version_revision)
  if (version_revision > 0):
    return

  if (repository_type == "git"):
    exec("git describe --tags --long", { 'Output': '_summary.txt' })

    file = StreamReader("_summary.txt")
    line = file.ReadLine()
    file.Close()
    file.Dispose()

    rm("_summary.txt")

    parts = line.Split(char('-'))

    revision = 0
    if (int.TryParse(parts[1].Trim(),revision)):
      version_revision = revision

  if (version_revision > 0):
    return
  else:
    raise "Unable to determine repository revision"

desc "Create the assembly info"
target assemblyInfo, (getRevision):
  print "Generating SolutionVersion.cs"
  with generate_assembly_info():
    .file = "${solution_folder}/SolutionVersion.cs"
    .version = String.Format("{0}.{1}.{2}.{3}", version_major, version_minor, version_build, version_revision)
    .fileVersion = String.Format("{0}.{1}.{2}.{3}", version_major, version_minor, version_build, version_revision)
    .title = title
    .description  = String.Format("{0} is a product of {1}", title, company)
    .copyright = String.Format("Copyright {0} {1}", DateTime.Now.Year, company)
    .comVisible = false
    .companyName = company
    .productName = title

import System
import System.IO

# Project Information
title            = "Stitch"
company          = ""
version_major    = 0
version_minor    = 1
version_build    = 0
version_revision = 0
repository_url   = ""
repository_type  = "git"

# Build Information
solution_folder  = "Source"
solution_file    = "Stitch.sln"
configuration    = "Release"
dotnet_version   = "4.0"
build_directory  = DirectoryInfo("Build/${configuration}")
package_dir      = "Build/${configuration}"

# Targets
desc "Default target"
target default:
  try:
    call clean
    call binaries
    call compile
    call package
  except e:
    print e

target clean:
  rmdir("Build")

target binaries:
  exec("Tools\\NuGet\\NuGet.exe install ${solution_folder}\\Stitch\\packages.config -o Libraries")

target compile, (assemblyInfo):
  print "Compiling ${solution_file}"
  with msbuild():
    .file = Path.Combine(solution_folder, solution_file)
    .configuration = configuration
    .version = dotnet_version
    .properties = { 'OutDir': build_directory.FullName+"/" }

target package:
  zip(package_dir, String.Format("Build/{4}-{0}.{1}.{2}.{3}.zip", version_major, version_minor, version_build, version_revision, title))

target getRevision:
  int.TryParse(env("build.vcs.number"), version_revision)
  if (version_revision > 0):
    return

  if (repository_type == "git"):
    exec("git describe --tags --long", { 'Output': '_summary.txt' })

    file = StreamReader("_summary.txt")
    line = file.ReadLine()
    file.Close()
    file.Dispose()

    rm("_summary.txt")

    parts = line.Split(char('-'))

    revision = 0
    if (int.TryParse(parts[1].Trim(),revision)):
      version_revision = revision

  if (version_revision > 0):
    return
  else:
    raise "Unable to determine repository revision"

desc "Create the assembly info"
target assemblyInfo, (getRevision):
  print "Generating SolutionVersion.cs"
  with generate_assembly_info():
    .file = "${solution_folder}/SolutionVersion.cs"
    .version = String.Format("{0}.{1}.{2}.{3}", version_major, version_minor, version_build, version_revision)
    .fileVersion = String.Format("{0}.{1}.{2}.{3}", version_major, version_minor, version_build, version_revision)
    .title = title
    .description  = String.Format("{0} is a product of {1}", title, company)
    .copyright = String.Format("Copyright {0} {1}", DateTime.Now.Year, company)
    .comVisible = false
    .companyName = company
    .productName = title


[booish] show descriptions for namespaces and others

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

import System
import System.Reflection
import Boo.Lang.Compiler.TypeSystem
import Boo.Lang.PatternMatching

def describe(obj):
	type = (obj as Type) or obj.GetType()
	for line in DescriptionFormatter("    ").GenerateFormattedLinesFor(type):
		Console.WriteLine(line)

class DescriptionFormatter:

	indent as string

	def constructor(indent as string):
		self.indent = indent

	def GenerateFormattedLinesFor(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())

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

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

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

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

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

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

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

def DescribeEntity(entity as IEntity):
	match entity:
		case method = ExternalMethod(MethodInfo: MethodInfo()):
			return DescribeMethod(method.MethodInfo)
		case field = ExternalField():
			return DescribeField(field.FieldInfo)
		case property = ExternalProperty():
			return DescribeProperty(property.PropertyInfo)
		case e = ExternalEvent():
			return DescribeEvent(e.EventInfo)
		otherwise:
			return entity.ToString()

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

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

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

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

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

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

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

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

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

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

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

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 GenericTypeNameFor(type) if type.IsGenericType
	return FullNameOf(type)

def FullNameOf(type as System.Type):
	fullName = type.FullName
	if string.IsNullOrEmpty(fullName):
		return type.Name
	return fullName

def GenericTypeNameFor(type as System.Type):
	fullName = FullNameOf(type.GetGenericTypeDefinition())
	simpleName = fullName[:fullName.IndexOf('`')]
	parameterNames = (GetBooTypeName(t) for t in type.GetGenericArguments())
	return "$simpleName[of $(join(parameterNames, ', '))]"

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

import System
import System.Reflection
import Boo.Lang.Compiler.TypeSystem
import Boo.Lang.PatternMatching

def describe(obj):
	type = (obj as Type) or obj.GetType()
	for line in DescriptionFormatter("    ").GenerateFormattedLinesFor(type):
		Console.WriteLine(line)

class DescriptionFormatter:

	indent as string

	def constructor(indent as string):
		self.indent = indent

	def GenerateFormattedLinesFor(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())

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

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

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

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

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

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

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

def DescribeEntity(entity as IEntity):
	match entity:
		case method = ExternalMethod(MethodInfo: MethodInfo()):
			return DescribeMethod(method.MethodInfo)
		case field = ExternalField():
			return DescribeField(field.FieldInfo)
		case property = ExternalProperty():
			return DescribeProperty(property.PropertyInfo)
		case e = ExternalEvent():
			return DescribeEvent(e.EventInfo)
		otherwise:
			entity.ToString()

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

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

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

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

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

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

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

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

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

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

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

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 GenericTypeNameFor(type) if type.IsGenericType
	return FullNameOf(type)

def FullNameOf(type as System.Type):
	fullName = type.FullName
	if string.IsNullOrEmpty(fullName):
		return type.Name
	return fullName

def GenericTypeNameFor(type as System.Type):
	fullName = FullNameOf(type.GetGenericTypeDefinition())
	simpleName = fullName[:fullName.IndexOf('`')]
	parameterNames = (GetBooTypeName(t) for t in type.GetGenericArguments())
	return "$simpleName[of $(join(parameterNames, ', '))]"