EnumMember support

EnumMember support

git-svn-id: c3b8989232654601f1acea7403f771adaaa44a81@1375 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.
//
// BooBinding 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 BooBinding; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#endregion

namespace BooBinding.CodeCompletion

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

class Using(AbstractUsing):
	pass

class Visitor(AbstractVisitorCompilerStep):
	[Getter(Cu)]
	_cu as CompilationUnit = CompilationUnit()

	_currentClass as Stack = Stack()
	_firstModule = true

	override def Run():
		print "RUN"
		try:
			Visit(CompileUnit)
		except e:
			print e.ToString()
			//msg as IMessageService = ServiceManager.Services.GetService(typeof(IMessageService))
			//msg.ShowError(e)

	private def GetModifier(m as AST.TypeMember) as ModifierEnum:
		r = ModifierEnum.None
		r = r | ModifierEnum.Public    if m.IsPublic
		r = r | ModifierEnum.Protected if m.IsProtected
		r = r | ModifierEnum.Private   if m.IsPrivate
		r = r | ModifierEnum.Internal  if m.IsInternal

		r = r | ModifierEnum.Static   if m.IsStatic
		r = r | ModifierEnum.Virtual  if m.IsModifierSet(AST.TypeMemberModifiers.Virtual)
		r = r | ModifierEnum.Abstract if m.IsModifierSet(AST.TypeMemberModifiers.Abstract)
		r = r | ModifierEnum.Override if m.IsModifierSet(AST.TypeMemberModifiers.Override)

		r = r | ModifierEnum.Final if m.IsFinal
		return r

	[Property(LineLength)]
	_lineLength as (int)

	private def GetLineEnd(line as int) as int:
		return 0 if _lineLength == null or line < 1 or line > _lineLength.Length
		return _lineLength[line - 1] + 1

	private def GetRegion(m as AST.Node):
		l = m.LexicalInfo
		return null if (l.Line < 0)
		return DefaultRegion(l.Line, 0 /*l.Column*/, l.Line, GetLineEnd(l.Line))

	private def GetClientRegion(m as AST.Node) as DefaultRegion:
		l = m.LexicalInfo
		return null if l.Line < 0
		l2 as AST.SourceLocation = null
		if m isa AST.Method:
			l2 = cast(AST.Method, m).Body.EndSourceLocation
		elif m isa AST.Property:
			p as AST.Property = m
			if p.Getter != null and p.Getter.Body != null:
				l2 = cast(AST.Property, m).Getter.Body.EndSourceLocation
				if p.Setter != null and p.Setter.Body != null:
					l3 = cast(AST.Property, m).Setter.Body.EndSourceLocation
					l2 = l3 if l3.Line > l2.Line
			elif p.Setter != null and p.Setter.Body != null:
				l2 = cast(AST.Property, m).Setter.Body.EndSourceLocation
		else:
			l2 = m.EndSourceLocation
		return null if l2 == null or l2.Line < 0 or l.Line == l2.Line
		// TODO: use l.Column / l2.Column when the tab-bug has been fixed
		return DefaultRegion(l.Line, GetLineEnd(l.Line), l2.Line, GetLineEnd(l2.Line))

	override def OnImport(p as AST.Import):
		u = Using()
		if p.Alias == null:
			u.Usings.Add(p.Namespace)
		else:
			u.Aliases[p.Alias.Name] = p.Namespace
		_cu.Usings.Add(u)

	override def OnCallableDefinition(node as AST.CallableDefinition):
		print "OnCallableDefinition: ${node.FullName}"
		region = GetRegion(node)
		modifier = GetModifier(node)
		c = Class(_cu, ClassType.Delegate, modifier, region)
		c.BaseTypes.Add('System.Delegate')
		c.FullyQualifiedName = node.FullName
		if _currentClass.Count > 0:
			cast(Class, _currentClass.Peek()).InnerClasses.Add(c)
		else:
			_cu.Classes.Add(c)
		invokeMethod = Method('Invoke', ReturnType(node.ReturnType), modifier, region, region)
		invokeMethod.Parameters = GetParameters(node.Parameters)
		c.Methods.Add(invokeMethod)

	override def EnterClassDefinition(node as AST.ClassDefinition):
		EnterTypeDefinition(node, ClassType.Class)
		return super(node)

	override def EnterInterfaceDefinition(node as AST.InterfaceDefinition):
		EnterTypeDefinition(node, ClassType.Interface)
		return super(node)

	override def EnterEnumDefinition(node as AST.EnumDefinition):
		EnterTypeDefinition(node, ClassType.Enum)
		return super(node)

	override def EnterModule(node as AST.Module):
		EnterTypeDefinition(node, ClassType.Class) unless _firstModule
		_firstModule = false
		return super(node)

	private def EnterTypeDefinition(node as AST.TypeDefinition, classType as ClassType):
		try:
			print "Enter ${node.GetType().Name} (${node.FullName})"
			region = GetClientRegion(node)
			modifier = GetModifier(node)
			c = Class(_cu, classType, modifier, region)
			c.FullyQualifiedName = node.FullName
			c.Documentation = node.Documentation
			if _currentClass.Count > 0:
				cast(Class, _currentClass.Peek()).InnerClasses.Add(c)
			else:
				_cu.Classes.Add(c)
			if node.BaseTypes != null:
				for r as AST.SimpleTypeReference in node.BaseTypes:
					c.BaseTypes.Add(r.Name)
			_currentClass.Push(c)
		except ex:
			print ex.ToString()
			raise

	override def LeaveClassDefinition(node as AST.ClassDefinition):
		LeaveTypeDefinition(node)
		super(node)

	override def LeaveInterfaceDefinition(node as AST.InterfaceDefinition):
		LeaveTypeDefinition(node)
		super(node)

	override def LeaveEnumDefinition(node as AST.EnumDefinition):
		LeaveTypeDefinition(node)
		super(node)

	override def LeaveModule(node as AST.Module):
		LeaveTypeDefinition(node) unless _currentClass.Count == 0
		super(node)

	private def LeaveTypeDefinition(node as AST.TypeDefinition):
		c as Class = _currentClass.Pop()
		print "Leave ${node.GetType().Name} ${node.FullName} (Class = ${c.FullyQualifiedName})"
		c.UpdateModifier()

	override def OnMethod(node as AST.Method):
		try:
			print "Method: ${node.FullName}"
			method = Method(node.Name, ReturnType(node.ReturnType), GetModifier(node), GetRegion(node), GetClientRegion(node))
			method.Parameters = GetParameters(node.Parameters)
			method.Node = node
			method.Documentation = node.Documentation
			cast(Class, _currentClass.Peek()).Methods.Add(method)
		except ex:
			print ex.ToString()
			raise

	private def GetParameters(params as AST.ParameterDeclarationCollection):
		parameters = ParameterCollection()
		return parameters if params == null
		for par as AST.ParameterDeclaration in params:
			parameters.Add(Parameter(par.Name, ReturnType(par.Type)))
		return parameters

	override def OnConstructor(node as AST.Constructor):
		return if node.Body.Statements.Count == 0
		ctor = Constructor(GetModifier(node), GetRegion(node), GetClientRegion(node))
		ctor.Parameters = GetParameters(node.Parameters)
		ctor.Node = node
		ctor.Documentation = node.Documentation
		cast(Class, _currentClass.Peek()).Methods.Add(ctor)

	override def OnEnumMember(node as AST.EnumMember):
		try:
			c as Class = _currentClass.Peek()
			field = Field(ReturnType(c), node.Name, GetModifier(node), GetRegion(node))
			field.Documentation = node.Documentation
			field.SetModifiers(ModifierEnum.Const | ModifierEnum.SpecialName)
			c.Fields.Add(field)
		except x:
			print x
			raise

	override def OnField(node as AST.Field):
		try:
			print "Field ${node.Name}"
			c as Class = _currentClass.Peek()
			field = Field(ReturnType(node.Type), node.Name, GetModifier(node), GetRegion(node))
			field.Documentation = node.Documentation
			c.Fields.Add(field)
		except ex:
			print ex.ToString()
			raise

	override def OnProperty(node as AST.Property):
		try:
			print "Property ${node.Name}"
			property = Property(node.Name, ReturnType(node.Type), GetModifier(node), GetRegion(node), GetClientRegion(node))
			property.Documentation = node.Documentation
			property.Node = node
			cast(Class, _currentClass.Peek()).Properties.Add(property)
		except ex:
			print ex.ToString()
			raise

	/*
	// TODO: Event Declaration
	override def Visit(eventDeclaration as AST.EventDeclaration, data as object) as object:
		region as DefaultRegion = GetRegion(eventDeclaration.StartLocation, eventDeclaration.EndLocation)
		bodyRegion as DefaultRegion = GetRegion(eventDeclaration.BodyStart, eventDeclaration.BodyEnd)
		type as ReturnType = ReturnType(eventDeclaration.TypeReference)
		c as Class = _currentClass.Peek()
		e as Event = null
		if eventDeclaration.VariableDeclarators != null:
			for varDecl as ICSharpCode.SharpRefactory.Parser.AST.VariableDeclaration in eventDeclaration.VariableDeclarators:
				e = Event(varDecl.Name, type, eventDeclaration.Modifier, region, bodyRegion)
				c.Events.Add(e)

		else:
			e = Event(eventDeclaration.Name, type, eventDeclaration.Modifier, region, bodyRegion)
			c.Events.Add(e)

		return null

	// TODO: Detect indexer method and add it as Indexer
	override def Visit(indexerDeclaration as AST.IndexerDeclaration, data as object) as object:
		region as DefaultRegion = GetRegion(indexerDeclaration.StartLocation, indexerDeclaration.EndLocation)
		bodyRegion as DefaultRegion = GetRegion(indexerDeclaration.BodyStart, indexerDeclaration.BodyEnd)
		parameters as ParameterCollection = ParameterCollection()
		i as Indexer = Indexer(ReturnType(indexerDeclaration.TypeReference), parameters, indexerDeclaration.Modifier, region, bodyRegion)
		if indexerDeclaration.Parameters != null:
			for par as AST.ParameterDeclarationExpression in indexerDeclaration.Parameters:
				parType as ReturnType = ReturnType(par.TypeReference)
				p as Parameter = Parameter(par.ParameterName, parType)
				parameters.Add(p)

		c as Class = _currentClass.Peek()
		c.Indexer.Add(i)
		return null
	*/

