BOO-968: Makes subclassing the required attribute more friendly

git-svn-id: fcce7d6ce0ac15bf24f68871b5145d46fc068279@2889 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.Extensions

import System
import Boo.Lang.Compiler.Ast

class RequiredAttribute(Boo.Lang.Compiler.AbstractAstAttribute):

	_condition as Expression
	_message as Expression

	def constructor():
		pass

	def constructor(condition as Expression):
		if condition is null: raise ArgumentNullException('condition')
		_condition = condition

	def constructor(condition as Expression, message as Expression):
		self(condition)
		_message = message

	override def Apply(node as Boo.Lang.Compiler.Ast.Node):

		parameter = node as ParameterDeclaration
		if parameter is not null:
			method as Method = TargetMethod(parameter)
			method.Body.Insert(0, BuildAssertion(parameter.Name))
			return

		property = node as Property
		CheckProperty property
		property.Setter.Body.Insert(0, BuildAssertion('value'))

	private def TargetMethod(parameter as ParameterDeclaration):
		method = parameter.ParentNode as Method
		return method if method is not null

		property = parameter.ParentNode as Property
		CheckProperty property
		return property.Setter

	private def CheckProperty(property as Property):
		if property is null or property.Setter is null:
			InvalidNodeForAttribute('ParameterDeclaration or Property')

	protected virtual def BuildAssertion(parameterName as string):
		value = ReferenceExpression(parameterName)
		if _condition is null:
			return [|
				raise System.ArgumentNullException($parameterName) if $value is null
			|]

		if _message is null:
			_message = StringLiteralExpression("Expected: " + _condition.ToCodeString())

		if _message isa StringLiteralExpression:
			return [|
				raise System.ArgumentException($_message, $parameterName) unless $_condition
			|]
		else:
			return [|
				raise System.ArgumentException(($_message).ToString(), $parameterName) unless $_condition
			|]

#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.Extensions

import System
import Boo.Lang.Compiler.Ast

class RequiredAttribute(Boo.Lang.Compiler.AbstractAstAttribute):

	_condition as Expression
	_message as Expression

	def constructor():
		pass

	def constructor(condition as Expression):
		if condition is null: raise ArgumentNullException('condition')
		_condition = condition

	def constructor(condition as Expression, message as Expression):
		self(condition)
		_message = message

	override def Apply(node as Boo.Lang.Compiler.Ast.Node):

		parameter = node as ParameterDeclaration
		if parameter is not null:
			method as Method = TargetMethod(parameter)
			method.Body.Insert(0, BuildAssertion(parameter.Name))
			return

		property = node as Property
		CheckProperty property
		property.Setter.Body.Insert(0, BuildAssertion('value'))

	private def TargetMethod(parameter as ParameterDeclaration):
		method = parameter.ParentNode as Method
		return method if method is not null

		property = parameter.ParentNode as Property
		CheckProperty property
		return property.Setter

	private def CheckProperty(property as Property):
		if property is null or property.Setter is null:
			InvalidNodeForAttribute('ParameterDeclaration or Property')

	private def BuildAssertion(parameterName as string):
		value = ReferenceExpression(parameterName)
		if _condition is null:
			return [|
				raise System.ArgumentNullException($parameterName) if $value is null
			|]

		if _message is null:
			_message = StringLiteralExpression("Expected: " + _condition.ToCodeString())

		if _message isa StringLiteralExpression:
			return [|
				raise System.ArgumentException($_message, $parameterName) unless $_condition
			|]
		else:
			return [|
				raise System.ArgumentException(($_message).ToString(), $parameterName) unless $_condition
			|]


dump parsing errors

dump parsing errors

namespace Boo.MonoDevelop.ProjectModel

import System
import System.IO

import MonoDevelop.Projects.Dom
import MonoDevelop.Projects.Dom.Parser

import Boo.Lang.Compiler as BLC

class BooParser(AbstractParser):

	def constructor():
		super("Boo", BooMimeType)

	override def CanParse(fileName as string):
		return Path.GetExtension(fileName).ToLower() == ".boo"

	override def Parse(dom as ProjectDom, fileName as string, content as string):

		document = ParsedDocument(fileName)
		document.CompilationUnit = CompilationUnit(fileName)

		try:
			result = ParseBooText(fileName, content)
			result.CompileUnit.Accept(DomConversionVisitor(document.CompilationUnit))
			if len(result.Errors): Console.Error.WriteLine(result.Errors.ToString(true))
		except e:
			Console.Error.WriteLine(e)

		return document

	override def CreateResolver(dom as ProjectDom, editor, fileName as string):
		doc = cast(MonoDevelop.Ide.Gui.Document, editor)
		return BooResolver(dom, doc.CompilationUnit, fileName)

def ParseBooText(fileName as string, text as string):

	pipeline = BLC.Pipelines.Parse()
	pipeline.Add(BLC.Steps.InitializeTypeSystemServices())
	pipeline.Add(BLC.Steps.IntroduceModuleClasses())

	compiler = BLC.BooCompiler()
	compiler.Parameters.Pipeline = pipeline
	compiler.Parameters.Input.Add(BLC.IO.StringInput(fileName, text))
	return compiler.Run()

namespace Boo.MonoDevelop.ProjectModel

import System.IO

import MonoDevelop.Projects.Dom
import MonoDevelop.Projects.Dom.Parser

import Boo.Lang.Compiler as BLC

class BooParser(AbstractParser):

	def constructor():
		super("Boo", BooMimeType)

	override def CanParse(fileName as string):
		return Path.GetExtension(fileName).ToLower() == ".boo"

	override def Parse(dom as ProjectDom, fileName as string, content as string):
		result = ParseBooText(fileName, content)

		document = ParsedDocument(fileName)
		document.CompilationUnit = CompilationUnit(fileName)

		result.CompileUnit.Accept(DomConversionVisitor(document.CompilationUnit))

		return document

	override def CreateResolver(dom as ProjectDom, editor, fileName as string):
		doc = cast(MonoDevelop.Ide.Gui.Document, editor)
		return BooResolver(dom, doc.CompilationUnit, fileName)

def ParseBooText(fileName as string, text as string):

	pipeline = BLC.Pipelines.Parse()
	pipeline.Add(BLC.Steps.InitializeTypeSystemServices())
	pipeline.Add(BLC.Steps.IntroduceModuleClasses())

	compiler = BLC.BooCompiler()
	compiler.Parameters.Pipeline = pipeline
	compiler.Parameters.Input.Add(BLC.IO.StringInput(fileName, text))
	return compiler.Run()


keep variable close to its usage

keep variable close to its usage

namespace UnityScript.Steps

import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.TypeSystem
import Boo.Lang.Compiler.TypeSystem.Internal
import Boo.Lang.Compiler.Steps

import Boo.Lang.Environments

import UnityScript.Core
import UnityScript.Macros
import UnityScript.TypeSystem

