booish now allow blocks to be typed

booish now allow blocks to be typed

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

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

class InterpreterEntity(ITypedEntity):

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

	[getter(Type)]
	_type as IType

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

	EntityType:
		get:
			return TypeSystem.EntityType.Custom

	static def IsInterpreterEntity(node as Node):
		return node.Entity is not null and TypeSystem.EntityType.Custom == node.Entity.EntityType

class InterpreterNamespace(INamespace):

	[getter(ParentNamespace)]
	_parent as INamespace

	_tss as TypeSystemServices

	_interpreter as InteractiveInterpreter

	_declarations = {}

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

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

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

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

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

		return false

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

class ProcessVariableDeclarations(Steps.ProcessMethodBodiesWithDuckTyping):

	_namespace as InterpreterNamespace
	_interpreter as InteractiveInterpreter

	def constructor(interpreter):
		_interpreter = interpreter

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

		_namespace = InterpreterNamespace(
							_interpreter,
							TypeSystemServices,
							NameResolutionService.GlobalNamespace)
		NameResolutionService.GlobalNamespace = _namespace

	override def Dispose():
		_namespace = null

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

	override def DeclareLocal(name as string, type as IType, privateScope as bool):
		return super(name, type, privateScope) if privateScope
		return _namespace.Declare(name, type)

class ProcessInterpreterReferences(Steps.AbstractTransformerCompilerStep):

	static InteractiveInterpreter_GetValue = typeof(InteractiveInterpreter).GetMethod("GetValue")
	static InteractiveInterpreter_SetValue = typeof(InteractiveInterpreter).GetMethod("SetValue")

	_interpreterField as Field
	_interpreter as InteractiveInterpreter

	def constructor(interpreter):
		_interpreter = interpreter

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

	override def EnterModule(node as Module):

		module = cast(ModuleEntity, node.Entity).ModuleClass
		return false unless module

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

		return true

	override def OnReferenceExpression(node as ReferenceExpression):

		return unless InterpreterEntity.IsInterpreterEntity(node) and not AstUtil.IsLhsOfAssignment(node)

		ReplaceCurrentNode(CreateGetValue(node))

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

	def CreateGetValue(node as ReferenceExpression):
		return CodeBuilder.CreateCast(
				node.ExpressionType,
				CodeBuilder.CreateMethodInvocation(
					CodeBuilder.CreateReference(_interpreterField),
					TypeSystemServices.Map(InteractiveInterpreter_GetValue),
					CodeBuilder.CreateStringLiteral(node.Name)))

	def CreateSetValue(node as BinaryExpression):
		return CodeBuilder.CreateMethodInvocation(
					CodeBuilder.CreateReference(_interpreterField),
					TypeSystemServices.Map(InteractiveInterpreter_SetValue),
					CodeBuilder.CreateStringLiteral(cast(ReferenceExpression, node.Left).Name),
					node.Right)

class InteractiveInterpreter:

	_compiler = BooCompiler()

	_parser = BooCompiler()

	_values = {}

	_imports = ImportCollection()

	def constructor():
		pipeline = Pipelines.CompileToMemory()
		pipeline.RemoveAt(0)

		index = pipeline.Find(Steps.ProcessMethodBodiesWithDuckTyping)
		pipeline[index] = ProcessVariableDeclarations(self)
		pipeline.InsertBefore(Steps.EmitAssembly, ProcessInterpreterReferences(self))

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

		_compiler.Parameters.Pipeline = pipeline

		_parser.Parameters.Pipeline = Pipelines.Parse()

	def Eval(code as string):
		result = Parse(code)
		return result if len(result.Errors)

		cu = result.CompileUnit
		module = cu.Modules[0]

		hasMembers = module.Members.Count > 0
		hasStatements = module.Globals.Statements.Count > 0

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

		if hasStatements:
			_compiler.Parameters.OutputType = CompilerOutputType.ConsoleApplication
		else:
			_compiler.Parameters.OutputType = CompilerOutputType.Library

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

		RecordImports(savedImports)

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

		InitializeModuleInterpreter(asm, module)

		result.GeneratedAssemblyEntryPoint.Invoke(null, (null,)) if hasStatements
		return result

	def Parse(code as string):
		_parser.Parameters.Input.Clear()
		_parser.Parameters.Input.Add(StringInput("src", code))
		return _parser.Run()

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

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

	private def InitializeModuleInterpreter(asm as System.Reflection.Assembly,
										module as Module):
		moduleType = asm.GetType(cast(ModuleEntity, module.Entity).ModuleClass.FullName)
		moduleType.GetField("ParentInterpreter").SetValue(null, self)

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

def ReadBlock(line as string):
	newLine = System.Environment.NewLine
	buffer = System.Text.StringBuilder()
	buffer.Append(line)
	buffer.Append(newLine)
	while line=prompt("... "):
		break if 0 == len(line)
		buffer.Append(line)
		buffer.Append(newLine)
	return buffer.ToString()

interpreter = InteractiveInterpreter()
while line=prompt(">>> "):
	try:
		line = ReadBlock(line) if line.EndsWith(":")
		result = interpreter.Eval(line)
		for error in result.Errors:
			pos = error.LexicalInfo.StartColumn
			print("---" + "-" * pos + "^") if pos > 0
			print("ERROR: ${error.Message}")
	except x:
		print(x)

#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 booish

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

class InterpreterEntity(ITypedEntity):

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

	[getter(Type)]
	_type as IType

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

	EntityType:
		get:
			return TypeSystem.EntityType.Custom

	static def IsInterpreterEntity(node as Node):
		return node.Entity is not null and TypeSystem.EntityType.Custom == node.Entity.EntityType

class InterpreterNamespace(INamespace):

	[getter(ParentNamespace)]
	_parent as INamespace

	_tss as TypeSystemServices

	_interpreter as InteractiveInterpreter

	_declarations = {}

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

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

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

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

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

		return false

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

class ProcessVariableDeclarations(Steps.ProcessMethodBodiesWithDuckTyping):

	_namespace as InterpreterNamespace
	_interpreter as InteractiveInterpreter

	def constructor(interpreter):
		_interpreter = interpreter

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

		_namespace = InterpreterNamespace(
							_interpreter,
							TypeSystemServices,
							NameResolutionService.GlobalNamespace)
		NameResolutionService.GlobalNamespace = _namespace

	override def Dispose():
		_namespace = null

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

	override def DeclareLocal(name as string, type as IType, privateScope as bool):
		return super(name, type, privateScope) if privateScope
		return _namespace.Declare(name, type)

class ProcessInterpreterReferences(Steps.AbstractTransformerCompilerStep):

	static InteractiveInterpreter_GetValue = typeof(InteractiveInterpreter).GetMethod("GetValue")
	static InteractiveInterpreter_SetValue = typeof(InteractiveInterpreter).GetMethod("SetValue")

	_interpreterField as Field
	_interpreter as InteractiveInterpreter

	def constructor(interpreter):
		_interpreter = interpreter

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

	override def EnterModule(node as Module):

		module = cast(ModuleEntity, node.Entity).ModuleClass
		return false unless module

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

		return true

	override def OnReferenceExpression(node as ReferenceExpression):

		return unless InterpreterEntity.IsInterpreterEntity(node) and not AstUtil.IsLhsOfAssignment(node)

		ReplaceCurrentNode(CreateGetValue(node))

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

	def CreateGetValue(node as ReferenceExpression):
		return CodeBuilder.CreateCast(
				node.ExpressionType,
				CodeBuilder.CreateMethodInvocation(
					CodeBuilder.CreateReference(_interpreterField),
					TypeSystemServices.Map(InteractiveInterpreter_GetValue),
					CodeBuilder.CreateStringLiteral(node.Name)))

	def CreateSetValue(node as BinaryExpression):
		return CodeBuilder.CreateMethodInvocation(
					CodeBuilder.CreateReference(_interpreterField),
					TypeSystemServices.Map(InteractiveInterpreter_SetValue),
					CodeBuilder.CreateStringLiteral(cast(ReferenceExpression, node.Left).Name),
					node.Right)