#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.
//
// BooBinding 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 BooBinding; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#endregion

namespace BooBinding.CodeCompletion

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

class Using(AbstractUsing):
	pass

class Visitor(AbstractVisitorCompilerStep):
	[Getter(Cu)]
	_cu as CompilationUnit = CompilationUnit()

	_currentClass as Stack = Stack()
	_firstModule = true

	override def Run():
		print "RUN"
		try:
			Visit(CompileUnit)
		except e:
			print e.ToString()
			//msg as IMessageService = ServiceManager.Services.GetService(typeof(IMessageService))
			//msg.ShowError(e)

	private def GetModifier(m as AST.TypeMember) as ModifierEnum:
		r = ModifierEnum.None
		r = r | ModifierEnum.Public    if m.IsPublic
		r = r | ModifierEnum.Protected if m.IsProtected
		r = r | ModifierEnum.Private   if m.IsPrivate
		r = r | ModifierEnum.Internal  if m.IsInternal

		r = r | ModifierEnum.Static   if m.IsStatic
		r = r | ModifierEnum.Virtual  if m.IsModifierSet(AST.TypeMemberModifiers.Virtual)
		r = r | ModifierEnum.Abstract if m.IsModifierSet(AST.TypeMemberModifiers.Abstract)
		r = r | ModifierEnum.Override if m.IsModifierSet(AST.TypeMemberModifiers.Override)

		r = r | ModifierEnum.Final if m.IsFinal
		return r

	[Property(LineLength)]
	_lineLength as (int)

	private def GetLineEnd(line as int) as int:
		return 0 if _lineLength == null or line < 1 or line > _lineLength.Length
		return _lineLength[line - 1] + 1

	private def GetRegion(m as AST.Node):
		l = m.LexicalInfo
		return null if (l.Line < 0)
		return DefaultRegion(l.Line, 0 /*l.Column*/, l.Line, GetLineEnd(l.Line))

	private def GetClientRegion(m as AST.Node) as DefaultRegion:
		l = m.LexicalInfo
		return null if l.Line < 0
		l2 as AST.SourceLocation = null
		if m isa AST.Method:
			l2 = cast(AST.Method, m).Body.EndSourceLocation
		elif m isa AST.Property:
			p as AST.Property = m
			if p.Getter != null and p.Getter.Body != null:
				l2 = cast(AST.Property, m).Getter.Body.EndSourceLocation
				if p.Setter != null and p.Setter.Body != null:
					l3 = cast(AST.Property, m).Setter.Body.EndSourceLocation
					l2 = l3 if l3.Line > l2.Line
			elif p.Setter != null and p.Setter.Body != null:
				l2 = cast(AST.Property, m).Setter.Body.EndSourceLocation
		else:
			l2 = m.EndSourceLocation
		return null if l2 == null or l2.Line < 0 or l.Line == l2.Line
		// TODO: use l.Column / l2.Column when the tab-bug has been fixed
		return DefaultRegion(l.Line, GetLineEnd(l.Line), l2.Line, GetLineEnd(l2.Line))

	override def OnImport(p as AST.Import):
		u = Using()
		if p.Alias == null:
			u.Usings.Add(p.Namespace)
		else:
			u.Aliases[p.Alias.Name] = p.Namespace
		_cu.Usings.Add(u)

	override def OnCallableDefinition(node as AST.CallableDefinition):
		print "OnCallableDefinition: ${node.FullName}"
		region = GetRegion(node)
		modifier = GetModifier(node)
		c = Class(_cu, ClassType.Delegate, modifier, region)
		c.BaseTypes.Add('System.Delegate')
		c.FullyQualifiedName = node.FullName
		if _currentClass.Count > 0:
			cast(Class, _currentClass.Peek()).InnerClasses.Add(c)
		else:
			_cu.Classes.Add(c)
		invokeMethod = Method('Invoke', ReturnType(node.ReturnType), modifier, region, region)
		invokeMethod.Parameters = GetParameters(node.Parameters)
		c.Methods.Add(invokeMethod)

	override def EnterClassDefinition(node as AST.ClassDefinition):
		EnterTypeDefinition(node, ClassType.Class)
		return super(node)

	override def EnterInterfaceDefinition(node as AST.InterfaceDefinition):
		EnterTypeDefinition(node, ClassType.Interface)
		return super(node)

	override def EnterEnumDefinition(node as AST.EnumDefinition):
		EnterTypeDefinition(node, ClassType.Enum)
		return super(node)

	override def EnterModule(node as AST.Module):
		EnterTypeDefinition(node, ClassType.Class) unless _firstModule
		_firstModule = false
		return super(node)

	private def EnterTypeDefinition(node as AST.TypeDefinition, classType as ClassType):
		try:
			print "Enter ${node.GetType().Name} (${node.FullName})"
			region = GetClientRegion(node)
			modifier = GetModifier(node)
			c = Class(_cu, classType, modifier, region)
			c.FullyQualifiedName = node.FullName
			c.Documentation = node.Documentation
			if _currentClass.Count > 0:
				cast(Class, _currentClass.Peek()).InnerClasses.Add(c)
			else:
				_cu.Classes.Add(c)
			if node.BaseTypes != null:
				for r as AST.SimpleTypeReference in node.BaseTypes:
					c.BaseTypes.Add(r.Name)
			_currentClass.Push(c)
		except ex:
			print ex.ToString()
			raise

	override def LeaveClassDefinition(node as AST.ClassDefinition):
		LeaveTypeDefinition(node)
		super(node)

	override def LeaveInterfaceDefinition(node as AST.InterfaceDefinition):
		LeaveTypeDefinition(node)
		super(node)

	override def LeaveEnumDefinition(node as AST.EnumDefinition):
		LeaveTypeDefinition(node)
		super(node)

	override def LeaveModule(node as AST.Module):
		LeaveTypeDefinition(node) unless _currentClass.Count == 0
		super(node)

	private def LeaveTypeDefinition(node as AST.TypeDefinition):
		c as Class = _currentClass.Pop()
		print "Leave ${node.GetType().Name} ${node.FullName} (Class = ${c.FullyQualifiedName})"
		c.UpdateModifier()

	override def OnMethod(node as AST.Method):
		try:
			print "Method: ${node.FullName}"
			method = Method(node.Name, ReturnType(node.ReturnType), GetModifier(node), GetRegion(node), GetClientRegion(node))
			method.Parameters = GetParameters(node.Parameters)
			method.Node = node
			method.Documentation = node.Documentation
			cast(Class, _currentClass.Peek()).Methods.Add(method)
		except ex:
			print ex.ToString()
			raise

	private def GetParameters(params as AST.ParameterDeclarationCollection):
		parameters = ParameterCollection()
		return parameters if params == null
		for par as AST.ParameterDeclaration in params:
			parameters.Add(Parameter(par.Name, ReturnType(par.Type)))
		return parameters

	override def OnConstructor(node as AST.Constructor):
		return if node.Body.Statements.Count == 0
		ctor = Constructor(GetModifier(node), GetRegion(node), GetClientRegion(node))
		ctor.Parameters = GetParameters(node.Parameters)
		ctor.Node = node
		ctor.Documentation = node.Documentation
		cast(Class, _currentClass.Peek()).Methods.Add(ctor)

	override def OnField(node as AST.Field):
		try:
			print "Field ${node.Name}"
			c as Class = _currentClass.Peek()
			field = Field(ReturnType(node.Type), node.Name, GetModifier(node), GetRegion(node))
			field.Documentation = node.Documentation
			if c.ClassType == ClassType.Enum:
				field.SetModifiers(ModifierEnum.Const | ModifierEnum.SpecialName)
			c.Fields.Add(field)
		except ex:
			print ex.ToString()
			raise

	override def OnProperty(node as AST.Property):
		try:
			print "Property ${node.Name}"
			property = Property(node.Name, ReturnType(node.Type), GetModifier(node), GetRegion(node), GetClientRegion(node))
			property.Documentation = node.Documentation
			property.Node = node
			cast(Class, _currentClass.Peek()).Properties.Add(property)
		except ex:
			print ex.ToString()
			raise

	/*
	// TODO: Event Declaration
	override def Visit(eventDeclaration as AST.EventDeclaration, data as object) as object:
		region as DefaultRegion = GetRegion(eventDeclaration.StartLocation, eventDeclaration.EndLocation)
		bodyRegion as DefaultRegion = GetRegion(eventDeclaration.BodyStart, eventDeclaration.BodyEnd)
		type as ReturnType = ReturnType(eventDeclaration.TypeReference)
		c as Class = _currentClass.Peek()
		e as Event = null
		if eventDeclaration.VariableDeclarators != null:
			for varDecl as ICSharpCode.SharpRefactory.Parser.AST.VariableDeclaration in eventDeclaration.VariableDeclarators:
				e = Event(varDecl.Name, type, eventDeclaration.Modifier, region, bodyRegion)
				c.Events.Add(e)

		else:
			e = Event(eventDeclaration.Name, type, eventDeclaration.Modifier, region, bodyRegion)
			c.Events.Add(e)

		return null

	// TODO: Detect indexer method and add it as Indexer
	override def Visit(indexerDeclaration as AST.IndexerDeclaration, data as object) as object:
		region as DefaultRegion = GetRegion(indexerDeclaration.StartLocation, indexerDeclaration.EndLocation)
		bodyRegion as DefaultRegion = GetRegion(indexerDeclaration.BodyStart, indexerDeclaration.BodyEnd)
		parameters as ParameterCollection = ParameterCollection()
		i as Indexer = Indexer(ReturnType(indexerDeclaration.TypeReference), parameters, indexerDeclaration.Modifier, region, bodyRegion)
		if indexerDeclaration.Parameters != null:
			for par as AST.ParameterDeclarationExpression in indexerDeclaration.Parameters:
				parType as ReturnType = ReturnType(par.TypeReference)
				p as Parameter = Parameter(par.ParameterName, parType)
				parameters.Add(p)

		c as Class = _currentClass.Peek()
		c.Indexer.Add(i)
		return null
	*/