class ProcessUnityScriptMethods(ProcessMethodBodiesWithDuckTyping):

	deferred IEnumerable_GetEnumerator = Types.IEnumerable.GetMethod("GetEnumerator");

	deferred IEnumerator_MoveNext = Types.IEnumerator.GetMethod("MoveNext");

	deferred IEnumerator_get_Current = Types.IEnumerator.GetProperty("Current").GetGetMethod();

	deferred _StartCoroutine = NameResolutionService.ResolveMethod(UnityScriptTypeSystem.ScriptBaseType, "StartCoroutine_Auto")

	deferred _UnityRuntimeServices_GetEnumerator = ResolveUnityRuntimeMethod("GetEnumerator")

	deferred _UnityRuntimeServices_Update = ResolveUnityRuntimeMethod("Update")

	deferred _UnityRuntimeServices_GetTypeOf = ResolveUnityRuntimeMethod("GetTypeOf")

	_implicit = false

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

		// don't transform
		//     foo == null
		// into:
		//     foo is null
		// but into:
		//     foo.Equals(null)
		self.OptimizeNullComparisons = false

	def ResolveUnityRuntimeMethod(name as string):
		return NameResolutionService.ResolveMethod(UnityRuntimeServicesType, name)

	def ResolveUnityRuntimeField(name as string):
		return NameResolutionService.ResolveField(UnityRuntimeServicesType, name)

	deferred UnityRuntimeServicesType = TypeSystemServices.Map(UnityScript.Lang.UnityRuntimeServices)

	UnityScriptTypeSystem as UnityScript.TypeSystem.UnityScriptTypeSystem:
		get: return self.TypeSystemServices

	UnityScriptParameters as UnityScript.UnityScriptCompilerParameters:
		get: return _context.Parameters

	override def GetGeneratorReturnType(generator as InternalMethod):
		return TypeSystemServices.IEnumeratorType

	override def IsDuckTyped(e as Expression):
		if Strict: return false
		return super(e)

	override def IsDuckTyped(member as IMember):
		if Strict: return false
		return super(member)

	override protected def MemberNotFound(node as MemberReferenceExpression, ns as INamespace):
		if (not Strict) and (UnityScriptParameters.Expando or super.IsDuckTyped(node.Target)):
			BindQuack(node);
			return
		super(node, ns)

	override protected def LocalToReuseFor(d as Declaration):
		if DeclarationAnnotations.ShouldForceNewVariableFor(d):
			AssertUniqueLocal(d)
			return null
		return super(d)

	override def OnModule(module as Module):
		downcastPermissions = my(UnityDowncastPermissions)
		preserving _activeModule, Parameters.Strict, _implicit, downcastPermissions.Enabled:
			_activeModule = module
			Parameters.Strict = Pragmas.IsEnabledOn(module, Pragmas.Strict)
			_implicit = Pragmas.IsEnabledOn(module, Pragmas.Implicit)
			downcastPermissions.Enabled = Pragmas.IsEnabledOn(module, Pragmas.Downcast)
			super(module)

	override def VisitMemberPreservingContext(node as TypeMember):

		module = node.EnclosingModule
		if module is _activeModule:
			super(node)
			return

		downcastPermissions = my(UnityDowncastPermissions)
		preserving _activeModule, Parameters.Strict, _implicit, downcastPermissions.Enabled:
			_activeModule = module
			Parameters.Strict = Pragmas.IsEnabledOn(module, Pragmas.Strict)
			_implicit = Pragmas.IsEnabledOn(module, Pragmas.Implicit)
			downcastPermissions.Enabled = Pragmas.IsEnabledOn(module, Pragmas.Downcast)
			super(node)

	_activeModule as Module

	Strict:
		get: return Parameters.Strict

	override def OnMethod(node as Method):
		super(node)
		CheckForEmptyCoroutine(node)
		return if Parameters.OutputType == CompilerOutputType.Library
		CheckEntryPoint(node)

	def CheckForEmptyCoroutine(node as Method):
		if not IsEmptyCoroutine(node):
			return
		node.Body.Add([| return $(EmptyEnumeratorReference) |])

	def IsEmptyCoroutine(node as Method):
		entity as InternalMethod = GetEntity(node)
		return entity.ReturnType is GetGeneratorReturnType(entity) and HasNeitherReturnNorYield(node)

	deferred EmptyEnumeratorReference = CodeBuilder.CreateMemberReference(ResolveUnityRuntimeField("EmptyEnumerator"))

	def CheckEntryPoint(node as Method):
		if not node.IsStatic: return
		if not node.IsPublic: return
		if node.Name != "Main": return
		if GetType(node.ReturnType) is not TypeSystemServices.VoidType: return

		ContextAnnotations.SetEntryPoint(_context, node)

	override def ProcessAutoLocalDeclaration(node as BinaryExpression, reference as ReferenceExpression):
		if (Strict and not _implicit) and not IsCompilerGenerated(reference):
			EmitUnknownIdentifierError(reference, reference.Name)
		else:
			super(node, reference)

	def IsCompilerGenerated(reference as ReferenceExpression):
		return reference.Name.Contains('$')

	override protected def ProcessBuiltinInvocation(function as BuiltinFunction, node as MethodInvocationExpression):
		if function is UnityScriptTypeSystem.UnityScriptEval:
			EvalAnnotation.Mark(_currentMethod.Method)
			BindExpressionType(node, TypeSystemServices.ObjectType)
			return
		if function is UnityScriptTypeSystem.UnityScriptTypeof:
			ProcessTypeofBuiltin(node);
			return
		super(function, node)

	private def ProcessTypeofBuiltin(node as MethodInvocationExpression):
		if node.Arguments.Count != 1:
			Error(node, CompilerError("UCE0001", node.Target.LexicalInfo, "'typeof' takes a single argument.", null))
			return

		type = node.Arguments[0].Entity as IType
		if type is not null:
			node.ParentNode.Replace(node, CodeBuilder.CreateTypeofExpression(type))
			return

		node.Target = CodeBuilder.CreateReference(_UnityRuntimeServices_GetTypeOf)
		BindExpressionType(node, TypeSystemServices.TypeType)

	override protected def ProcessMethodInvocation(node as MethodInvocationExpression, targetEntity as IEntity):
	"""
	Automatically detects coroutine invocations in assignments and as standalone
	expressions and generates StartCoroutine invocations.
	"""
		super(node, targetEntity)

		if not IsPossibleStartCoroutineInvocation(node):
			return

		method as IMethod = targetEntity
		if method is null or method.IsStatic: return

		tss = self.UnityScriptTypeSystem
		if not tss.IsScriptType(method.DeclaringType): return
		if not tss.IsGenerator(method): return

		parentNode = node.ParentNode
		parentNode.Replace(
			node,
			CodeBuilder.CreateMethodInvocation(
				TargetForStartCoroutineInvocation(node, method),
				_StartCoroutine,
				node))

	def TargetForStartCoroutineInvocation(node as MethodInvocationExpression, method as IMethod):
		target = cast(MemberReferenceExpression, node.Target).Target
		if target isa SuperLiteralExpression: // super becomes self for coroutine invocation
			return CodeBuilder.CreateSelfReference(target.LexicalInfo, method.DeclaringType)
		return target.CloneNode()

	override def ProcessStaticallyTypedAssignment(node as BinaryExpression):
		TryToResolveAmbiguousAssignment(node)
		ApplyImplicitArrayConversion(node)
		if ValidateAssignment(node):
			BindExpressionType(node, GetExpressionType(node.Right))
		else:
			Error(node)

	def ApplyImplicitArrayConversion(node as BinaryExpression):
		left = GetExpressionType(node.Left)
		if not left.IsArray: return

		right = GetExpressionType(node.Right)
		if right is not TypeSystemServices.Map(UnityScript.Lang.Array): return

		node.Right = CodeBuilder.CreateCast(left,
						CodeBuilder.CreateMethodInvocation(
							node.Right,
							ResolveMethod(right, "ToBuiltin"),
							CodeBuilder.CreateTypeofExpression(left.ElementType)))

	override def OnForStatement(node as ForStatement):
		assert 1 == len(node.Declarations)
		Visit(node.Iterator)
		if NeedsUpdateableIteration(node):
			ProcessUpdateableIteration(node)
		else:
			ProcessNormalIteration(node)

	def ProcessNormalIteration(node as ForStatement):
		node.Iterator = ProcessIterator(node.Iterator, node.Declarations)
		VisitForStatementBlock(node)

	def ProcessUpdateableIteration(node as ForStatement):
		newIterator = CodeBuilder.CreateMethodInvocation(_UnityRuntimeServices_GetEnumerator, node.Iterator)
		newIterator.LexicalInfo = LexicalInfo(node.Iterator.LexicalInfo)
		node.Iterator = newIterator
		ProcessDeclarationForIterator(node.Declarations[0], TypeSystemServices.ObjectType)
		VisitForStatementBlock(node)
		TransformIteration(node)

	def TransformIteration(node as ForStatement):
		iterator = CodeBuilder.DeclareLocal(
						_currentMethod.Method,
						_context.GetUniqueName("iterator"),
						TypeSystemServices.IEnumeratorType)
		iterator.IsUsed = true
		body = Block(node.LexicalInfo)
		body.Add(
			CodeBuilder.CreateAssignment(
				node.LexicalInfo,
				CodeBuilder.CreateReference(iterator),
				node.Iterator))

		// while __iterator.MoveNext():
		ws = WhileStatement(node.LexicalInfo)
		ws.Condition = CodeBuilder.CreateMethodInvocation(
						CodeBuilder.CreateReference(iterator),
						IEnumerator_MoveNext)

		current = CodeBuilder.CreateMethodInvocation(
							CodeBuilder.CreateReference(iterator),
							IEnumerator_get_Current)

		//	item = __iterator.Current
		loopVariable as InternalLocal = TypeSystemServices.GetEntity(node.Declarations[0])
		ws.Block.Add(
				CodeBuilder.CreateAssignment(
					node.LexicalInfo,
					CodeBuilder.CreateReference(loopVariable),
					current))
		ws.Block.Add(node.Block)

		LoopVariableUpdater(self, _context, iterator, loopVariable).Visit(node)

		body.Add(ws)
		node.ParentNode.Replace(node, body)

	def NeedsUpdateableIteration(node as ForStatement):
		iteratorType = GetExpressionType(node.Iterator)
		if iteratorType.IsArray: return false
		return true

	class LoopVariableUpdater(DepthFirstVisitor):

		_parent as ProcessUnityScriptMethods
		_context as CompilerContext
		_iteratorVariable as IEntity
		_loopVariable as IEntity
		_found as bool

		def constructor(parent as ProcessUnityScriptMethods, context as CompilerContext, iteratorVariable as IEntity, loopVariable as IEntity):
			_parent = parent
			_context = context
			_iteratorVariable = iteratorVariable
			_loopVariable = loopVariable

		override def OnExpressionStatement(node as ExpressionStatement):
			_found = false
			Visit(node.Expression)
			if not _found: return

			parentNode = node.ParentNode

			codeBuilder = _context.CodeBuilder
			block = Block(node.LexicalInfo)
			block.Add(node)
			block.Add(
				codeBuilder.CreateMethodInvocation(
					_parent._UnityRuntimeServices_Update,
					codeBuilder.CreateReference(_iteratorVariable),
					codeBuilder.CreateReference(_loopVariable)))

			parentNode.Replace(node, block)

		override def OnReferenceExpression(node as ReferenceExpression):
			if _found: return

			referent = node.Entity
			_found = referent is _loopVariable