class InteractiveInterpreter:

	_compiler = BooCompiler()

	_parser = BooCompiler()

	_values = {}

	_imports = ImportCollection()

	def constructor():
		pipeline = Pipelines.CompileToMemory()
		pipeline.RemoveAt(0)

		index = pipeline.Find(Steps.ProcessMethodBodiesWithDuckTyping)
		pipeline[index] = ProcessVariableDeclarations(self)
		pipeline.InsertBefore(Steps.EmitAssembly, ProcessInterpreterReferences(self))

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

		_compiler.Parameters.Pipeline = pipeline

		_parser.Parameters.Pipeline = Pipelines.Parse()

	def Eval(code as string):
		result = Parse(code)
		return result if len(result.Errors)

		cu = result.CompileUnit
		module = cu.Modules[0]

		hasMembers = module.Members.Count > 0
		hasStatements = module.Globals.Statements.Count > 0

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

		if hasStatements:
			_compiler.Parameters.OutputType = CompilerOutputType.ConsoleApplication
		else:
			_compiler.Parameters.OutputType = CompilerOutputType.Library

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

		RecordImports(savedImports)

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

		InitializeModuleInterpreter(asm, module)

		result.GeneratedAssemblyEntryPoint.Invoke(null, (null,)) if hasStatements
		return result

	def Parse(code as string):
		_parser.Parameters.Input.Clear()
		_parser.Parameters.Input.Add(StringInput("src", code))
		return _parser.Run()

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

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

	private def InitializeModuleInterpreter(asm as System.Reflection.Assembly,
										module as Module):
		moduleType = asm.GetType(cast(ModuleEntity, module.Entity).ModuleClass.FullName)
		moduleType.GetField("ParentInterpreter").SetValue(null, self)

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

interpreter = InteractiveInterpreter()
while line=prompt(">>> "):
	try:
		result = interpreter.Eval(line)
		for error in result.Errors:
			print("ERROR: ${error.Message}")
	except x:
		print(x)


cleaning up prepare ast step

cleaning up prepare ast step

namespace BooJs.Compiler.Steps

import Boo.Lang.Compiler
from Boo.Lang.Compiler.Ast import *
from Boo.Lang.Compiler.Steps import AbstractTransformerCompilerStep, IntroduceModuleClasses

from Boo.Lang.Environments import EnvironmentProvision
from Boo.Lang.Compiler.TypeSystem import EntityType
from Boo.Lang.Compiler.TypeSystem.Services import RuntimeMethodCache as BooRuntimeMethodCache
from Boo.Lang.Compiler.TypeSystem.Reflection import ExternalType
from BooJs.Compiler.TypeSystem import RuntimeMethodCache
from BooJs.Lang.Extensions import TransformAttribute, VarArgsAttribute