test case for an object pattern matching on a string field

namespace Boo.OMeta.Tests

import Boo.OMeta
import Boo.Lang.PatternMatching
import Boo.Adt
import NUnit.Framework

data Exp = Const(value as int) | Sum(left as Exp, right as Exp)

[TestFixture]
class ObjectMatchingTest:

	[Test]
	def PropertyPatternMatching():
		ometa ConstList:
			option ParseTree
			parse = ++(Const(value) ^ value)

		match ConstList().parse(OMetaInput.For([Const(1), Const(42)])):
			case SuccessfulMatch(Input, Value):
				assert Input.IsEmpty
				Assert.AreEqual([1, 42], Value)

	[Test]
	def PropertyParsing():
		ometa Evaluator:
			eval = const | sum
			const = Const(value) ^ value
			sum = Sum(left: eval >> l as int, right: eval >> r as int) ^ (l + r)

		match Evaluator().eval(OMetaInput.Singleton(Sum(Const(21), Sum(Const(11), Const(10))))):
			case SuccessfulMatch(Input, Value):
				assert Input.IsEmpty
				Assert.AreEqual(42, Value)

	[Test]
	def StringFieldMatchingWithCustomRule():

		data Person(name)

		ometa JohnOrPaulMatcher:
			john = "John "
			paul = "Paul "
			john_or_paul = john | paul
			match = Person(name: string[john_or_paul] >> _)
			string[rule] = $(string_matching(rule, input, context))

		def john_or_paul(person):
			return JohnOrPaulMatcher().match(OMetaInput.Singleton(person))

		match john_or_paul(Person("John Stewart")):
			case SuccessfulMatch(Input):
				assert Input.IsEmpty

		match john_or_paul(Person("Paul Jones")):
			case SuccessfulMatch(Input):
				assert Input.IsEmpty

		match john_or_paul(Person("Eric")):
			case FailedMatch(Input):
				assert Input.Head == "Eric"

		match john_or_paul(Person(42)):
			case FailedMatch(Input, Failure: ObjectPatternFailure(Pattern)):
				assert Input.Head == 42
				assert Pattern == "'42' is not a string"

	static def string_matching(rule as string, input as OMetaInput, context as OMetaEvaluationContext):
		match input.Head:
			case s = string():
				match context.Eval(rule, OMetaInput.For(s)):
					case SuccessfulMatch():
						return SuccessfulMatch(input.Tail, input.Head)
					case FailedMatch(Failure):
						return FailedMatch(input, Failure)
			otherwise:
				return FailedMatch(input, ObjectPatternFailure("'$(input.Head)' is not a string"))