namespace UnityScript.Steps

import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.TypeSystem
import Boo.Lang.Compiler.TypeSystem.Internal
import Boo.Lang.Compiler.Steps

import Boo.Lang.Environments

import UnityScript.Core
import UnityScript.Macros
import UnityScript.TypeSystem

class ProcessUnityScriptMethods(ProcessMethodBodiesWithDuckTyping):

	deferred IEnumerable_GetEnumerator = Types.IEnumerable.GetMethod("GetEnumerator");

	deferred IEnumerator_MoveNext = Types.IEnumerator.GetMethod("MoveNext");

	deferred IEnumerator_get_Current = Types.IEnumerator.GetProperty("Current").GetGetMethod();

	deferred _StartCoroutine = NameResolutionService.ResolveMethod(UnityScriptTypeSystem.ScriptBaseType, "StartCoroutine_Auto")

	deferred _UnityRuntimeServices_GetEnumerator = ResolveUnityRuntimeMethod("GetEnumerator")

	deferred _UnityRuntimeServices_Update = ResolveUnityRuntimeMethod("Update")

	deferred _UnityRuntimeServices_GetTypeOf = ResolveUnityRuntimeMethod("GetTypeOf")

	_implicit = false

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

		// don't transform
		//     foo == null
		// into:
		//     foo is null
		// but into:
		//     foo.Equals(null)
		self.OptimizeNullComparisons = false

	def ResolveUnityRuntimeMethod(name as string):
		return NameResolutionService.ResolveMethod(UnityRuntimeServicesType, name)

	def ResolveUnityRuntimeField(name as string):
		return NameResolutionService.ResolveField(UnityRuntimeServicesType, name)

	deferred UnityRuntimeServicesType = TypeSystemServices.Map(UnityScript.Lang.UnityRuntimeServices)

	UnityScriptTypeSystem as UnityScript.TypeSystem.UnityScriptTypeSystem:
		get: return self.TypeSystemServices

	UnityScriptParameters as UnityScript.UnityScriptCompilerParameters:
		get: return _context.Parameters

	override def GetGeneratorReturnType(generator as InternalMethod):
		return TypeSystemServices.IEnumeratorType

	override def IsDuckTyped(e as Expression):
		if Strict: return false
		return super(e)

	override def IsDuckTyped(member as IMember):
		if Strict: return false
		return super(member)

	override protected def MemberNotFound(node as MemberReferenceExpression, ns as INamespace):
		if (not Strict) and (UnityScriptParameters.Expando or super.IsDuckTyped(node.Target)):
			BindQuack(node);
			return
		super(node, ns)

	override protected def LocalToReuseFor(d as Declaration):
		if DeclarationAnnotations.ShouldForceNewVariableFor(d):
			AssertUniqueLocal(d)
			return null
		return super(d)

	override def OnModule(module as Module):
		downcastPermissions = my(UnityDowncastPermissions)

		preserving _activeModule, Parameters.Strict, _implicit, downcastPermissions.Enabled:
			_activeModule = module
			Parameters.Strict = Pragmas.IsEnabledOn(module, Pragmas.Strict)
			_implicit = Pragmas.IsEnabledOn(module, Pragmas.Implicit)
			downcastPermissions.Enabled = Pragmas.IsEnabledOn(module, Pragmas.Downcast)
			super(module)

	override def VisitMemberPreservingContext(node as TypeMember):
		downcastPermissions = my(UnityDowncastPermissions)

		module = node.EnclosingModule
		if module is _activeModule:
			super(node)
			return

		preserving _activeModule, Parameters.Strict, _implicit, downcastPermissions.Enabled:
			_activeModule = module
			Parameters.Strict = Pragmas.IsEnabledOn(module, Pragmas.Strict)
			_implicit = Pragmas.IsEnabledOn(module, Pragmas.Implicit)
			downcastPermissions.Enabled = Pragmas.IsEnabledOn(module, Pragmas.Downcast)
			super(node)

	_activeModule as Module

	Strict:
		get: return Parameters.Strict

	override def OnMethod(node as Method):
		super(node)
		CheckForEmptyCoroutine(node)
		return if Parameters.OutputType == CompilerOutputType.Library
		CheckEntryPoint(node)

	def CheckForEmptyCoroutine(node as Method):
		if not IsEmptyCoroutine(node):
			return
		node.Body.Add([| return $(EmptyEnumeratorReference) |])

	def IsEmptyCoroutine(node as Method):
		entity as InternalMethod = GetEntity(node)
		return entity.ReturnType is GetGeneratorReturnType(entity) and HasNeitherReturnNorYield(node)

	deferred EmptyEnumeratorReference = CodeBuilder.CreateMemberReference(ResolveUnityRuntimeField("EmptyEnumerator"))

	def CheckEntryPoint(node as Method):
		if not node.IsStatic: return
		if not node.IsPublic: return
		if node.Name != "Main": return
		if GetType(node.ReturnType) is not TypeSystemServices.VoidType: return

		ContextAnnotations.SetEntryPoint(_context, node)

	override def ProcessAutoLocalDeclaration(node as BinaryExpression, reference as ReferenceExpression):
		if (Strict and not _implicit) and not IsCompilerGenerated(reference):
			EmitUnknownIdentifierError(reference, reference.Name)
		else:
			super(node, reference)

	def IsCompilerGenerated(reference as ReferenceExpression):
		return reference.Name.Contains('$')

	override protected def ProcessBuiltinInvocation(function as BuiltinFunction, node as MethodInvocationExpression):
		if function is UnityScriptTypeSystem.UnityScriptEval:
			EvalAnnotation.Mark(_currentMethod.Method)
			BindExpressionType(node, TypeSystemServices.ObjectType)
			return
		if function is UnityScriptTypeSystem.UnityScriptTypeof:
			ProcessTypeofBuiltin(node);
			return
		super(function, node)

	private def ProcessTypeofBuiltin(node as MethodInvocationExpression):
		if node.Arguments.Count != 1:
			Error(node, CompilerError("UCE0001", node.Target.LexicalInfo, "'typeof' takes a single argument.", null))
			return

		type = node.Arguments[0].Entity as IType
		if type is not null:
			node.ParentNode.Replace(node, CodeBuilder.CreateTypeofExpression(type))
			return

		node.Target = CodeBuilder.CreateReference(_UnityRuntimeServices_GetTypeOf)
		BindExpressionType(node, TypeSystemServices.TypeType)

	override protected def ProcessMethodInvocation(node as MethodInvocationExpression, targetEntity as IEntity):
	"""
	Automatically detects coroutine invocations in assignments and as standalone
	expressions and generates StartCoroutine invocations.
	"""
		super(node, targetEntity)

		if not IsPossibleStartCoroutineInvocation(node):
			return

		method as IMethod = targetEntity
		if method is null or method.IsStatic: return

		tss = self.UnityScriptTypeSystem
		if not tss.IsScriptType(method.DeclaringType): return
		if not tss.IsGenerator(method): return

		parentNode = node.ParentNode
		parentNode.Replace(
			node,
			CodeBuilder.CreateMethodInvocation(
				TargetForStartCoroutineInvocation(node, method),
				_StartCoroutine,
				node))

	def TargetForStartCoroutineInvocation(node as MethodInvocationExpression, method as IMethod):
		target = cast(MemberReferenceExpression, node.Target).Target
		if target isa SuperLiteralExpression: // super becomes self for coroutine invocation
			return CodeBuilder.CreateSelfReference(target.LexicalInfo, method.DeclaringType)
		return target.CloneNode()

	override def ProcessStaticallyTypedAssignment(node as BinaryExpression):
		TryToResolveAmbiguousAssignment(node)
		ApplyImplicitArrayConversion(node)
		if ValidateAssignment(node):
			BindExpressionType(node, GetExpressionType(node.Right))
		else:
			Error(node)

	def ApplyImplicitArrayConversion(node as BinaryExpression):
		left = GetExpressionType(node.Left)
		if not left.IsArray: return

		right = GetExpressionType(node.Right)
		if right is not TypeSystemServices.Map(UnityScript.Lang.Array): return

		node.Right = CodeBuilder.CreateCast(left,
						CodeBuilder.CreateMethodInvocation(
							node.Right,
							ResolveMethod(right, "ToBuiltin"),
							CodeBuilder.CreateTypeofExpression(left.ElementType)))

	override def OnForStatement(node as ForStatement):
		assert 1 == len(node.Declarations)
		Visit(node.Iterator)
		if NeedsUpdateableIteration(node):
			ProcessUpdateableIteration(node)
		else:
			ProcessNormalIteration(node)

	def ProcessNormalIteration(node as ForStatement):
		node.Iterator = ProcessIterator(node.Iterator, node.Declarations)
		VisitForStatementBlock(node)

	def ProcessUpdateableIteration(node as ForStatement):
		newIterator = CodeBuilder.CreateMethodInvocation(_UnityRuntimeServices_GetEnumerator, node.Iterator)
		newIterator.LexicalInfo = LexicalInfo(node.Iterator.LexicalInfo)
		node.Iterator = newIterator
		ProcessDeclarationForIterator(node.Declarations[0], TypeSystemServices.ObjectType)
		VisitForStatementBlock(node)
		TransformIteration(node)

	def TransformIteration(node as ForStatement):
		iterator = CodeBuilder.DeclareLocal(
						_currentMethod.Method,
						_context.GetUniqueName("iterator"),
						TypeSystemServices.IEnumeratorType)
		iterator.IsUsed = true
		body = Block(node.LexicalInfo)
		body.Add(
			CodeBuilder.CreateAssignment(
				node.LexicalInfo,
				CodeBuilder.CreateReference(iterator),
				node.Iterator))

		// while __iterator.MoveNext():
		ws = WhileStatement(node.LexicalInfo)
		ws.Condition = CodeBuilder.CreateMethodInvocation(
						CodeBuilder.CreateReference(iterator),
						IEnumerator_MoveNext)

		current = CodeBuilder.CreateMethodInvocation(
							CodeBuilder.CreateReference(iterator),
							IEnumerator_get_Current)

		//	item = __iterator.Current
		loopVariable as InternalLocal = TypeSystemServices.GetEntity(node.Declarations[0])
		ws.Block.Add(
				CodeBuilder.CreateAssignment(
					node.LexicalInfo,
					CodeBuilder.CreateReference(loopVariable),
					current))
		ws.Block.Add(node.Block)

		LoopVariableUpdater(self, _context, iterator, loopVariable).Visit(node)

		body.Add(ws)
		node.ParentNode.Replace(node, body)

	def NeedsUpdateableIteration(node as ForStatement):
		iteratorType = GetExpressionType(node.Iterator)
		if iteratorType.IsArray: return false
		return true

	class LoopVariableUpdater(DepthFirstVisitor):

		_parent as ProcessUnityScriptMethods
		_context as CompilerContext
		_iteratorVariable as IEntity
		_loopVariable as IEntity
		_found as bool

		def constructor(parent as ProcessUnityScriptMethods, context as CompilerContext, iteratorVariable as IEntity, loopVariable as IEntity):
			_parent = parent
			_context = context
			_iteratorVariable = iteratorVariable
			_loopVariable = loopVariable

		override def OnExpressionStatement(node as ExpressionStatement):
			_found = false
			Visit(node.Expression)
			if not _found: return

			parentNode = node.ParentNode

			codeBuilder = _context.CodeBuilder
			block = Block(node.LexicalInfo)
			block.Add(node)
			block.Add(
				codeBuilder.CreateMethodInvocation(
					_parent._UnityRuntimeServices_Update,
					codeBuilder.CreateReference(_iteratorVariable),
					codeBuilder.CreateReference(_loopVariable)))

			parentNode.Replace(node, block)

		override def OnReferenceExpression(node as ReferenceExpression):
			if _found: return

			referent = node.Entity
			_found = referent is _loopVariable