from System.Runtime.CompilerServices import CompilerGeneratedAttribute, CompilerGlobalScopeAttribute

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

        - Remove unneeded nodes
        - Convert builtins and runtime references

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

    [getter(BooMethodCache)]
    private _booMethodCache as BooRuntimeMethodCache

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

        if anode = node as INodeWithAttributes:
            targetAttr = TypeSystemServices.Map(T)
            for attr in anode.Attributes:
                ctor = TypeSystemServices.GetEntity(attr) as TypeSystem.IConstructor
                if ctor and targetAttr == ctor.DeclaringType:
                    return true

        return false

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

    protected def IsCompilerGenerated(node as Node):
        return HasAttribute[of CompilerGeneratedAttribute](node)

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

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

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

        return false

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

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

    def EnterModule(node as Module):
        if IsCompilerGenerated(node):
            RemoveCurrentNode()
            return false

        # TODO: Is this still needed?
        if node.Name == 'CompilerGenerated':
            RemoveCurrentNode()
            return false

        return true

    def EnterClassDefinition(node as ClassDefinition):
        if IsCompilerGenerated(node):
            RemoveCurrentNode()
            return false

        # TODO: Is this still needed?
        if node.Name == 'CompilerGeneratedExtensions':
            RemoveCurrentNode()
            return false

        return true

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

        # Ignore initialization value calls (__initobj__). We do it elsewhere.
        mie = node.Expression as MethodInvocationExpression
        if mie and mie.Target.Entity == TypeSystem.BuiltinFunction.InitValueType:
            RemoveCurrentNode
            return

        super(node)

    protected def ProcessReference(node as ReferenceExpression) as Node:
        # Primitive type references
        if entity = node.Entity as ExternalType and TypeSystemServices.IsLiteralPrimitive(entity):
            return StringLiteralExpression(node.LexicalInfo, entity.FullName)

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

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

        # Members of the module are placed in the top scope
        intEntity = node.Entity as TypeSystem.IInternalEntity
        if intEntity and intEntity.EntityType in (EntityType.Type, EntityType.Constructor):
            # Detect module class
            if HasAttribute[of CompilerGlobalScopeAttribute](intEntity.Node):
                node.Name = 'exports'
                return node
            # Prefix type references in the same module with exports
            if intEntity.Node.LexicalInfo.FullPath == node.LexicalInfo.FullPath:
                mre = MemberReferenceExpression(
                    node.LexicalInfo,
                    Target: [| exports |],
                    Name: node.Name,
                    Entity: node.Entity,
                    ExpressionType: node.ExpressionType
                )
                return mre

        # Special handling for extension methods
        intMethod = node.Entity as TypeSystem.Internal.InternalMethod
        if intMethod and intMethod.IsExtension:
            if intMethod.Node.LexicalInfo.FullPath == node.LexicalInfo.FullPath:
                node.Name = 'exports.' + intMethod.Name
                return node

        return node

    def OnReferenceExpression(node as ReferenceExpression):
        # Apply Transform attribute metadata to generate the output AST node
        if TransformAttribute.HasAttribute(node):
            result = TransformAttribute.Resolve(node, null)
            ReplaceCurrentNode Visit(result)
            return

        ReplaceCurrentNode ProcessReference(node)

    def OnMemberReferenceExpression(node as MemberReferenceExpression):
        # Apply Transform attribute metadata to generate the output AST node
        if TransformAttribute.HasAttribute(node):
            result = TransformAttribute.Resolve(node, null)
            Visit result
            ReplaceCurrentNode result
            return

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

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

        super(node)

    def OnMethodInvocationExpression(node as MethodInvocationExpression):
        # Apply Transform attribute metadata to generate the output AST node
        if TransformAttribute.HasAttribute(node.Target):
            result = TransformAttribute.Resolve(node.Target, node.Arguments)
            result = VisitNode(result)
            ReplaceCurrentNode result
            return

        # Handle vararg attribute
        if HasAttribute[of VarArgsAttribute](node.Target) and not node.ContainsAnnotation('varargs-processed'):
            node['varargs-processed'] = true
            // Common case where the only param is already an array
            if len(node.Arguments) == 1 and node.Arguments.Last isa ListLiteralExpression:
                last = node.Arguments.Last as ListLiteralExpression
                node.Arguments.Remove(last)
                for itm in last.Items:
                    node.Arguments.Add(itm)

                Visit(node.Arguments)
                return

            // Use .apply to call the method: ref.apply(target, [one, two].concat(params))
            params = ListLiteralExpression()
            for i in range(0, len(node.Arguments)-1):
                params.Items.Add( node.Arguments[i] )
            mie = [| $(params).concat($(node.Arguments.Last)) |]
            if node.Target isa MemberReferenceExpression:
                target = (node.Target as MemberReferenceExpression).Target
            else:
                target = [| Boo.UNDEF |]
            node = [| $(node.Target).apply($target, $mie) |]
            ReplaceCurrentNode node

        /*
        rts = TypeSystemServices.RuntimeType
        rtsInvoke = ResolveMethod(rts, 'Invoke')
        rtsInvokeCallable = ResolveMethod(rts, 'InvokeCallable')
        rtsInvokeBinaryOperator = ResolveMethod(rts, 'InvokeBinaryOperator')
        rtsInvokeUnaryOperator = ResolveMethod(rts, 'InvokeUnaryOperator')
        rtsSetProperty = ResolveMethod(rts, 'SetProperty')
        rtsGetProperty = ResolveMethod(rts, 'GetProperty')
        rtsSetSlice = ResolveMethod(rts, 'SetSlice')
        rtsGetSlice = ResolveMethod(rts, 'GetSlice')
        */

        # Undo binary operator overloads
        if node.Target.Entity == MethodCache.InvokeBinaryOperator:

            # Convert it back into a binary expression
            be = BinaryExpression(node.LexicalInfo)
            be.ExpressionType = node.ExpressionType
            be.Left = node.Arguments[1]
            be.Right = node.Arguments[2]

            # Remove the op_ to find the operator type
            operator = (node.Arguments[0] as StringLiteralExpression).Value[3:]
            be.Operator = System.Enum.Parse(BinaryOperatorType, operator)

            ReplaceCurrentNode Visit(be)
            return

        # Undo unary operator overloads
        if node.Target.Entity == MethodCache.InvokeUnaryOperator:
            # Convert it back into a unary expression
            ue = UnaryExpression(node.LexicalInfo)
            ue.ExpressionType = node.ExpressionType
            ue.Operand = node.Arguments[1]

            # Remove the op_ to find the operator type
            operator = (node.Arguments[0] as StringLiteralExpression).Value[3:]
            ue.Operator = System.Enum.Parse(UnaryOperatorType, operator)

            ReplaceCurrentNode Visit(ue)
            return

        super(node)

    protected def LocalsToDecls(locals as LocalCollection):
        # Convert locals back to simple declaration statements
        found = ['$locals']
        for local in locals:
            # Avoid duplicates
            if local.Name is null or local.Name in found:
                continue

            found.Push(local.Name)

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

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

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

            yield DeclarationStatement(LexicalInfo: local.LexicalInfo, Declaration: decl, Initializer: initializer)

    def OnBlockExpression(node as BlockExpression):
    """ Convert locals annotation to declarations.
        Boo AST does not allow to attach locals to block expressions but we
        need them in some cases, here we convert those local nodes to variable
        declarations.
    """
        if node.ContainsAnnotation('locals'):
            for st in LocalsToDecls(node['locals']):
                node.Body.Insert(0, st)
            node.RemoveAnnotation('locals')

        Visit node.Body

    def EnterMethod(node as Method):
        # Skip compiler generated methods
        if node.IsSynthetic and node.IsInternal:
            RemoveCurrentNode
            return false
        return true

    def OnMethod(node as Method):
    """ Process locals and detect the Main method to move its statements into the Module globals
    """
        for st in LocalsToDecls(node.Locals):
            node.Body.Insert(0, st)

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

        super(node)

    def IsEntryPoint(node as Method):
        # Note: We cannot use ContextAnnotations.GetEntryPoint(Context) to detect
        # the entry point since when emitting the assembly and loading it again
        # we might have to clone the AST and then the node instance is different.
        # TODO: Is this still the case?
        return node.Name == IntroduceModuleClasses.EntryPointMethodName \
               and node.IsSynthetic and node.IsStatic and node.IsPrivate

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

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

    protected def ProcessCast(mie as MethodInvocationExpression, type as Node) as MethodInvocationExpression:
        if not type isa Expression:
            type = TypeofExpression(type.LexicalInfo, type)

        mie.Arguments.Add(type)
        return Visit(mie)

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

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

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

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

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

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

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

        node.Value = re + modifier

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

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

        ReplaceCurrentNode ifst

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

        super(node)

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

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

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

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

        elif node.Type isa ArrayTypeReference:

            refe = CodeBuilder.CreateReference(node.LexicalInfo, TypeSystemServices.ArrayType)
            ReplaceCurrentNode ProcessReference(refe)
            return

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

    static _negated_binary_ops = {
        BinaryOperatorType.Equality: BinaryOperatorType.Inequality,
        BinaryOperatorType.Inequality: BinaryOperatorType.Equality,
        BinaryOperatorType.GreaterThan: BinaryOperatorType.LessThanOrEqual,
        BinaryOperatorType.GreaterThanOrEqual: BinaryOperatorType.LessThan,
        BinaryOperatorType.LessThan: BinaryOperatorType.GreaterThanOrEqual,
        BinaryOperatorType.LessThanOrEqual: BinaryOperatorType.GreaterThan,
    }

    def OnUnaryExpression(node as UnaryExpression):
    """ Fold logical not expressions to simplify the generated code. This is
        specially useful for normalizing `unless` conditions converted to `if not`
    """
        if node.Operator == UnaryOperatorType.LogicalNot:
            # not(not(expr)) -> expr
            if ue = node.Operand as UnaryExpression and ue.Operator == UnaryOperatorType.LogicalNot:
                ReplaceCurrentNode Visit(ue.Operand)
                return

            # not i != 10 -> i == 10
            if be = node.Operand as BinaryExpression:
                if be.Operator in _negated_binary_ops:
                    be.Operator = _negated_binary_ops[be.Operator]
                    ReplaceCurrentNode Visit(be)
                    return

        super(node)

    def OnBlock(node as Block):
    """ Flatten nested blocks. During the code transformation steps we may
        have introduced nested blocks as statements.
    """
        Visit node.Statements

        ofs = 0
        while ofs < len(node.Statements):
            st = node.Statements[ofs]
            if st.NodeType == NodeType.Block:
                node.Statements.Remove(st)
                for st2 in (st as Block).Statements:
                    node.Statements.Insert(ofs, st2)
                    ofs++
            else:
                ofs++

namespace BooJs.Compiler.Steps

import Boo.Lang.Compiler
from Boo.Lang.Compiler.Ast import *
from Boo.Lang.Compiler.Steps import AbstractTransformerCompilerStep, IntroduceModuleClasses

from Boo.Lang.Environments import EnvironmentProvision
from Boo.Lang.Compiler.TypeSystem import EntityType
from Boo.Lang.Compiler.TypeSystem.Services import RuntimeMethodCache as BooRuntimeMethodCache
from Boo.Lang.Compiler.TypeSystem.Reflection import ExternalType
from BooJs.Compiler.TypeSystem import RuntimeMethodCache
from BooJs.Lang.Extensions import TransformAttribute, VarArgsAttribute