okay, let's be less academic then ;)

okay, let's be less academic then ;)

git-svn-id: fcce7d6ce0ac15bf24f68871b5145d46fc068279@2509 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
				Console.Write(", ") if i > 0
				if i > 20: #TODO: maxcandidates pref + paging?
					Console.Write("... (too much candidates)")
					break
				if i == _selectedSuggestionIndex:
					Console.ForegroundColor = _selectedSuggestionColor
				Console.Write(l)
				i++

		elif _suggestions isa (IEntity):
			for e in _suggestions as (IEntity):
				Console.ForegroundColor = _suggestionsColor
				Console.Write(", ") if i > 0
				if i > 20: #TODO: maxcandidates pref + paging?
					Console.Write("... (too much candidates)")
					break
				if i == _selectedSuggestionIndex:
					Console.ForegroundColor = _selectedSuggestionColor
				Console.Write(DescribeEntity(e))
				i++

		Console.ResetColor()
		#Console.CursorTop = cursorTop
		Console.Write(Environment.NewLine)
		ConsolePrintPrompt()
		Console.Write(_line.ToString())
		Console.CursorLeft = cursorLeft

	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 > 0:
						cx = Console.CursorLeft-len(CurrentPrompt)
						if cx < _line.Length:
							_line.Remove(cx, 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 (or just press the damn escape key!)

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

	def DisplayGoodbye():	// booish is friendly
		Console.ForegroundColor = _interpreterColor
		print ""
		print "All your boo are belong to us!"
		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
				Console.Write(", ") if i > 0
				if i > 20: #TODO: maxcandidates pref + paging?
					Console.Write("... (too much candidates)")
					break
				if i == _selectedSuggestionIndex:
					Console.ForegroundColor = _selectedSuggestionColor
				Console.Write(l)
				i++

		elif _suggestions isa (IEntity):
			for e in _suggestions as (IEntity):
				Console.ForegroundColor = _suggestionsColor
				Console.Write(", ") if i > 0
				if i > 20: #TODO: maxcandidates pref + paging?
					Console.Write("... (too much candidates)")
					break
				if i == _selectedSuggestionIndex:
					Console.ForegroundColor = _selectedSuggestionColor
				Console.Write(DescribeEntity(e))
				i++

		Console.ResetColor()
		#Console.CursorTop = cursorTop
		Console.Write(Environment.NewLine)
		ConsolePrintPrompt()
		Console.Write(_line.ToString())
		Console.CursorLeft = cursorLeft

	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 > 0:
						cx = Console.CursorLeft-len(CurrentPrompt)
						if cx < _line.Length:
							_line.Remove(cx, 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 (escape key works too)

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

	def DisplayGoodbye():	// booish is friendly
		Console.ForegroundColor = _interpreterColor
		print ""
		print "All your boo are belong to us!"
		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!"


fix broken brail build

fix broken brail build

git-svn-id: bab0c2de90ee11a20376049c2cdee62584491f34@2544 73e77b4c-caa6-f847-a29a-24ab75ae54b6

// Copyright 2004-2005 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
namespace Castle.MonoRail.Views.Brail

import System
import System.IO
import System.Collections
import Castle.MonoRail.Framework

# a class to output the error to the user in a nice way
class OutputError(BrailBase):
	exception as Exception

	def constructor(viewEngine as BooViewEngine, output as TextWriter, context as IRailsEngineContext,controller as Controller, exception as Exception):
		super(viewEngine, output, context, controller)
		self.exception = exception

	override def Run():
		outputStream.Write("<title>Brail error!</title>
")
		outputStream.Write(exception.ToString())
		outputStream.Write("
")

	override ScriptDirectory as string:
		get:
			return Environment.CurrentDirectory

// Copyright 2004-2005 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
namespace Castle.MonoRail.Views.Brail

import System
import System.IO
import System.Collections
import Castle.MonoRail.Framework

# a class to output the error to the user in a nice way
class OutputError(BrailBase):
	exception as Exception

	def constructor(viewEngine as BooViewEngine, output as TextWriter, context as IRailsEngineContext,controller as Controller, exception as Exception):
		super(viewEngine, output, context, controller)
		self.exception = exception

	override def Run():
		outputStream.Write("<title>Brail error!</title>
")
		outputStream.Write(exception.ToString())
		outputStream.Write("
")

	override ScriptDirectory:
		get:
			return Environment.CurrentDirectory