hmm.. some work is needed to make it work on MS.NET since mono has different behavior (will try to fix this in mono).

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

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

namespace Boo.Lang.Interpreter

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

class InteractiveInterpreter2(AbstractInterpreter):

	_values = {}

	_declarations = {}

	_representers = []

	[property(ShowWarnings)]
	_showWarnings = false

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

	[getter(LastValue)]
	_lastValue

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

	def constructor():
		super()
		InitializeStandardReferences()

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

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

	private _buffer = StringBuilder()

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

	[property(PromptColor)]
	_promptColor = ConsoleColor.Green

	[property(ExceptionColor)]
	_exceptionColor = ConsoleColor.Red

	[property(SuggestionsColor)]
	_suggestionsColor = ConsoleColor.Yellow

	[property(SelectedSuggestionColor)]
	_selectedSuggestionColor = ConsoleColor.Magenta

	[property(SelectedSuggestionIndex)]
	_selectedSuggestionIndex = -1

	[property(Suggestions)]
	_suggestions as (object)

	_builtins as (IEntity)

	_filter as string

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

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

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

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

		i = 0

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

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

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

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

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

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

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

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

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

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

		_selectedSuggestionIndex = -1
		_suggestions = null

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

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

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

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

			_selectedSuggestionIndex = -1

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

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

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

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

			lastChar = keyChar

		DisplayGoodbye()

	private _indent as int = 0

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

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

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

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

	override def SetLastValue(value):
		_lastValue = value

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

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

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

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

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

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

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

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

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

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

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

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

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

	def load([required] path as string):
		if path.EndsWith(".boo"):
			result = EvalCompilerInput(FileInput(path))
			if ShowWarnings:
				DisplayProblems(result.Warnings)
			if not DisplayProblems(result.Errors):
				ProcessLastValue()
		else:
			try:
				References.Add(System.Reflection.Assembly.LoadFrom(path))
			except e:
				print e.Message

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	callable Representer(value, writer as TextWriter)

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

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

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