from System.Runtime.CompilerServices import CompilerGeneratedAttribute

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

        - Remove unneeded nodes
        - Convert builtins and runtime references

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

    [getter(BooMethodCache)]
    private _booMethodCache as BooRuntimeMethodCache

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

        if anode = node as INodeWithAttributes:
            targetAttr = TypeSystemServices.Map(T)
            for attr in anode.Attributes:
                ctor = TypeSystemServices.GetEntity(attr) as TypeSystem.IConstructor
                if ctor and targetAttr == ctor.DeclaringType:
                    return true

        return false

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

    protected def IsCompilerGenerated(node as Node):
        return HasAttribute[of CompilerGeneratedAttribute](node)

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

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

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

        return false

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

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

    def EnterModule(node as Module):
        if IsCompilerGenerated(node):
            RemoveCurrentNode()
            return false

        # TODO: Is this still needed?
        if node.Name == 'CompilerGenerated':
            RemoveCurrentNode()
            return false

        return true

    def EnterClassDefinition(node as ClassDefinition):
        if IsCompilerGenerated(node):
            RemoveCurrentNode()
            return false

        # TODO: Is this still needed?
        if node.Name == 'CompilerGeneratedExtensions':
            RemoveCurrentNode()
            return false

        return true

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

        # Ignore initialization value calls (__initobj__). We do it elsewhere.
        mie = node.Expression as MethodInvocationExpression
        if mie and mie.Target.Entity == TypeSystem.BuiltinFunction.InitValueType:
            RemoveCurrentNode
            return

        super(node)

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

        # Primitive type references
        if entity = node.Entity as ExternalType and TypeSystemServices.IsLiteralPrimitive(entity):
            return StringLiteralExpression(node.LexicalInfo, entity.FullName)

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

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

        # Members of the module are placed in the top scope
        intEntity = node.Entity as TypeSystem.IInternalEntity
        if intEntity and intEntity.EntityType in (EntityType.Type, EntityType.Constructor):
            # HACK: We should check for the Module attribute but I'm lazy right now :(
            if intEntity.Name.EndsWith('Module'):
                node.Name = 'exports'
                return node
            # Prefix type references in the same module with exports
            if intEntity.Node.LexicalInfo.FullPath == node.LexicalInfo.FullPath:
                mre = MemberReferenceExpression(
                    node.LexicalInfo,
                    Target: [| exports |],
                    Name: node.Name,
                    Entity: node.Entity,
                    ExpressionType: node.ExpressionType
                )
                return mre

        # Special handling for extension methods
        intMethod = node.Entity as TypeSystem.Internal.InternalMethod
        if intMethod and intMethod.IsExtension:
            if intMethod.Node.LexicalInfo.FullPath == node.LexicalInfo.FullPath:
                node.Name = 'exports.' + intMethod.Name
                return node

        return node

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

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

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

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

        super(node)

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

        if HasAttribute[of VarArgsAttribute](node.Target) and not node.ContainsAnnotation('varargs-processed'):
            node['varargs-processed'] = true
            // Common case where the only param is already an array
            if len(node.Arguments) == 1 and node.Arguments.Last isa ListLiteralExpression:
                last = node.Arguments.Last as ListLiteralExpression
                node.Arguments.Remove(last)
                for itm in last.Items:
                    node.Arguments.Add(itm)

                Visit(node.Arguments)
                return

            // Use .apply to call the method: ref.apply(target, [one, two].concat(params))
            params = ListLiteralExpression()
            for i in range(0, len(node.Arguments)-1):
                params.Items.Add( node.Arguments[i] )
            mie = [| $(params).concat($(node.Arguments.Last)) |]
            if node.Target isa MemberReferenceExpression:
                target = (node.Target as MemberReferenceExpression).Target
            else:
                target = [| Boo.UNDEF |]
            node = [| $(node.Target).apply($target, $mie) |]
            ReplaceCurrentNode node

        /*
        rts = TypeSystemServices.RuntimeType
        rtsInvoke = ResolveMethod(rts, 'Invoke')
        rtsInvokeCallable = ResolveMethod(rts, 'InvokeCallable')
        rtsInvokeBinaryOperator = ResolveMethod(rts, 'InvokeBinaryOperator')
        rtsInvokeUnaryOperator = ResolveMethod(rts, 'InvokeUnaryOperator')
        rtsSetProperty = ResolveMethod(rts, 'SetProperty')
        rtsGetProperty = ResolveMethod(rts, 'GetProperty')
        rtsSetSlice = ResolveMethod(rts, 'SetSlice')
        rtsGetSlice = ResolveMethod(rts, 'GetSlice')
        */

        if node.Target.Entity == MethodCache.InvokeBinaryOperator:

            # Convert it back into a binary expression
            be = BinaryExpression(node.LexicalInfo)
            be.ExpressionType = node.ExpressionType
            be.Left = node.Arguments[1]
            be.Right = node.Arguments[2]

            # Remove the op_ to find the operator type
            operator = (node.Arguments[0] as StringLiteralExpression).Value[3:]
            be.Operator = System.Enum.Parse(BinaryOperatorType, operator)

            ReplaceCurrentNode Visit(be)
            return

        if node.Target.Entity == MethodCache.InvokeUnaryOperator:
            # Convert it back into a unary expression
            ue = UnaryExpression(node.LexicalInfo)
            ue.ExpressionType = node.ExpressionType
            ue.Operand = node.Arguments[1]

            # Remove the op_ to find the operator type
            operator = (node.Arguments[0] as StringLiteralExpression).Value[3:]
            ue.Operator = System.Enum.Parse(UnaryOperatorType, operator)

            ReplaceCurrentNode Visit(ue)
            return

        super(node)

    protected def LocalsToDecls(locals as LocalCollection):
        # Convert locals back to simple declaration statements
        found = ['$locals']
        for local in locals:
            # Avoid duplicates
            if local.Name is null or local.Name in found:
                continue

            found.Push(local.Name)

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

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

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

            yield DeclarationStatement(LexicalInfo: local.LexicalInfo, Declaration: decl, Initializer: initializer)

    def OnBlockExpression(node as BlockExpression):
    """ Convert locals annotation to declarations
    """
        if node.ContainsAnnotation('locals'):
            for st in LocalsToDecls(node['locals']):
                node.Body.Insert(0, st)
            node.RemoveAnnotation('locals')

        Visit node.Body

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

        for st in LocalsToDecls(node.Locals):
            node.Body.Insert(0, st)

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

        super(node)

    def IsEntryPoint(node as Method):
        # Note: We cannot use ContextAnnotations.GetEntryPoint(Context) to detect
        # the entry point since when emitting the assembly we have to clone the AST
        # and then the node instance is different.
        return node.Name == IntroduceModuleClasses.EntryPointMethodName \
               and node.IsSynthetic and node.IsStatic and node.IsPrivate

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

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

    protected def ProcessCast(mie as MethodInvocationExpression, type as Node) as MethodInvocationExpression:
        if not type isa Expression:
            type = TypeofExpression(type.LexicalInfo, type)

        mie.Arguments.Add(type)
        return Visit(mie)

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

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

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

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

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

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

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

        node.Value = re + modifier

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

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

        ReplaceCurrentNode ifst

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

        super(node)

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

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

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

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

        elif node.Type isa ArrayTypeReference:

            refe = CodeBuilder.CreateReference(node.LexicalInfo, TypeSystemServices.ArrayType)
            ReplaceCurrentNode ProcessReference(refe)
            return

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

    static _negated_binary_ops = {
        BinaryOperatorType.Equality: BinaryOperatorType.Inequality,
        BinaryOperatorType.Inequality: BinaryOperatorType.Equality,
        BinaryOperatorType.GreaterThan: BinaryOperatorType.LessThanOrEqual,
        BinaryOperatorType.GreaterThanOrEqual: BinaryOperatorType.LessThan,
        BinaryOperatorType.LessThan: BinaryOperatorType.GreaterThanOrEqual,
        BinaryOperatorType.LessThanOrEqual: BinaryOperatorType.GreaterThan,
    }

    def OnUnaryExpression(node as UnaryExpression):
    """ Fold logical not expressions to simplify the generated code. This is
        specially useful for normalizing `unless` conditions converted to `if not`
    """
        if node.Operator == UnaryOperatorType.LogicalNot:
            # not(not(expr)) -> expr
            if ue = node.Operand as UnaryExpression and ue.Operator == UnaryOperatorType.LogicalNot:
                ReplaceCurrentNode Visit(ue.Operand)
                return

            # not i != 10 -> i == 10
            if be = node.Operand as BinaryExpression:
                if be.Operator in _negated_binary_ops:
                    be.Operator = _negated_binary_ops[be.Operator]
                    ReplaceCurrentNode Visit(be)
                    return

        super(node)

    def OnBlock(node as Block):
    """ Flatten nested blocks. During the code transformation steps we may
        have introduced nested blocks as statements.
    """
        Visit node.Statements

        ofs = 0
        while ofs < len(node.Statements):
            st = node.Statements[ofs]
            if st.NodeType == NodeType.Block:
                node.Statements.Remove(st)
                for st2 in (st as Block).Statements:
                    node.Statements.Insert(ofs, st2)
                    ofs++
            else:
                ofs++


Fixed bug in import processor

Fixed bug in import processor

namespace BooJs.Compiler.Steps

import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.Steps
import Boo.Lang.Compiler.TypeSystem(EntityType)
import Boo.Lang.Compiler.TypeSystem.Reflection(ExternalType)

import BooJs.Compiler.Utils