namespace Boo.Lang.Interpreter

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

class InteractiveInterpreter2(AbstractInterpreter):

	_values = {}

	_declarations = {}

	_representers = []

	[property(ShowWarnings)]
	_showWarnings = false

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

	[getter(LastValue)]
	_lastValue

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

	def constructor():
		super()
		InitializeStandardReferences()

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

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

	private _buffer = StringBuilder()

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

	[property(PromptColor)]
	_promptColor = ConsoleColor.Green

	[property(ExceptionColor)]
	_exceptionColor = ConsoleColor.Red

	[property(SuggestionsColor)]
	_suggestionsColor = ConsoleColor.Yellow

	[property(SelectedSuggestionColor)]
	_selectedSuggestionColor = ConsoleColor.Magenta

	[property(SelectedSuggestionIndex)]
	_selectedSuggestionIndex = -1

	[property(Suggestions)]
	_suggestions as (object)

	_builtins as (IEntity)

	_filter as string

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

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

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

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

		i = 0

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

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

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

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

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

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

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

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

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

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

		_selectedSuggestionIndex = -1
		_suggestions = null

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

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

			newLine = keyChar.ToString() == Environment.NewLine

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

			_selectedSuggestionIndex = -1

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

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

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

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

			lastChar = keyChar

		DisplayGoodbye()

	private _indent as int = 0

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

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

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

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

	override def SetLastValue(value):
		_lastValue = value

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

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

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

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

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

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

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

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

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

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

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

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

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

	def load([required] path as string):
		if path.EndsWith(".boo"):
			result = EvalCompilerInput(FileInput(path))
			if ShowWarnings:
				DisplayProblems(result.Warnings)
			if not DisplayProblems(result.Errors):
				ProcessLastValue()
		else:
			try:
				References.Add(System.Reflection.Assembly.LoadFrom(path))
			except e:
				print e.Message

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	callable Representer(value, writer as TextWriter)

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

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


support for running tests when tmp folder path contain spaces

namespace Boojay.Compilation.Tests

import System
import System.IO
import System.Diagnostics

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

import Boojay.Compilation
import Boojay.Compilation.TypeSystem

static class JavaClassRunner:
	def run(className as string, *jars as (string)):
		classPath = (ProjectFolders.binFolder(), ProjectFolders.distFolder() + "/boojay.lang.jar") + jars

		exitCode as int, output, error as string = runp("java", "-cp ${quote(join(classPath, Path.PathSeparator))} ${className}")

		if exitCode != 0: raise output + "\n" + error
		return output

	def runp(filename as string, arguments as string):
		p = System.Diagnostics.Process()
		p.StartInfo.Arguments = arguments
		p.StartInfo.CreateNoWindow = true
		p.StartInfo.UseShellExecute = false
		p.StartInfo.RedirectStandardOutput = true
		p.StartInfo.RedirectStandardInput = true
		p.StartInfo.RedirectStandardError = true
		p.StartInfo.FileName = filename

		result = ""
		p.OutputDataReceived += def(sender, e as System.Diagnostics.DataReceivedEventArgs):
			result += e.Data + "\n"

		p.Start()
		p.BeginOutputReadLine()
		p.WaitForExit()

		exitCode = p.ExitCode
		error = p.StandardError.ReadToEnd()

		p.Close()

		return (exitCode, result, error)

static class ProjectFolders:
	def binFolder():
		return Path.GetFullPath(Path.Combine(testParentFolder(), "bin"))

	def distFolder():
		return Path.GetFullPath(Path.Combine(testParentFolder(), "dist"))

	def testParentFolder():
		folder = "."
		while not Directory.Exists(Path.Combine(folder, "tests")):
			folder = Path.Combine(folder, "..")
			assert Directory.Exists(folder)
		return folder

def generateTempJarWith(code as Module):
	jar = Path.GetTempFileName()
	boojayCompile(CompileUnit(code))
	generateJar(jar, "${type.FullName.Replace('.', '/')}.class" for type in code.Members)
	return jar

def booCompile(unit as CompileUnit):
	booCompile(unit, List[of ICompileUnit]())

def booCompile(unit as CompileUnit, *refs as (ICompileUnit)):
	compiler = newBooCompiler()
	result = compiler.Run(unit)
	assert 0 == len(result.Errors), result.Errors.ToString(true) + unit.ToCodeString()

def newBooCompiler():
	compiler = BooCompiler(CompilerParameters(true))
	compiler.Parameters.OutputType = CompilerOutputType.Auto
	compiler.Parameters.Pipeline = Boo.Lang.Compiler.Pipelines.CompileToMemory()

	return compiler

def runTestWithJar(test as Module, jar as Module):
	generatedJar = generateTempJarWith(jar)
	jarCompileUnit = JarTypeSystemProvider().ForJar(generatedJar)
	unit = CompileUnit(test)
	try:
		boojayCompile(unit, jarCompileUnit)
		className = moduleClassFor(test)
		print JavaClassRunner.run(className, Path.GetFullPath(generatedJar))
	except e:
		raise e.ToString() + unit.ToCodeString()

def moduleClassFor(module as Module):
	return module.FullName.Replace("-", "_").Replace("$", "_") + "Module"

def quote(arg as string):
	return '"' + arg + '"'

namespace Boojay.Compilation.Tests

import System
import System.IO
import System.Diagnostics

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

import Boojay.Compilation
import Boojay.Compilation.TypeSystem

static class JavaClassRunner:
	def run(className as string, *jars as (string)):
		classPath = (ProjectFolders.binFolder(), ProjectFolders.distFolder() + "/boojay.lang.jar") + jars

		exitCode as int, output, error as string = runp("java", "-cp ${join(classPath, Path.PathSeparator)} ${className}")

		if exitCode != 0: raise output + "\n" + error
		return output

	def runp(filename as string, arguments as string):
		p = System.Diagnostics.Process()
		p.StartInfo.Arguments = arguments
		p.StartInfo.CreateNoWindow = true
		p.StartInfo.UseShellExecute = false
		p.StartInfo.RedirectStandardOutput = true
		p.StartInfo.RedirectStandardInput = true
		p.StartInfo.RedirectStandardError = true
		p.StartInfo.FileName = filename

		result = ""
		p.OutputDataReceived += def(sender, e as System.Diagnostics.DataReceivedEventArgs):
			result += e.Data + "\n"

		p.Start()
		p.BeginOutputReadLine()
		p.WaitForExit()

		exitCode = p.ExitCode
		error = p.StandardError.ReadToEnd()

		p.Close()

		return (exitCode, result, error)

static class ProjectFolders:
	def binFolder():
		return Path.GetFullPath(Path.Combine(testParentFolder(), "bin"))

	def distFolder():
		return Path.GetFullPath(Path.Combine(testParentFolder(), "dist"))

	def testParentFolder():
		folder = "."
		while not Directory.Exists(Path.Combine(folder, "tests")):
			folder = Path.Combine(folder, "..")
			assert Directory.Exists(folder)
		return folder

def generateTempJarWith(code as Module):
	jar = Path.GetTempFileName()
	boojayCompile(CompileUnit(code))
	generateJar(jar, "${type.FullName.Replace('.', '/')}.class" for type in code.Members)
	return jar

def booCompile(unit as CompileUnit):
	booCompile(unit, List[of ICompileUnit]())

def booCompile(unit as CompileUnit, *refs as (ICompileUnit)):
	compiler = newBooCompiler()
	result = compiler.Run(unit)
	assert 0 == len(result.Errors), result.Errors.ToString(true) + unit.ToCodeString()

def newBooCompiler():
	compiler = BooCompiler(CompilerParameters(true))
	compiler.Parameters.OutputType = CompilerOutputType.Auto
	compiler.Parameters.Pipeline = Boo.Lang.Compiler.Pipelines.CompileToMemory()

	return compiler

def runTestWithJar(test as Module, jar as Module):
	generatedJar = generateTempJarWith(jar)
	jarCompileUnit = JarTypeSystemProvider().ForJar(generatedJar)
	unit = CompileUnit(test)
	try:
		boojayCompile(unit, jarCompileUnit)
		className = moduleClassFor(test)
		print JavaClassRunner.run(className, Path.GetFullPath(generatedJar))
	except e:
		raise e.ToString() + unit.ToCodeString()

def moduleClassFor(module as Module):
	return module.FullName.Replace("-", "_").Replace("$", "_") + "Module"


fix Gendarme-enabled build ;)

fix Gendarme-enabled build ;)

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

//
// Gendarme.Rules.Abstract.DependenciesMacro DSL for DependencyCheckingRule
//
// Authors:
//	Cedric Vivier  <cedricv@neonux.com>
//
// Copyright (C) 2009 Cedric Vivier
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

namespace Gendarme.Rules.Abstract

import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.TypeSystem
import Boo.Lang.Compiler.TypeSystem.Services
import Boo.Lang.PatternMatching

macro dependencies(name as ReferenceExpression):
"""
This macro/DSL generates a Gendarme rule that checks if there is disallowed
internal dependencies in a project.

Example below denies using any BCL type from within type/namespace Foo:

dependencies NameOfTheDependencySet:
	within Foo:
		deny System

Example below denies using classes from namespace Bar within namespace Foo
(and its inner namespaces), this means usage of Bar interfaces is still allowed:

dependencies NameOfTheDependencySet:
	within Foo:
		deny Bar, Class

Example below denies using interfaces of Bar namespace within visible members
of type/namespace Foo:

dependencies NameOfTheDependencySet:
	within Foo:
		deny Bar, Interface|Visible

"""
	raise dependencies.Documentation if not len(dependencies.Body.Statements)

	macro within(reference):
	"""
	Macro `within' must contain at least one `deny' or `allow' macro.
	"""
		raise within.Documentation if not len(within.Body.Statements)

		macro deny(reference, options as ReferenceExpression*):
			yield [| yield $(BuildDependencyPermission(NameResolutionService, reference, true, options)) |]

		macro allow(reference, options as ReferenceExpression*):
			yield [| yield $(BuildDependencyPermission(NameResolutionService, reference, false, options)) |]

		nref = GetNormalizedReference(reference)
		entity = NameResolutionService.ResolveQualifiedName(nref, EntityType.Namespace|EntityType.Type)
		if not entity or entity.EntityType != EntityType.Namespace:
			matcher = [| DoesTargetMatchNamespace(CurrentType, typeof($nref).FullName) |] #TODO: MatchType
		else: #namespace
			matcher = [| DoesTargetMatchNamespace(CurrentType.FullName, $(nref)) |]
		yield [|
			if $matcher:
				$(within.Body)
		|]

	yield [|
		public class $(name + "DependencyCheckingRule") (DependencyCheckingRule):
			protected Permissions as DependencyPermission*:
				override get:
					$(dependencies.Body)
	|]

internal def GetNormalizedReference(reference as Expression):
	nref = reference.ToCodeString()
	//nref = nref.Substring(0, len(nref)-2) if nref.EndsWith('_')
	return nref

internal def BuildDependencyPermission(nre as NameResolutionService, reference as ReferenceExpression, deny as bool, options as ReferenceExpression*):
	nref = GetNormalizedReference(reference)
	entity = nre.ResolveQualifiedName(nref, EntityType.Namespace|EntityType.Type)
	perm = [| DependencyPermission(self) |]

	if not entity or entity.EntityType != EntityType.Namespace:
		perm.NamedArguments.Add([| Namespace: typeof($nref).FullName |]) #TODO: Type:
	else: #namespace
		perm.NamedArguments.Add([| Namespace: $nref |])

	if not deny:
		perm.NamedArguments.Add([| Deny: false |])

	for option in options:
		if option.Name == "Class": perm.NamedArguments.Add([| Relation: DependencyRelation.Static |])
		elif option.Name == "Interface": perm.NamedArguments.Add([| Relation: DependencyRelation.Dynamic |])
		elif option.Name == "Visible": perm.NamedArguments.Add([| Visibility: DependencyVisibility.Visible |])
		elif option.Name == "NonVisible": perm.NamedArguments.Add([| Visibility: DependencyVisibility.NonVisible |])
		else: raise "Invalid option: `${option.Name}'"

	return perm

//
// Gendarme.Rules.Abstract.DependenciesMacro DSL for DependencyCheckingRule
//
// Authors:
//	Cedric Vivier  <cedricv@neonux.com>
//
// Copyright (C) 2009 Cedric Vivier
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

namespace Gendarme.Rules.Abstract

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

macro dependencies(name as ReferenceExpression):
"""
This macro/DSL generates a Gendarme rule that checks if there is disallowed
internal dependencies in a project.

Example below denies using any BCL type from within type/namespace Foo:

dependencies NameOfTheDependencySet:
	within Foo:
		deny System

Example below denies using classes from namespace Bar within namespace Foo
(and its inner namespaces), this means usage of Bar interfaces is still allowed:

dependencies NameOfTheDependencySet:
	within Foo:
		deny Bar, Class

Example below denies using interfaces of Bar namespace within visible members
of type/namespace Foo:

dependencies NameOfTheDependencySet:
	within Foo:
		deny Bar, Interface|Visible

"""
	raise dependencies.Documentation if not len(dependencies.Body.Statements)

	macro within(reference):
	"""
	Macro `within' must contain at least one `deny' or `allow' macro.
	"""
		raise within.Documentation if not len(within.Body.Statements)

		macro deny(reference, options as ReferenceExpression*):
			yield [| yield $(BuildDependencyPermission(NameResolutionService, reference, true, options)) |]

		macro allow(reference, options as ReferenceExpression*):
			yield [| yield $(BuildDependencyPermission(NameResolutionService, reference, false, options)) |]

		nref = GetNormalizedReference(reference)
		entity = NameResolutionService.ResolveQualifiedName(nref, EntityType.Namespace|EntityType.Type)
		if not entity or entity.EntityType != EntityType.Namespace:
			matcher = [| DoesTargetMatchNamespace(CurrentType, typeof($nref).FullName) |] #TODO: MatchType
		else: #namespace
			matcher = [| DoesTargetMatchNamespace(CurrentType.FullName, $(nref)) |]
		yield [|
			if $matcher:
				$(within.Body)
		|]

	yield [|
		public class $(name + "DependencyCheckingRule") (DependencyCheckingRule):
			protected Permissions as DependencyPermission*:
				override get:
					$(dependencies.Body)
	|]