class ProcessImports(AbstractTransformerCompilerStep):
"""
    Process imports

    Wraps the module in an AMD style loader, placing any referenced namespaces as imports
"""
    _mappings = {}
    _asmrefs = {}
    _nsidx = 0

    protected def FindNamespace(fqn as string) as string:
        parts = fqn.Split(char('.'))
        while len(parts):
            type = NameResolutionService.ResolveQualifiedName(join(parts, '.'))
            if type.EntityType in (EntityType.Namespace,):
                return type.FullName
            parts = parts[:-1]
        return null

    protected def MapNamespace(ns as string, alias as string, asmref as ReferenceExpression):
        actualns = FindNamespace(ns)
        if actualns != ns:
            alias = null

        if not alias:
            alias = 'NS' + _nsidx++

        _mappings[actualns] = alias
        if asmref:
            _asmrefs[actualns] = asmref.Name

    def OnModule(node as Module):
        # Makes sure we visit the imports first
        Visit node.Imports
        Visit node.Members
        Visit node.Globals

        # Annotate the module with the reported namespace mappings
        # TODO: Avoid the annotations by converting this step into a simple visitor
        #       used from the printer
        node.Annotate('nsmapping', _mappings)
        node.Annotate('nsasmrefs', _asmrefs)

        # Reset for next module
        _mappings.Clear()
        _asmrefs.Clear()
        _nsidx = 0

    def OnImport(node as Import):
        if mie = node.Expression as MethodInvocationExpression:
            for arg as ReferenceExpression in mie.Arguments:
                fqn = node.Namespace + '.' + arg.Name
                continue if isExtern(fqn)

                if node.Alias:
                    MapNamespace(node.Namespace + '.' + arg.Name, null, node.AssemblyReference)
                else:
                    MapNamespace(node.Namespace + '.' + arg.Name, arg.Name, node.AssemblyReference)

        elif not isExtern(node.Namespace):
            if node.Alias:
                MapNamespace(node.Namespace, node.Alias.Name, node.AssemblyReference)
            else:
                MapNamespace(node.Namespace, null, node.AssemblyReference)

    def OnReferenceExpression(node as ReferenceExpression):
        # Only process external types
        if not node.Entity isa ExternalType:
            return

        parts = node.Name.Split(char('.'))
        rhs = []
        while len(parts):
            name = join(parts, '.')
            if name in _mappings:
                rhs.Insert(0, _mappings[name])
                node.Name = join(rhs, '.')
                break

            # Detect module classes to skip them
            if not IsModule(name):
                rhs.Add(parts[-1])

            parts = parts[:-1]

    protected def IsModule(fqn as string) as bool:
        try:
            type = NameResolutionService.ResolveQualifiedName(fqn) as ExternalType
            return type and type.IsClass and type.IsFinal and type.Name =~ /^\w+Module$/
        except:
            return false

namespace BooJs.Compiler.Steps

import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.Steps
import Boo.Lang.Compiler.TypeSystem(EntityType)
import Boo.Lang.Compiler.TypeSystem.Reflection(ExternalType)

import BooJs.Compiler.Utils

class ProcessImports(AbstractTransformerCompilerStep):
"""
    Process imports

    Wraps the module in an AMD style loader, placing any referenced namespaces as imports
"""
    _mappings = {}
    _asmrefs = {}
    _nsidx = 0

    protected def FindNamespace(fqn as string) as string:
        parts = fqn.Split(char('.'))
        while len(parts):
            type = NameResolutionService.ResolveQualifiedName(join(parts, '.'))
            if type.EntityType in (EntityType.Namespace,):
                return type.FullName
            parts = parts[:-1]
        return null

    protected def MapNamespace(ns as string, alias as string, asmref as ReferenceExpression):
        actualns = FindNamespace(ns)
        if actualns != ns:
            alias = null

        if not alias:
            alias = 'NS' + _nsidx++

        _mappings[actualns] = alias
        if asmref:
            _asmrefs[actualns] = asmref.Name

    def OnModule(node as Module):
        # Makes sure we visit the imports first
        Visit node.Imports
        Visit node.Members
        Visit node.Globals

        # Annotate the module with the reported namespace mappings
        # TODO: Avoid the annotations by converting this step into a simple visitor
        #       used from the printer
        node.Annotate('nsmapping', _mappings)
        node.Annotate('nsasmrefs', _asmrefs)

    def OnImport(node as Import):
        if mie = node.Expression as MethodInvocationExpression:
            for arg as ReferenceExpression in mie.Arguments:
                fqn = node.Namespace + '.' + arg.Name
                continue if isExtern(fqn)

                if node.Alias:
                    MapNamespace(node.Namespace + '.' + arg.Name, null, node.AssemblyReference)
                else:
                    MapNamespace(node.Namespace + '.' + arg.Name, arg.Name, node.AssemblyReference)

        elif not isExtern(node.Namespace):
            if node.Alias:
                MapNamespace(node.Namespace, node.Alias.Name, node.AssemblyReference)
            else:
                MapNamespace(node.Namespace, null, node.AssemblyReference)

    def OnReferenceExpression(node as ReferenceExpression):
        # Only process external types
        if not node.Entity isa ExternalType:
            return

        parts = node.Name.Split(char('.'))
        rhs = []
        while len(parts):
            name = join(parts, '.')
            if name in _mappings:
                rhs.Insert(0, _mappings[name])
                node.Name = join(rhs, '.')
                break

            # Detect module classes to skip them
            if not IsModule(name):
                rhs.Add(parts[-1])

            parts = parts[:-1]

    protected def IsModule(fqn as string) as bool:
        type = NameResolutionService.ResolveQualifiedName(fqn) as ExternalType
        return type and type.IsClass and type.IsFinal and type.Name =~ /^\w+Module$/


test has no business depending on CompilerContext

"""
hooray!
"""
import Boo.Lang.Environments

class ArbitraryService:
	override def ToString():
		return "hooray!"

Environment.With(ClosedEnvironment(ArbitraryService())):
	assert my(ArbitraryService) is my(ArbitraryService)
	print my(ArbitraryService)

"""
hooray!
"""
import Boo.Lang.Compiler
import Boo.Lang.Environments

class ArbitraryService:
	override def ToString():
		return "hooray!"

Environment.With(CompilerContext()):
	assert my(ArbitraryService) is my(ArbitraryService)
	print my(ArbitraryService)


StartColumn -> Column

StartColumn -> Column

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

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

namespace BooExplorer

import WeifenLuo.WinFormsUI
import System
import System.IO
import System.Windows.Forms
import System.Drawing
import Boo.Lang.Compiler

class TaskList(DockContent):

	_list as ListView
	_main as MainForm

	def constructor(main as MainForm):
		_main = main
		_list = ListView(Dock: DockStyle.Fill,
						View: View.Details,
						FullRowSelect: true,
						GridLines: true,
						Click: _list_Click)

		_list.Columns.Add("line"       , 50, HorizontalAlignment.Left)
		_list.Columns.Add("column"     , 50, HorizontalAlignment.Left)
		_list.Columns.Add("code"       , 75, HorizontalAlignment.Left)
		_list.Columns.Add("description", 500, HorizontalAlignment.Left)
		_list.Columns.Add("module"     , 150, HorizontalAlignment.Left)

		SuspendLayout()

		Controls.Add(_list)
		self.HideOnClose = true
		self.AllowRedocking = true
		self.DockableAreas = (
					DockAreas.Float |
					DockAreas.DockBottom |
					DockAreas.DockTop |
					DockAreas.DockLeft |
					DockAreas.DockRight)
		self.ClientSize = System.Drawing.Size(295, 347)
		self.ShowHint = DockState.DockBottom
		self.Text = "Task List"

		ResumeLayout(false)

	def Clear():
		_list.Items.Clear()

	def Update(errors as CompilerErrorCollection):
		_list.BeginUpdate()
		_list.Items.Clear()
		try:
			for error in errors:
				self.AddCompilerError(error)
		ensure:
			_list.EndUpdate()

	def AddCompilerError(error as CompilerError):
		item = _list.Items.Add(error.LexicalInfo.Line.ToString())
		item.SubItems.AddRange((
				error.LexicalInfo.Column.ToString(),
				error.Code,
				error.Message,
				error.LexicalInfo.FileName))
		item.Tag = error

	def _list_Click(sender, args as EventArgs):
		selected = _list.SelectedItems
		return unless len(selected) > 0

		error as CompilerError = selected[0].Tag
		fname = error.LexicalInfo.FileName
		if File.Exists(fname):
			document as BooEditor = _main.OpenDocument(fname)
			document.GoTo(error.LexicalInfo.Line-1)

	override protected def GetPersistString():
		return "TaskList|"

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