internal def GetNormalizedReference(reference as Expression):
	nref = reference.ToCodeString()
	//nref = nref.Substring(0, len(nref)-2) if nref.EndsWith('_')
	return nref

internal def BuildDependencyPermission(nre as NameResolutionService, reference as ReferenceExpression, deny as bool, options as ReferenceExpression*):
	nref = GetNormalizedReference(reference)
	entity = nre.ResolveQualifiedName(nref, EntityType.Namespace|EntityType.Type)
	perm = [| DependencyPermission(self) |]

	if not entity or entity.EntityType != EntityType.Namespace:
		perm.NamedArguments.Add([| Namespace: typeof($nref).FullName |]) #TODO: Type:
	else: #namespace
		perm.NamedArguments.Add([| Namespace: $nref |])

	if not deny:
		perm.NamedArguments.Add([| Deny: false |])

	for option in options:
		if option.Name == "Class": perm.NamedArguments.Add([| Relation: DependencyRelation.Static |])
		elif option.Name == "Interface": perm.NamedArguments.Add([| Relation: DependencyRelation.Dynamic |])
		elif option.Name == "Visible": perm.NamedArguments.Add([| Visibility: DependencyVisibility.Visible |])
		elif option.Name == "NonVisible": perm.NamedArguments.Add([| Visibility: DependencyVisibility.NonVisible |])
		else: raise "Invalid option: `${option.Name}'"

	return perm


Remove debug print

Remove debug print

namespace BooJs.Compiler.Steps

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

class UndoProcessMethod(AbstractTransformerCompilerStep):
"""
    Boo's ProcessMethodBodies performs some quite complex computations to
    infer types and to support 'dynamic' features using a non dynamic version
    of the CLI.

    This step tries to undo some of the changes performed to the AST in order
    to have a cleaner way to generate Javascript which is fully dynamic.
"""

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

    override def EnterMethod(node as Method):
    """ Remove the injected \$locals variable
    """

        for local in node.Locals:
            if local.Name == '$locals':
                node.Locals.Remove(local)
                break
        return true

    def EnterExpressionStatement(node as ExpressionStatement):
    """ Remove the assignment of the closure instance to the \$locals variable
            \$locals = FooModule.\$foo\$locals\$3()
    """
        if node.Expression.NodeType != NodeType.BinaryExpression:
            return true

        expr as BinaryExpression = node.Expression
        if expr.Operator == BinaryOperatorType.Assign:
            if expr.Left.ToString() == '$locals':
                RemoveCurrentNode
                return false

        return true

    def OnMethodInvocationExpression(node as MethodInvocationExpression):
    """ Fix closure invocations
            \$locals.\$foo.Invoke(1)  ->  foo(1)
    """
        if node.Target.NodeType != NodeType.MemberReferenceExpression:
            return

        target as MemberReferenceExpression = node.Target
        # Check if we come from a $locals
        while true:
            if target.Target.NodeType != NodeType.MemberReferenceExpression:
                if target.Target.ToString() == '$locals':
                    Visit target
                    # Remove injected Invoke calls
                    if (node.Target as MemberReferenceExpression).Name == 'Invoke':
                        target = node.Target
                        node.Replace(target, target.Target)
                break

            target = target.Target

    def EnterMemberReferenceExpression(node as MemberReferenceExpression):
    """ Fix closure references
            \$locals.\$foo = 1  ->  foo = 1
    """
        if node.Target.ToString() != '$locals':
            return true

        name = node.Name[1:]
        reference = ReferenceExpression(node.LexicalInfo)
        reference.Name = name
        node.ParentNode.Replace(node, reference)

        # Go up to the containing function to insert this as a global
        parent = node.ParentNode
        while parent:
            if parent.NodeType == NodeType.Method:
                # Check if we have already added it to the method Locals
                if not (parent as Method).Locals.Contains({local as Local| local.Name == name}):
                    CodeBuilder.DeclareLocal(parent, name, GetType(node))

                break

            parent = parent.ParentNode

        return false

    override def LeaveRaiseStatement(node as RaiseStatement):
        # Boo allows to re-raise an exception by omitting the exception argument. We always capture
        # the exception in `__e` so we just define it for empty raise statements.
        # TODO: Move this to ProcessTry ???
        if not node.Exception:
            node.Exception = [| __e |]

        # Boo wraps the raising of literals with System.Exception. We have to undo
        # that transformation to issue Javascript's native Error one.
        if node.Exception.NodeType == NodeType.MethodInvocationExpression:
            ex = node.Exception as MethodInvocationExpression
            if ex.Target.ToString() == 'System.Exception':
                ex.Target = [| Error |]

namespace BooJs.Compiler.Steps

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

class UndoProcessMethod(AbstractTransformerCompilerStep):
"""
    Boo's ProcessMethodBodies performs some quite complex computations to
    infer types and to support 'dynamic' features using a non dynamic version
    of the CLI.

    This step tries to undo some of the changes performed to the AST in order
    to have a cleaner way to generate Javascript which is fully dynamic.
"""

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

    def OnIntegerLiteralExpression(node as IntegerLiteralExpression):
        print node

    override def EnterMethod(node as Method):
    """ Remove the injected \$locals variable
    """

        for local in node.Locals:
            if local.Name == '$locals':
                node.Locals.Remove(local)
                break
        return true

    def EnterExpressionStatement(node as ExpressionStatement):
    """ Remove the assignment of the closure instance to the \$locals variable
            \$locals = FooModule.\$foo\$locals\$3()
    """
        if node.Expression.NodeType != NodeType.BinaryExpression:
            return true

        expr as BinaryExpression = node.Expression
        if expr.Operator == BinaryOperatorType.Assign:
            if expr.Left.ToString() == '$locals':
                RemoveCurrentNode
                return false

        return true

    def OnMethodInvocationExpression(node as MethodInvocationExpression):
    """ Fix closure invocations
            \$locals.\$foo.Invoke(1)  ->  foo(1)
    """
        if node.Target.NodeType != NodeType.MemberReferenceExpression:
            return

        target as MemberReferenceExpression = node.Target
        # Check if we come from a $locals
        while true:
            if target.Target.NodeType != NodeType.MemberReferenceExpression:
                if target.Target.ToString() == '$locals':
                    Visit target
                    # Remove injected Invoke calls
                    if (node.Target as MemberReferenceExpression).Name == 'Invoke':
                        target = node.Target
                        node.Replace(target, target.Target)
                break

            target = target.Target

    def EnterMemberReferenceExpression(node as MemberReferenceExpression):
    """ Fix closure references
            \$locals.\$foo = 1  ->  foo = 1
    """
        if node.Target.ToString() != '$locals':
            return true

        name = node.Name[1:]
        reference = ReferenceExpression(node.LexicalInfo)
        reference.Name = name
        node.ParentNode.Replace(node, reference)

        # Go up to the containing function to insert this as a global
        parent = node.ParentNode
        while parent:
            if parent.NodeType == NodeType.Method:
                # Check if we have already added it to the method Locals
                if not (parent as Method).Locals.Contains({local as Local| local.Name == name}):
                    CodeBuilder.DeclareLocal(parent, name, GetType(node))

                break

            parent = parent.ParentNode

        return false

    override def LeaveRaiseStatement(node as RaiseStatement):
        # Boo allows to re-raise an exception by omitting the exception argument. We always capture
        # the exception in `__e` so we just define it for empty raise statements.
        # TODO: Move this to ProcessTry ???
        if not node.Exception:
            node.Exception = [| __e |]

        # Boo wraps the raising of literals with System.Exception. We have to undo
        # that transformation to issue Javascript's native Error one.
        if node.Exception.NodeType == NodeType.MethodInvocationExpression:
            ex = node.Exception as MethodInvocationExpression
            if ex.Target.ToString() == 'System.Exception':
                ex.Target = [| Error |]