namespace BooExplorer

import WeifenLuo.WinFormsUI
import System
import System.IO
import System.Windows.Forms
import System.Drawing
import Boo.Lang.Compiler

class TaskList(DockContent):

	_list as ListView
	_main as MainForm

	def constructor(main as MainForm):
		_main = main
		_list = ListView(Dock: DockStyle.Fill,
						View: View.Details,
						FullRowSelect: true,
						GridLines: true,
						Click: _list_Click)

		_list.Columns.Add("line"       , 50, HorizontalAlignment.Left)
		_list.Columns.Add("column"     , 50, HorizontalAlignment.Left)
		_list.Columns.Add("code"       , 75, HorizontalAlignment.Left)
		_list.Columns.Add("description", 500, HorizontalAlignment.Left)
		_list.Columns.Add("module"     , 150, HorizontalAlignment.Left)

		SuspendLayout()

		Controls.Add(_list)
		self.HideOnClose = true
		self.AllowRedocking = true
		self.DockableAreas = (
					DockAreas.Float |
					DockAreas.DockBottom |
					DockAreas.DockTop |
					DockAreas.DockLeft |
					DockAreas.DockRight)
		self.ClientSize = System.Drawing.Size(295, 347)
		self.ShowHint = DockState.DockBottom
		self.Text = "Task List"

		ResumeLayout(false)

	def Clear():
		_list.Items.Clear()

	def Update(errors as CompilerErrorCollection):
		_list.BeginUpdate()
		_list.Items.Clear()
		try:
			for error in errors:
				self.AddCompilerError(error)
		ensure:
			_list.EndUpdate()

	def AddCompilerError(error as CompilerError):
		item = _list.Items.Add(error.LexicalInfo.Line.ToString())
		item.SubItems.AddRange((
				error.LexicalInfo.StartColumn.ToString(),
				error.Code,
				error.Message,
				error.LexicalInfo.FileName))
		item.Tag = error

	def _list_Click(sender, args as EventArgs):
		selected = _list.SelectedItems
		return unless len(selected) > 0

		error as CompilerError = selected[0].Tag
		fname = error.LexicalInfo.FileName
		if File.Exists(fname):
			document as BooEditor = _main.OpenDocument(fname)
			document.GoTo(error.LexicalInfo.Line-1)

	override protected def GetPersistString():
		return "TaskList|"


Added dummy CodeCompletion: only for "self."

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

namespace BooBinding.CodeCompletion

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

class Resolver:
	static _typeKeywords = ("bool", "void",
	                        "single", "double",
	                        "byte", "short", "ushort", "int", "uint", "long", "ulong",
	                        "object", "string", "regex", "array", "hash", "list", "type")

	def CtrlSpace(parserService as IParserService, caretLine as int, caretColumn as int, fileName as string) as ArrayList:
		result = ArrayList(_typeKeywords)
		//parseInfo = parserService.GetParseInformation(fileName)
		//cu = parseInfo.MostRecentCompilationUnit as CompilationUnit
		//if cu != null:
		//	curClass = parserService.GetInnermostClass(cu, caretLine, caretColumn) as Class
		//	if
		return result

	def Resolve(parserService as IParserService, expression as string, caretLine as int, caretColumn as int, fileName as string, fileContent as string) as ResolveResult:
		if expression == null or expression == '':
			return null

		try:
			int.Parse(expression)
			return null
		except exception as Exception:
			pass

		//self._caretLine = caretLineNumber
		//self._caretColumn = caretColumn
		//self._parserService = parserService
		parseInfo = parserService.GetParseInformation(fileName)
		cu = parseInfo.MostRecentCompilationUnit as CompilationUnit
		if cu == null:
			print "BooResolver: No parse information!"
			return null

		callingClass as Class = parserService.GetInnermostClass(cu, caretLine, caretColumn)
		returnClass as Class = null
		if callingClass == null:
			print "BooResolver: CallingClass not found!"
			return null
		if expression == "self":
			returnClass = callingClass
		return null if returnClass == null
		return ResolveResult(returnClass, parserService.ListMembers(ArrayList(), returnClass, callingClass, false))

namespace BooBinding.CodeCompletion

import System
import System.Collections
import System.Diagnostics
import System.IO
import ICSharpCode.SharpDevelop.Services
import ICSharpCode.SharpDevelop.Internal.Project
import SharpDevelop.Internal.Parser
import Boo.Lang.Compiler
import Boo.Lang.Compiler.IO
import Boo.Lang.Compiler.Pipelines
import Boo.Lang.Compiler.Steps

class Resolver:
	def CtrlSpace(parserService as IParserService, caretLine as int, caretColumn as int, fileName as string) as ArrayList:
		return ArrayList()

	def Resolve(parserService as IParserService, expression as string, caretLineNumber as int, caretColumn as int, fileName as string, fileContent as string) as ResolveResult:
		return null


exceptions are now printed to the output pane

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

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

namespace BooExplorer

import ICSharpCode.TextEditor
import ICSharpCode.TextEditor.Document
import ICSharpCode.TextEditor.Actions

import WeifenLuo.WinFormsUI
import System
import System.IO
import System.ComponentModel
import System.Windows.Forms
import System.Drawing
import Boo.Lang.Compiler
import Boo.Lang.Compiler.IO
import Boo.Lang.Compiler.Pipelines
import Boo.Lang.Compiler.Ast
import BooExplorer.Common

class WaitCursor(IDisposable):
	_saved
	_form as Form

	def constructor([required] form as Form):
		_form = form
		_saved = form.Cursor
		form.Cursor = Cursors.WaitCursor

	def Dispose():
		_form.Cursor = _saved

class BooEditor(DockContent):

	_editor as BooxTextAreaControl

	[getter(Main)]
	_main as MainForm

	[getter(FileName)]
	_fname as string

	[getter(IsDirty)]
	_dirty = false

	_moduleDirty = true

	[getter(Module)]
	_module as Module

	_compiler as BooCompiler

	def constructor(main as MainForm):
		_main = main
		settings = main.Settings
		_editor = BooxTextAreaControl(Dock: DockStyle.Fill,
							Font: settings.TextFont,
							EnableFolding: settings.EnableFolding,
							ShowLineNumbers: settings.ShowLineNumbers,
							ShowSpaces: settings.ShowSpaces,
							ShowTabs: settings.ShowTabs,
							ShowEOLMarkers: settings.ShowEOLMarkers,
							IndentStyle: settings.IndentStyle)

		_editor.Editor = self
		_editor.Encoding = System.Text.Encoding.UTF8
		_editor.Document.FormattingStrategy = BooFormattingStrategy()
		_editor.Document.HighlightingStrategy = GetBooHighlighting()
		_editor.Document.DocumentChanged += _editor_DocumentChanged

		SuspendLayout()
		Controls.Add(_editor)
		self.HideOnClose = false
		self.DockableAreas = DockAreas.Document
		self.Text = GetSafeFileName()
		self.DockPadding.All = 1
		self.Menu = CreateMenu()
		ResumeLayout(false)

	TextArea:
		get:
			return _editor.ActiveTextAreaControl.TextArea

	TextContent:
		get:
			return _editor.Document.TextContent

		set:
			_editor.Document.TextContent = value

	def GoTo(line as int):
		document = _editor.Document
		segment = document.GetLineSegment(line)
		wsLen = /\s*/.Match(document.GetText(segment)).Groups[0].Length
		_editor.ActiveTextAreaControl.JumpTo(line, wsLen)
		self.TextArea.Focus()
		self.TextArea.Select()

	def Save():
		if _fname:
			_editor.SaveFile(_fname)
			ClearDirtyFlag()
		else:
			SaveAs()

	def SaveAs():
		dlg = SaveFileDialog(AddExtension: true,
							DefaultExt: ".boo",
							OverwritePrompt: true,
							Filter: "boo files (*.boo)|*.boo")
		if DialogResult.OK == dlg.ShowDialog():
			_editor.SaveFile(dlg.FileName)
			_fname = dlg.FileName
			ClearDirtyFlag()

	def Open([required] fname as string):
		_editor.LoadFile(fname)
		_fname = fname
		ClearDirtyFlag()

	def ClearDirtyFlag():
		_dirty = false
		self.Text = _fname

	def _editor_DocumentChanged(sender, args as DocumentEventArgs):
		_moduleDirty = true
		if not _dirty:
			self.Text = "${GetSafeFileName()} (modified)"
			_dirty = true

	def _menuItemUndo_Click(sender, args as EventArgs):
		_editor.Undo()

	def _menuItemRedo_Click(sender, args as EventArgs):
		_editor.Redo()

	def _menuItemSplit_Click(sender, args as EventArgs):
		_editor.Split()

	def _menuItemRemoveTrailingWS_Click(sender, args as EventArgs):
		RemoveTrailingWS().Execute(TextArea)

	def _menuItemCut_Click(sender, args as EventArgs):
		Cut().Execute(TextArea)

	def _menuItemCopy_Click(sender, args as EventArgs):
		Copy().Execute(TextArea)

	def _menuItemPaste_Click(sender, args as EventArgs):
		Paste().Execute(TextArea)

	def _menuItemGoTo_Click(sender, args as EventArgs):
		dlg = PromptDialog(Text: GetSafeFileName(), Message: "Line number: ")
		if DialogResult.OK == dlg.ShowDialog():
			GoTo(int.Parse(dlg.Value)-1)

	def _menuItemRun_Click(sender, args as EventArgs):
		using WaitCursor(self):
			Run()

	def _menuItemExpand_Click(sender, args as EventArgs):
		using WaitCursor(self):
			Expand()

	private def Expand():
		_main.Expand(self.GetSafeFileName(), self.TextContent)

	private def Run():

		if _compiler is null:
			_compiler = BooCompiler()
			// enable duck typing
			_compiler.Parameters.Pipeline = CompileToMemory()
			_compiler.Parameters.References.Add(typeof(Form).Assembly)
			_compiler.Parameters.References.Add(typeof(System.Drawing.Size).Assembly)
			_compiler.Parameters.References.Add(System.Reflection.Assembly.GetExecutingAssembly())

		_compiler.Parameters.Input.Add(StringInput(GetSafeFileName(), self.TextContent))

		try:
			using console=ConsoleCapture():

				started = date.Now
				result = _compiler.Run()
				finished = date.Now
				_main.StatusText = "Compilation finished in ${finished-started} with ${len(result.Errors)} error(s)."

				_main.UpdateTaskList(result.Errors)

				if len(result.Errors):
					UpdateOutputPane(result.Errors.ToString(true))
				else:

					AppDomain_AssemblyResolve = def (sender, args as ResolveEventArgs):
						name = GetSimpleName(args.Name)
						compiledName = GetSimpleName(result.GeneratedAssembly.FullName)
						return result.GeneratedAssembly if name == compiledName

					current = AppDomain.CurrentDomain
					try:
						current.AssemblyResolve += AppDomain_AssemblyResolve
						result.GeneratedAssemblyEntryPoint.Invoke(null, (null,))
					except x:
						print(x)
					ensure:
						current.AssemblyResolve -= AppDomain_AssemblyResolve
					UpdateOutputPane(console.ToString())
		ensure:
			_compiler.Parameters.Input.Clear()

	private def GetSimpleName(name as string):
		return /,\s*/.Split(name)[0]

	def UpdateOutputPane(text as string):
		_main.OutputPane.SetBuildText(text)
		_main.ShowOutputPane() if len(text)

	def UpdateModule():
		return unless _moduleDirty

		fname = GetSafeFileName()
		code = self.TextContent
		try:
			_module = _main.ParseString(fname, code).Modules[0]
			_moduleDirty = false
		except x:
			print(x)

	override protected def OnClosing(args as CancelEventArgs):
		super(args)
		if (not args.Cancel) and _dirty:
			result = MessageBox.Show("Save changes to ${GetSafeFileName()}?",
									"File not saved",
									MessageBoxButtons.YesNoCancel)
			if DialogResult.Yes == result:
				Save()
			else:
				args.Cancel = DialogResult.Cancel == result

	def GetSafeFileName():
		return _fname if _fname
		return "untitled.boo"

	def CreateMenu():
		menu = MainMenu()

		edit = MenuItem(Text: "&Edit", MergeOrder: 1)
		edit.MenuItems.AddRange(
			(
				MenuItem("&Undo",
						Click: _menuItemUndo_Click,
						Shortcut: Shortcut.CtrlZ),
				MenuItem("&Redo",
						Click: _menuItemRedo_Click,
						Shortcut: Shortcut.CtrlY),
				MenuItem("-"),
				MenuItem("Cu&t",
						Click: _menuItemCut_Click,
						Shortcut: Shortcut.CtrlX),
				MenuItem("&Copy",
						Click: _menuItemCopy_Click,
						Shortcut: Shortcut.CtrlC),
				MenuItem("&Paste",
						Click: _menuItemPaste_Click,
						Shortcut: Shortcut.CtrlV),
				MenuItem("-"),
				MenuItem("&Go to line...",
						Shortcut: Shortcut.CtrlG,
						Click: _menuItemGoTo_Click),
				MenuItem("&Split", Click: _menuItemSplit_Click),
				MenuItem("Remove trailing whitespace", Click: _menuItemRemoveTrailingWS_Click)
			))

		tools = MenuItem(Text: "&Tools", MergeOrder: 2, MergeType: MenuMerge.MergeItems)
		tools.MenuItems.AddRange(
			(
				MenuItem(Text: "Run",
						Click: _menuItemRun_Click,
						Shortcut: Shortcut.F5),
				MenuItem(Text: "Expand",
						Click: _menuItemExpand_Click,
						Shortcut: Shortcut.CtrlE),
				MenuItem(Text: "-")
			))

		menu.MenuItems.AddRange((edit, tools))
		return menu

	def GetBooHighlighting():
		return HighlightingManager.Manager.FindHighlighter("boo")

	override protected def GetPersistString():
		return "BooEditor|${GetSafeFileName()}"

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

namespace BooExplorer

import ICSharpCode.TextEditor
import ICSharpCode.TextEditor.Document
import ICSharpCode.TextEditor.Actions

import WeifenLuo.WinFormsUI
import System
import System.IO
import System.ComponentModel
import System.Windows.Forms
import System.Drawing
import Boo.Lang.Compiler
import Boo.Lang.Compiler.IO
import Boo.Lang.Compiler.Pipelines
import Boo.Lang.Compiler.Ast
import BooExplorer.Common

class WaitCursor(IDisposable):
	_saved
	_form as Form

	def constructor([required] form as Form):
		_form = form
		_saved = form.Cursor
		form.Cursor = Cursors.WaitCursor

	def Dispose():
		_form.Cursor = _saved