Include 'any' as an alias to 'duck'

Include 'any' as an alias to 'duck'

namespace BooJs.Compiler.TypeSystem

import Boo.Lang.Compiler.TypeSystem(BuiltinFunction, IType)
import Boo.Lang.Compiler.TypeSystem.TypeSystemServices as BooServices

import BooJs.Lang(Globals, Builtins, RuntimeServices)

class TypeSystemServices(BooServices):

    override static ErrorEntity = Globals.Error

    public IGeneratorType as IType
    public IGeneratorGenericType as IType

    override ExceptionType:
         get: return Map(Globals.Error)

    override protected def PreparePrimitives():
        # Setup new defaults for primitive types
        ObjectType = Map(Globals.Object)
        ObjectArrayType = ObjectType.MakeArrayType(1)

        StringType = Map(Globals.String)
        CharType = StringType  # We handle chars as strings

        BoolType = Map(Globals.Boolean)

        LongType = Map(Globals.NumberInt)
        IntType = LongType
        SByteType = LongType
        ShortType = LongType

        ULongType = Map(Globals.NumberUInt)
        UIntType = ULongType
        UShortType = ULongType
        ByteType = ULongType

        DoubleType = Map(Globals.Number)
        SingleType = DoubleType

        # In BooJs arrays are mutable too
        ArrayType = Map(typeof(Globals.Array))
        ListType = ArrayType
        # List is implemented with a generic version of Array
        #ListType = Map(typeof(Globals.Array[of Globals.Object]))

        HashType = Map(Builtins.Hash)

        # Enumerables
        IEnumerableType = Map(typeof(Globals.Iterable))
        IEnumerableGenericType = Map(typeof(Globals.Iterable[of*]))

        # TODO: Custom generator type
        IGeneratorType = Map(typeof(Globals.GeneratorIterator))
        IGeneratorGenericType = Map(typeof(Globals.GeneratorIterator[of*]))

        RegExpType = Map(Globals.RegExp)

        ICallableType = Map(Builtins.ICallable)

        DuckType = Map(Builtins.Duck)

        RuntimeServicesType = Map(RuntimeServices)
        BuiltinsType = Map(Builtins)

        # Add primitive types
        AddPrimitiveType("void", VoidType)
        AddPrimitiveType("object", ObjectType)
        AddPrimitiveType("callable", ICallableType)
        AddPrimitiveType("duck", DuckType)
        # Make TypeScript folks feel more at home :)
        AddPrimitiveType("any", DuckType)

        AddLiteralPrimitiveType("bool", BoolType)
        AddLiteralPrimitiveType("int", LongType)
        AddLiteralPrimitiveType("uint", ULongType)
        AddLiteralPrimitiveType("double", DoubleType)
        AddLiteralPrimitiveType("string", StringType)
        AddLiteralPrimitiveType('regex', RegExpType)

    override protected def PrepareBuiltinFunctions():
        AddBuiltin(BuiltinFunction.Len)
        AddBuiltin(BuiltinFunction.Eval);

    override public def CanBeReachedByPromotion(expectedType as IType, actualType as IType) as bool:
        # Boo allows to cast chars to integers, since in BooJs a char is just a string
        # we can't allow that kind of cast.
        if IsIntegerNumber(actualType) and expectedType == CharType:
            return false
        if IsIntegerNumber(expectedType) and actualType == CharType:
            return false

        return super(expectedType, actualType)

     new def IsSystemObject(type as IType):
        # ObjectType is mapped to our custom object so we have to check also against System.Object
        return super.IsSystemObject(type) or type is Map(System.Object)

     new def IsDuckType(type as IType) as bool:
        # ObjectType is mapped to our custom object so we have to check also against System.Object
        return super.IsDuckType(type) or (Context.Parameters.Ducky and IsSystemObject(type))

     override def IsDuckTyped(node as Boo.Lang.Compiler.Ast.Expression) as bool:
        type = node.ExpressionType
        return type is not null and IsDuckType(type)

namespace BooJs.Compiler.TypeSystem

import Boo.Lang.Compiler.TypeSystem(BuiltinFunction, IType)
import Boo.Lang.Compiler.TypeSystem.TypeSystemServices as BooServices

import BooJs.Lang(Globals, Builtins, RuntimeServices)

class TypeSystemServices(BooServices):

    override static ErrorEntity = Globals.Error

    public IGeneratorType as IType
    public IGeneratorGenericType as IType

    override ExceptionType:
         get: return Map(Globals.Error)

    override protected def PreparePrimitives():
        # Setup new defaults for primitive types
        ObjectType = Map(Globals.Object)
        ObjectArrayType = ObjectType.MakeArrayType(1)

        StringType = Map(Globals.String)
        CharType = StringType  # We handle chars as strings

        BoolType = Map(Globals.Boolean)

        LongType = Map(Globals.NumberInt)
        IntType = LongType
        SByteType = LongType
        ShortType = LongType

        ULongType = Map(Globals.NumberUInt)
        UIntType = ULongType
        UShortType = ULongType
        ByteType = ULongType

        DoubleType = Map(Globals.Number)
        SingleType = DoubleType

        # In BooJs arrays are mutable too
        ArrayType = Map(typeof(Globals.Array))
        ListType = ArrayType
        # List is implemented with a generic version of Array
        #ListType = Map(typeof(Globals.Array[of Globals.Object]))

        HashType = Map(Builtins.Hash)

        # Enumerables
        IEnumerableType = Map(typeof(Globals.Iterable))
        IEnumerableGenericType = Map(typeof(Globals.Iterable[of*]))

        # TODO: Custom generator type
        IGeneratorType = Map(typeof(Globals.GeneratorIterator))
        IGeneratorGenericType = Map(typeof(Globals.GeneratorIterator[of*]))

        RegExpType = Map(Globals.RegExp)

        ICallableType = Map(Builtins.ICallable)

        DuckType = Map(Builtins.Duck)

        RuntimeServicesType = Map(RuntimeServices)
        BuiltinsType = Map(Builtins)

        # Add primitive types
        AddPrimitiveType("void", VoidType)
        AddPrimitiveType("object", ObjectType)
        AddPrimitiveType("callable", ICallableType)
        AddPrimitiveType("duck", DuckType)

        AddLiteralPrimitiveType("bool", BoolType)
        AddLiteralPrimitiveType("int", LongType)
        AddLiteralPrimitiveType("uint", ULongType)
        AddLiteralPrimitiveType("double", DoubleType)
        AddLiteralPrimitiveType("string", StringType)
        AddLiteralPrimitiveType('regex', RegExpType)

    override protected def PrepareBuiltinFunctions():
        AddBuiltin(BuiltinFunction.Len)
        AddBuiltin(BuiltinFunction.Eval);

    override public def CanBeReachedByPromotion(expectedType as IType, actualType as IType) as bool:
        # Boo allows to cast chars to integers, since in BooJs a char is just a string
        # we can't allow that kind of cast.
        if IsIntegerNumber(actualType) and expectedType == CharType:
            return false
        if IsIntegerNumber(expectedType) and actualType == CharType:
            return false

        return super(expectedType, actualType)

     new def IsSystemObject(type as IType):
        # ObjectType is mapped to our custom object so we have to check also against System.Object
        return super.IsSystemObject(type) or type is Map(System.Object)

     new def IsDuckType(type as IType) as bool:
        # ObjectType is mapped to our custom object so we have to check also against System.Object
        return super.IsDuckType(type) or (Context.Parameters.Ducky and IsSystemObject(type))

     override def IsDuckTyped(node as Boo.Lang.Compiler.Ast.Expression) as bool:
        type = node.ExpressionType
        return type is not null and IsDuckType(type)