class BooEditor(DockContent):

	_editor as BooxTextAreaControl

	[getter(Main)]
	_main as MainForm

	[getter(FileName)]
	_fname as string

	[getter(IsDirty)]
	_dirty = false

	_moduleDirty = true

	[getter(Module)]
	_module as Module

	_compiler as BooCompiler

	def constructor(main as MainForm):
		_main = main
		settings = main.Settings
		_editor = BooxTextAreaControl(Dock: DockStyle.Fill,
							Font: settings.TextFont,
							EnableFolding: settings.EnableFolding,
							ShowLineNumbers: settings.ShowLineNumbers,
							ShowSpaces: settings.ShowSpaces,
							ShowTabs: settings.ShowTabs,
							ShowEOLMarkers: settings.ShowEOLMarkers,
							IndentStyle: settings.IndentStyle)

		_editor.Editor = self
		_editor.Encoding = System.Text.Encoding.UTF8
		_editor.Document.FormattingStrategy = BooFormattingStrategy()
		_editor.Document.HighlightingStrategy = GetBooHighlighting()
		_editor.Document.DocumentChanged += _editor_DocumentChanged

		SuspendLayout()
		Controls.Add(_editor)
		self.HideOnClose = false
		self.DockableAreas = DockAreas.Document
		self.Text = GetSafeFileName()
		self.DockPadding.All = 1
		self.Menu = CreateMenu()
		ResumeLayout(false)

	TextArea:
		get:
			return _editor.ActiveTextAreaControl.TextArea

	TextContent:
		get:
			return _editor.Document.TextContent

		set:
			_editor.Document.TextContent = value

	def GoTo(line as int):
		document = _editor.Document
		segment = document.GetLineSegment(line)
		wsLen = /\s*/.Match(document.GetText(segment)).Groups[0].Length
		_editor.ActiveTextAreaControl.JumpTo(line, wsLen)
		self.TextArea.Focus()
		self.TextArea.Select()

	def Save():
		if _fname:
			_editor.SaveFile(_fname)
			ClearDirtyFlag()
		else:
			SaveAs()

	def SaveAs():
		dlg = SaveFileDialog(AddExtension: true,
							DefaultExt: ".boo",
							OverwritePrompt: true,
							Filter: "boo files (*.boo)|*.boo")
		if DialogResult.OK == dlg.ShowDialog():
			_editor.SaveFile(dlg.FileName)
			_fname = dlg.FileName
			ClearDirtyFlag()

	def Open([required] fname as string):
		_editor.LoadFile(fname)
		_fname = fname
		ClearDirtyFlag()

	def ClearDirtyFlag():
		_dirty = false
		self.Text = _fname

	def _editor_DocumentChanged(sender, args as DocumentEventArgs):
		_moduleDirty = true
		if not _dirty:
			self.Text = "${GetSafeFileName()} (modified)"
			_dirty = true

	def _menuItemUndo_Click(sender, args as EventArgs):
		_editor.Undo()

	def _menuItemRedo_Click(sender, args as EventArgs):
		_editor.Redo()

	def _menuItemSplit_Click(sender, args as EventArgs):
		_editor.Split()

	def _menuItemRemoveTrailingWS_Click(sender, args as EventArgs):
		RemoveTrailingWS().Execute(TextArea)

	def _menuItemCut_Click(sender, args as EventArgs):
		Cut().Execute(TextArea)

	def _menuItemCopy_Click(sender, args as EventArgs):
		Copy().Execute(TextArea)

	def _menuItemPaste_Click(sender, args as EventArgs):
		Paste().Execute(TextArea)

	def _menuItemGoTo_Click(sender, args as EventArgs):
		dlg = PromptDialog(Text: GetSafeFileName(), Message: "Line number: ")
		if DialogResult.OK == dlg.ShowDialog():
			GoTo(int.Parse(dlg.Value)-1)

	def _menuItemRun_Click(sender, args as EventArgs):
		using WaitCursor(self):
			Run()

	def _menuItemExpand_Click(sender, args as EventArgs):
		using WaitCursor(self):
			Expand()

	private def Expand():
		_main.Expand(self.GetSafeFileName(), self.TextContent)

	private def Run():

		if _compiler is null:
			_compiler = BooCompiler()
			// enable duck typing
			_compiler.Parameters.Pipeline = CompileToMemory()
			_compiler.Parameters.References.Add(typeof(Form).Assembly)
			_compiler.Parameters.References.Add(typeof(System.Drawing.Size).Assembly)
			_compiler.Parameters.References.Add(System.Reflection.Assembly.GetExecutingAssembly())

		_compiler.Parameters.Input.Add(StringInput(GetSafeFileName(), self.TextContent))

		try:
			using console=ConsoleCapture():

				started = date.Now
				result = _compiler.Run()
				finished = date.Now
				_main.StatusText = "Compilation finished in ${finished-started} with ${len(result.Errors)} error(s)."

				_main.UpdateTaskList(result.Errors)

				unless len(result.Errors):

					AppDomain_AssemblyResolve = def (sender, args as ResolveEventArgs):
						name = GetSimpleName(args.Name)
						compiledName = GetSimpleName(result.GeneratedAssembly.FullName)
						return result.GeneratedAssembly if name == compiledName

					current = AppDomain.CurrentDomain
					try:
						current.AssemblyResolve += AppDomain_AssemblyResolve
						result.GeneratedAssemblyEntryPoint.Invoke(null, (null,))
					except x:
						print(x)
					ensure:
						current.AssemblyResolve -= AppDomain_AssemblyResolve

				UpdateOutputPane(console.ToString())
		ensure:
			_compiler.Parameters.Input.Clear()

	private def GetSimpleName(name as string):
		return /,\s*/.Split(name)[0]

	def UpdateOutputPane(text as string):
		_main.OutputPane.SetBuildText(text)
		_main.ShowOutputPane() if len(text)

	def UpdateModule():
		return unless _moduleDirty

		fname = GetSafeFileName()
		code = self.TextContent
		try:
			_module = _main.ParseString(fname, code).Modules[0]
			_moduleDirty = false
		except x:
			print(x)

	override protected def OnClosing(args as CancelEventArgs):
		super(args)
		if (not args.Cancel) and _dirty:
			result = MessageBox.Show("Save changes to ${GetSafeFileName()}?",
									"File not saved",
									MessageBoxButtons.YesNoCancel)
			if DialogResult.Yes == result:
				Save()
			else:
				args.Cancel = DialogResult.Cancel == result

	def GetSafeFileName():
		return _fname if _fname
		return "untitled.boo"

	def CreateMenu():
		menu = MainMenu()

		edit = MenuItem(Text: "&Edit", MergeOrder: 1)
		edit.MenuItems.AddRange(
			(
				MenuItem("&Undo",
						Click: _menuItemUndo_Click,
						Shortcut: Shortcut.CtrlZ),
				MenuItem("&Redo",
						Click: _menuItemRedo_Click,
						Shortcut: Shortcut.CtrlY),
				MenuItem("-"),
				MenuItem("Cu&t",
						Click: _menuItemCut_Click,
						Shortcut: Shortcut.CtrlX),
				MenuItem("&Copy",
						Click: _menuItemCopy_Click,
						Shortcut: Shortcut.CtrlC),
				MenuItem("&Paste",
						Click: _menuItemPaste_Click,
						Shortcut: Shortcut.CtrlV),
				MenuItem("-"),
				MenuItem("&Go to line...",
						Shortcut: Shortcut.CtrlG,
						Click: _menuItemGoTo_Click),
				MenuItem("&Split", Click: _menuItemSplit_Click),
				MenuItem("Remove trailing whitespace", Click: _menuItemRemoveTrailingWS_Click)
			))

		tools = MenuItem(Text: "&Tools", MergeOrder: 2, MergeType: MenuMerge.MergeItems)
		tools.MenuItems.AddRange(
			(
				MenuItem(Text: "Run",
						Click: _menuItemRun_Click,
						Shortcut: Shortcut.F5),
				MenuItem(Text: "Expand",
						Click: _menuItemExpand_Click,
						Shortcut: Shortcut.CtrlE),
				MenuItem(Text: "-")
			))

		menu.MenuItems.AddRange((edit, tools))
		return menu

	def GetBooHighlighting():
		return HighlightingManager.Manager.FindHighlighter("boo")

	override protected def GetPersistString():
		return "BooEditor|${GetSafeFileName()}"