Ensure int and float literals produce the correct type

namespace BooJs.Compiler.Steps

import Boo.Lang.Compiler.Steps
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.TypeSystem(ExternalMethod, IExternalEntity, IMethod,
                                    Reflection, Internal,
                                    BuiltinFunction, BuiltinFunctionType)

class OverrideProcessMethodBodies(ProcessMethodBodiesWithDuckTyping):
""" Overrides some methods of the step to skip some modification made originally
"""

    class Pmb(IQuackFu):
    """ Uses reflection to call private methods in the ancestor class ProcessMethodBodies via a
        IQuackFu api.
    """
        pmb = typeof(ProcessMethodBodies)
        instance as ProcessMethodBodies
        def constructor(instance):
            self.instance = instance

        def QuackGet(prop as string, args as (object)):
            pass
        def QuackSet(prop as string, args as (object), val as object):
            pass
        def QuackInvoke(method as string, params as (object)) as object:
            types = array(System.Type, param.GetType() for param in params)
            mi = pmb.GetMethod(method, BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance,
                               null, types, null)
            if not mi:
                raise 'Private method {0} not found' % method
            return mi.Invoke(instance, params)

    # Obtain an instance to call ProcessMethodBodies private methods
    pmb = Pmb(self)

    override def Initialize(context as Boo.Lang.Compiler.CompilerContext):
        super(context)

    override def OnIntegerLiteralExpression(node as IntegerLiteralExpression):
    """ In BooJs ints are always long (64bits)
    """
        self.BindExpressionType(node, TypeSystemServices.LongType)

    override def OnDoubleLiteralExpression(node as DoubleLiteralExpression):
    """ In BooJs floats are always double (64bits)
    """
        node.IsSingle = false
        super(node)

    override def OnMethodInvocationExpression(node as MethodInvocationExpression):

        super(node)

        # HACK: The ICallable interface declares a generic Call method with object arguments
        #       and return type. In order to support jQuery style APIs we detect the ICallable
        #       trying to modify it by using a better match among the methods named Call in
        #       the declarting type.
        # TODO: Perhaps it's better to keep ICallable implementation as is and use an attribute
        #       or a custom interface to signal this behaviour.
        target = node.Target as MemberReferenceExpression
        if target and target.Entity == MethodCache.ICallable_Call:
            # Find the original declaring type and fetch all its Call methods
            type = target.Target.ExpressionType as Reflection.ExternalType
            methods = List[of IMethod]()
            for member in type.GetMembers():
                if member isa ExternalMethod and member.Name == 'Call':
                    methods.Add(member)

            # Resolve the best match among the Call methods for the given params
            args = (node.Arguments[0] as ArrayLiteralExpression).Items
            resolved as ExternalMethod = CallableResolutionService.ResolveCallableReference(args, methods.ToArray())
            if resolved:
                BindExpressionType(node, resolved.ReturnType)
                BindExpressionType(node.Target, resolved.Type)

        # Detect invokes for internal callables
        # TODO: We could move this to a custom step in order to allow it to be optional
        #       In debug mode we might want to dispatch via our runtime to detect and
        #       properly report errors.
        if target and target.Entity:
            targetType = target.Entity as Internal.InternalMethod
            if targetType:
                invoke as IMethod = ResolveMethod(targetType.DeclaringType, 'Invoke')
                if invoke is targetType:
                    node.Target = target.Target

    override def LeaveUnaryExpression(node as UnaryExpression):
        # Increment/Decrement operators are resolved using a fairly complex eval. Since
        # JS supports this operators natively we can simplify the case by just binding
        # the correct type to the expression.
        if node.Operator in (UnaryOperatorType.Increment, UnaryOperatorType.Decrement,
                             UnaryOperatorType.PostDecrement, UnaryOperatorType.PostIncrement):
            # TODO: This doesn't take into account any operator overloading logic

            # Bind the type of the operand to the operation
            node.ExpressionType = GetExpressionType(node.Operand)
            return

        # Fall back to the original logic
        super(node)

    override def ProcessBuiltinInvocation(node as MethodInvocationExpression, func as BuiltinFunction):
        # Handle len(val)
        if func.FunctionType == BuiltinFunctionType.Len:
            result as Expression
            target = node.Arguments[0]

            # Check if the target has a 'length' property
            exptype = target.ExpressionType as Reflection.ExternalType
            if exptype:
                field = NameResolutionService.ResolveMember(exptype, 'length', Boo.Lang.Compiler.TypeSystem.EntityType.Field)
                if field:
                    result = MemberReferenceExpression(node.LexicalInfo, target, 'length')
                    result.ExpressionType = TypeSystemServices.IntType
                    node.ParentNode.Replace(node, result)
                    return

            # Otherwise use a runtime helper
            lenfn = NameResolutionService.ResolveMethod(TypeSystemServices.RuntimeServicesType, 'Len')
            result = CodeBuilder.CreateMethodInvocation(lenfn, target)
            node.ParentNode.Replace(node, result)
            return

        super(node, func)

    override def LeaveDeclarationStatement(node as DeclarationStatement):
        # If the declaration has been annotated as global via the `global` macro
        # we must avoid generating a declaration for it, either removing it
        # or just setting a value to it if the used supplied an initializer
        decl = node.Declaration
        if decl.ContainsAnnotation('global'):
            if not decl.Type:
                decl.Type = CodeBuilder.CreateTypeReference(decl.LexicalInfo, TypeSystemServices.DuckType)

            type = GetType(decl.Type)
            entity as Internal.InternalLocal = DeclareLocal(node, decl.Name, type, false)
            entity.OriginalDeclaration = decl

            if node.Initializer:
                node.ReplaceBy(
                    ExpressionStatement(
                        CodeBuilder.CreateAssignment(
                            node.LexicalInfo,
                            CodeBuilder.CreateReference(entity),
                            node.Initializer)
                        )
                    )
            else:
                (node.ParentNode as Block).Statements.Remove(node)

            return

        super(node)

    override protected def BindBinaryExpression(node as BinaryExpression):
        # Boo would directly allow all arithmetic on number types. We have to
        # specifically process those that are not valid in javascript

        ltype = GetExpressionType(node.Left)
        rtype = GetExpressionType(node.Right)

        if node.Operator == BinaryOperatorType.Exponentiation:
            if not pmb.ResolveOperator(node):
                pmb.InvalidOperatorForTypes(node)
        # Handle integer division
        elif node.Operator == BinaryOperatorType.Division and \
             TypeSystemServices.IsIntegerNumber(ltype) and \
             TypeSystemServices.IsIntegerNumber(rtype):

             if not pmb.ResolveOperator(node):
                pmb.InvalidOperatorForTypes(node)
        else:
            super(node)

namespace BooJs.Compiler.Steps

import Boo.Lang.Compiler.Steps
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.TypeSystem(ExternalMethod, IExternalEntity, IMethod,
                                    Reflection, Internal,
                                    BuiltinFunction, BuiltinFunctionType)

class OverrideProcessMethodBodies(ProcessMethodBodiesWithDuckTyping):
""" Overrides some methods of the step to skip some modification made originally
"""

    class Pmb(IQuackFu):
    """ Uses reflection to call private methods in the ancestor class ProcessMethodBodies via a
        IQuackFu api.
    """
        pmb = typeof(ProcessMethodBodies)
        instance as ProcessMethodBodies
        def constructor(instance):
            self.instance = instance

        def QuackGet(prop as string, args as (object)):
            pass
        def QuackSet(prop as string, args as (object), val as object):
            pass
        def QuackInvoke(method as string, params as (object)) as object:
            types = array(System.Type, param.GetType() for param in params)
            mi = pmb.GetMethod(method, BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance,
                               null, types, null)
            if not mi:
                raise 'Private method {0} not found' % method
            return mi.Invoke(instance, params)

    # Obtain an instance to call ProcessMethodBodies private methods
    pmb = Pmb(self)

    override def Initialize(context as Boo.Lang.Compiler.CompilerContext):
        super(context)

    override def OnMethodInvocationExpression(node as MethodInvocationExpression):

        super(node)

        # HACK: The ICallable interface declares a generic Call method with object arguments
        #       and return type. In order to support jQuery style APIs we detect the ICallable
        #       trying to modify it by using a better match among the methods named Call in
        #       the declarting type.
        # TODO: Perhaps it's better to keep ICallable implementation as is and use an attribute
        #       or a custom interface to signal this behaviour.
        target = node.Target as MemberReferenceExpression
        if target and target.Entity == MethodCache.ICallable_Call:
            # Find the original declaring type and fetch all its Call methods
            type = target.Target.ExpressionType as Reflection.ExternalType
            methods = List[of IMethod]()
            for member in type.GetMembers():
                if member isa ExternalMethod and member.Name == 'Call':
                    methods.Add(member)

            # Resolve the best match among the Call methods for the given params
            args = (node.Arguments[0] as ArrayLiteralExpression).Items
            resolved as ExternalMethod = CallableResolutionService.ResolveCallableReference(args, methods.ToArray())
            if resolved:
                BindExpressionType(node, resolved.ReturnType)
                BindExpressionType(node.Target, resolved.Type)

        # Detect invokes for internal callables
        # TODO: We could move this to a custom step in order to allow it to be optional
        #       In debug mode we might want to dispatch via our runtime to detect and
        #       properly report errors.
        if target and target.Entity:
            targetType = target.Entity as Internal.InternalMethod
            if targetType:
                invoke as IMethod = ResolveMethod(targetType.DeclaringType, 'Invoke')
                if invoke is targetType:
                    node.Target = target.Target

    override def LeaveUnaryExpression(node as UnaryExpression):
        # Increment/Decrement operators are resolved using a fairly complex eval. Since
        # JS supports this operators natively we can simplify the case by just binding
        # the correct type to the expression.
        if node.Operator in (UnaryOperatorType.Increment, UnaryOperatorType.Decrement,
                             UnaryOperatorType.PostDecrement, UnaryOperatorType.PostIncrement):
            # TODO: This doesn't take into account any operator overloading logic

            # Bind the type of the operand to the operation
            node.ExpressionType = GetExpressionType(node.Operand)
            return

        # Fall back to the original logic
        super(node)

    override def ProcessBuiltinInvocation(node as MethodInvocationExpression, func as BuiltinFunction):
        # Handle len(val)
        if func.FunctionType == BuiltinFunctionType.Len:
            result as Expression
            target = node.Arguments[0]

            # Check if the target has a 'length' property
            exptype = target.ExpressionType as Reflection.ExternalType
            if exptype:
                field = NameResolutionService.ResolveMember(exptype, 'length', Boo.Lang.Compiler.TypeSystem.EntityType.Field)
                if field:
                    result = MemberReferenceExpression(node.LexicalInfo, target, 'length')
                    result.ExpressionType = TypeSystemServices.IntType
                    node.ParentNode.Replace(node, result)
                    return

            # Otherwise use a runtime helper
            lenfn = NameResolutionService.ResolveMethod(TypeSystemServices.RuntimeServicesType, 'Len')
            result = CodeBuilder.CreateMethodInvocation(lenfn, target)
            node.ParentNode.Replace(node, result)
            return

        super(node, func)

    override def LeaveDeclarationStatement(node as DeclarationStatement):
        # If the declaration has been annotated as global via the `global` macro
        # we must avoid generating a declaration for it, either removing it
        # or just setting a value to it if the used supplied an initializer
        decl = node.Declaration
        if decl.ContainsAnnotation('global'):
            if not decl.Type:
                decl.Type = CodeBuilder.CreateTypeReference(decl.LexicalInfo, TypeSystemServices.DuckType)

            type = GetType(decl.Type)
            entity as Internal.InternalLocal = DeclareLocal(node, decl.Name, type, false)
            entity.OriginalDeclaration = decl

            if node.Initializer:
                node.ReplaceBy(
                    ExpressionStatement(
                        CodeBuilder.CreateAssignment(
                            node.LexicalInfo,
                            CodeBuilder.CreateReference(entity),
                            node.Initializer)
                        )
                    )
            else:
                (node.ParentNode as Block).Statements.Remove(node)

            return

        super(node)

    override protected def BindBinaryExpression(node as BinaryExpression):
        # Boo would directly allow all arithmetic on number types. We have to
        # specifically process those that are not valid in javascript

        ltype = GetExpressionType(node.Left)
        rtype = GetExpressionType(node.Right)

        if node.Operator == BinaryOperatorType.Exponentiation:
            if not pmb.ResolveOperator(node):
                pmb.InvalidOperatorForTypes(node)
        # Handle integer division
        elif node.Operator == BinaryOperatorType.Division and \
             TypeSystemServices.IsIntegerNumber(ltype) and \
             TypeSystemServices.IsIntegerNumber(rtype):

             if not pmb.ResolveOperator(node):
                pmb.InvalidOperatorForTypes(node)
        else:
            super(node)


BOO-750 patch by Jaroslaw Kowalski

BOO-750 patch by Jaroslaw Kowalski

git-svn-id: fcce7d6ce0ac15bf24f68871b5145d46fc068279@2333 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.NAnt

import System
import System.Globalization
import System.IO
import System.Text.RegularExpressions
import NAnt.Core
import NAnt.Core.Attributes
import NAnt.Core.Types
import NAnt.Core.Util
import NAnt.DotNet.Types

import NAnt.DotNet.Tasks
import System.Reflection

[TaskName('booc')]
public class BoocTask(CompilerBase):
	#region Private Instance Fields
	private _debugOutput as DebugOutput = DebugOutput.Enable
	private _exe as string

	private _useruntime = true //keep true for mono compatibility (don't call booc.exe directly)
	private _noconfig = false
	private _nostdlib = false
	private _wsa = false
	private _ducky = false
	private _pipeline as string

	#endregion Private Instance Fields
	#region Private Static Fields
	private static _classNameRegex = Regex('^((?<comment>/\\*.*?(\\*/|$))|[\\s\\.\\{]+|class\\s+(?<class>\\w+)|(?<keyword>\\w+))*')

	private static _namespaceRegex = Regex('^((?<comment>/\\*.*?(\\*/|$))|[\\s\\.\\{]+|namespace\\s+(?<namespace>(\\w+(\\.\\w+)*)+)|(?<keyword>\\w+))*')

	#endregion Private Static Fields

	def constructor():
		SupportsKeyFile = true
		SupportsKeyContainer = true

	[FrameworkConfigurable("exename")]
	[TaskAttribute('exename')]
	public override ExeName as string:
		get:
			return _exe
		set:
			_exe = value

	[FrameworkConfigurable("useruntimeengine")]
	[TaskAttribute('useruntimeengine')]
	override UseRuntimeEngine as bool:
		get:
			return _useruntime
		set:
			_useruntime = value

	#region Public Instance Properties

	[TaskAttribute('debug')]
	public DebugOutput as DebugOutput:
		get:
			return _debugOutput
		set:
			_debugOutput = value

	[TaskAttribute('pipeline')]
	public Pipeline:
		get:
			return _pipeline
		set:
			_pipeline = value

	public Debug as bool:
		get:
			return (DebugOutput != DebugOutput.None)
		set:
			if value:
				DebugOutput = DebugOutput.Enable
			else:
				DebugOutput = DebugOutput.None

	[FrameworkConfigurable('noconfig')]
	[TaskAttribute('noconfig')]
	[BooleanValidator]
	public NoConfig as bool:
		get:
			return _noconfig
		set:
			_noconfig = value

	[FrameworkConfigurable('nostdlib')]
	[TaskAttribute('nostdlib')]
	[BooleanValidator]
	public NoStdLib as bool:
		get:
			return _nostdlib
		set:
			_nostdlib = value

	[TaskAttribute('wsa')]
	[BooleanValidator]
	public WhiteSpaceAgnostic as bool:
		get:
			return _wsa
		set:
			_wsa = value

	[TaskAttribute('ducky')]
	[BooleanValidator]
	public Ducky as bool:
		get:
			return _ducky
		set:
			_ducky = value

	private def FindBooc() as string:
		path as string
		dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
		if Project.TargetFramework:
			frameworkname = Project.TargetFramework.Name
			path = Path.Combine(Path.Combine(dir, frameworkname), "booc.exe")
			if File.Exists(path):
				return path
			//treat mono-1.0==net-1.1 and mono-##==net-##
			frameworkname = frameworkname.Replace("mono-1.0","net-1.1").Replace("mono-","net-")
			path = Path.Combine(Path.Combine(dir, frameworkname), "booc.exe")
			if File.Exists(path):
				return path
		path = Path.Combine(dir, "booc.exe")
		if File.Exists(path):
			return path
		_useruntime = false
		return "booc" //try booc in PATH

	protected override def ExecuteTask():
		if not ExeName or ExeName == string.Empty:
			ExeName = FindBooc()
		super()

	#endregion Public Instance Properties
	#region Override implementation of CompilerBase
	protected override def WriteOptions(writer as TextWriter):
		if DebugOutput != DebugOutput.None:
			WriteOption(writer, 'debug')
		else:
			WriteOption(writer, 'debug-')
		if NoConfig and (not Arguments.Contains('-noconfig')):
			Arguments.Add(Argument('-noconfig'))
		if NoStdLib:
			WriteOption(writer, "nostdlib")
		if Verbose:
			WriteOption(writer, "vv")
		if WhiteSpaceAgnostic:
			WriteOption(writer, "wsa")
		if Ducky:
			WriteOption(writer, "ducky")
		if Pipeline:
			WriteOption(writer, "p", _pipeline)

	protected override def WriteOption(writer as TextWriter, name as string):
		writer.WriteLine("-{0}", name)

	protected override def WriteOption(writer as TextWriter, name as string, value as string):
		if name == "resource":
			name = "embedres"
		if value.IndexOf(" ") > 0 and (not value.StartsWith("\"")
						or not value.EndsWith("\"")):
			writer.WriteLine("-{0}:\"{1}\"", name, value)
		else:
			writer.WriteLine("-{0}:{1}", name, value)

	public Extension as string:
		get:
			return 'boo'

	protected ClassNameRegex as Regex:
		get:
			return _classNameRegex

	protected NamespaceRegex as Regex:
		get:
			return _namespaceRegex
	#endregion Override implementation of CompilerBase

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

import System
import System.Globalization
import System.IO
import System.Text.RegularExpressions
import NAnt.Core
import NAnt.Core.Attributes
import NAnt.Core.Types
import NAnt.Core.Util
import NAnt.DotNet.Types

import NAnt.DotNet.Tasks
import System.Reflection

[TaskName('booc')]
public class BoocTask(CompilerBase):
	#region Private Instance Fields
	private _debugOutput as DebugOutput = DebugOutput.Enable
	private _exe as string

	private _useruntime = true //keep true for mono compatibility (don't call booc.exe directly)
	private _noconfig = false
	private _nostdlib = false
	private _wsa = false
	private _ducky = false
	private _pipeline as string

	#endregion Private Instance Fields
	#region Private Static Fields
	private static _classNameRegex = Regex('^((?<comment>/\\*.*?(\\*/|$))|[\\s\\.\\{]+|class\\s+(?<class>\\w+)|(?<keyword>\\w+))*')

	private static _namespaceRegex = Regex('^((?<comment>/\\*.*?(\\*/|$))|[\\s\\.\\{]+|namespace\\s+(?<namespace>(\\w+(\\.\\w+)*)+)|(?<keyword>\\w+))*')

	#endregion Private Static Fields

	def constructor():
		SupportsKeyFile = true
		SupportsKeyContainer = true

	[FrameworkConfigurable("exename")]
	[TaskAttribute('exename')]
	public override ExeName as string:
		get:
			return _exe
		set:
			_exe = value

	[FrameworkConfigurable("useruntimeengine")]
	[TaskAttribute('useruntimeengine')]
	override UseRuntimeEngine as bool:
		get:
			return _useruntime
		set:
			_useruntime = value

	#region Public Instance Properties

	[TaskAttribute('debug')]
	public DebugOutput as DebugOutput:
		get:
			return _debugOutput
		set:
			_debugOutput = value

	[TaskAttribute('pipeline')]
	public Pipeline:
		get:
			return _pipeline
		set:
			_pipeline = value

	public Debug as bool:
		get:
			return (DebugOutput != DebugOutput.None)
		set:
			if value:
				DebugOutput = DebugOutput.Enable
			else:
				DebugOutput = DebugOutput.None

	[FrameworkConfigurable('noconfig')]
	[TaskAttribute('noconfig')]
	[BooleanValidator]
	public NoConfig as bool:
		get:
			return _noconfig
		set:
			_noconfig = value

	[FrameworkConfigurable('nostdlib')]
	[TaskAttribute('nostdlib')]
	[BooleanValidator]
	public NoStdLib as bool:
		get:
			return _nostdlib
		set:
			_nostdlib = value

	[TaskAttribute('wsa')]
	[BooleanValidator]
	public WhiteSpaceAgnostic as bool:
		get:
			return _wsa
		set:
			_wsa = value

	[TaskAttribute('ducky')]
	[BooleanValidator]
	public Ducky as bool:
		get:
			return _ducky
		set:
			_ducky = value

	private def FindBooc() as string:
		path as string
		dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
		if Project.TargetFramework:
			frameworkname = Project.TargetFramework.Name
			path = Path.Combine(Path.Combine(dir, frameworkname), "booc.exe")
			if File.Exists(path):
				return path
			//treat mono-1.0==net-1.1 and mono-##==net-##
			frameworkname = frameworkname.Replace("mono-1.0","net-1.1").Replace("mono-","net-")
			path = Path.Combine(Path.Combine(dir, frameworkname), "booc.exe")
			if File.Exists(path):
				return path
		path = Path.Combine(dir, "booc.exe")
		if File.Exists(path):
			return path
		_useruntime = false
		return "booc" //try booc in PATH

	protected override def ExecuteTask():
		if not ExeName or ExeName == string.Empty:
			ExeName = FindBooc()
		super()

	#endregion Public Instance Properties
	#region Override implementation of CompilerBase
	protected override def WriteOptions(writer as TextWriter):
		if DebugOutput != DebugOutput.None:
			WriteOption(writer, 'debug')
		else:
			WriteOption(writer, 'debug-')
		if NoConfig and (not Arguments.Contains('-noconfig')):
			Arguments.Add(Argument('-noconfig'))
		if NoStdLib:
			WriteOption(writer, "nostdlib")
		if Verbose:
			WriteOption(writer, "vv")
		if WhiteSpaceAgnostic:
			WriteOption(writer, "wsa")
		if Ducky:
			WriteOption(writer, "ducky")
		if Pipeline:
			WriteOption(writer, "p", _pipeline)

	protected override def WriteOption(writer as TextWriter, name as string):
		writer.WriteLine("-{0}", name)

	protected override def WriteOption(writer as TextWriter, name as string, value as string):
		if value.IndexOf(" ") > 0 and (not value.StartsWith("\"")
						or not value.EndsWith("\"")):
			writer.WriteLine("-{0}:\"{1}\"", name, value)
		else:
			writer.WriteLine("-{0}:{1}", name, value)

	public Extension as string:
		get:
			return 'boo'

	protected ClassNameRegex as Regex:
		get:
			return _classNameRegex

	protected NamespaceRegex as Regex:
		get:
			return _namespaceRegex
	#endregion Override implementation of CompilerBase


code completion patch by Arron Washington

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

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

"""
Interactive Forms-based Console
"""

namespace booish.gui

import System
import System.Text
import System.IO
import System.Reflection
import System.Windows.Forms
import System.Drawing
import System.Runtime.InteropServices
import Boo.Lang.Interpreter

class KeyChar:

	public static Enter = chr(Keys.Enter)

	public static Dot = chr('.')

	public static Back = chr(Keys.Back)

	public static Esc = chr(Keys.Escape)

	public static Del = chr(Keys.Delete)

class CompletionBox(ListBox):

	_interpreter as InteractiveInterpreter

	_currentCodeCompletion = ""

	_codeCompletionArchive = []

	_upsideDown = false

	def constructor(interpreter):
		self._interpreter = interpreter
		self.Visible = false

	def Fill(type as System.Type):
		members = []
		for member in type.GetMembers():
			if IsValidSuggestion(member):
				if member isa FieldInfo:
					field = member as FieldInfo
					members.Add("${field.Name} as ${getBooTypeName(field.FieldType)}")
				elif member isa PropertyInfo:
					property = member as PropertyInfo
					getField = ("", "getter,")[property.CanRead]
					setField = ("", "setter")[property.CanWrite]
					members.Add("${member.Name} as ${getBooTypeName(property.PropertyType)} (${getField} ${setField})")
				elif member isa MethodInfo:
					method = member as MethodInfo
					params = join("${param.Name} as ${getBooTypeName(param.ParameterType)}"
										for param in method.GetParameters(), ", ")
					returnValue = ""
					returnValue = " as ${getBooTypeName(method.ReturnType)}" if method.ReturnType is not void
					members.Add("${member.Name}(${params})${returnValue}")

		Items.Clear()
		_codeCompletionArchive.Clear()
		for item in members.Sort():
			Items.Add(item) if not _upsideDown
			Items.Insert(0, item) if _upsideDown
			_codeCompletionArchive.Add(item)
		self.SelectedIndex = (0, self.Items.Count - 1)[_upsideDown and self.Items.Count > 1]

	def Show(pos as Point):
		//Ensure that this listbox won't extend past the bottom of the screen.
		//(Most common use-case.)
		self.Location = pos
		_upsideDown = self.Bottom > Parent.Bottom
		if _upsideDown:
			par = cast(PromptBox, Parent)
			//FIXME: Here's the deal - I don't know how to get the size of the caret,
			//So the code completion box will always be "just so" off
			//and prove to be visually distracting.
			//TODO: The FIXME above. ;)
			caretDis = Parent.Bottom - par.CaretPos.Y
			caretDis -= self.Font.Height
			self.Location =  Point(self.Location.X, (self.Location.Y - (self.Bottom - Parent.Bottom) ) - caretDis )

		self.Visible = true
		self.Focus()

	private def IsValidSuggestion(member as MemberInfo):
		return false if not IsPublic(member)
		method = member as MethodBase
		if method is not null:
			return false if method.IsSpecialName
		return true

	private def IsPublic(member as MemberInfo):
		if MemberTypes.Method == member.MemberType:
			return cast(MethodBase, member).IsPublic
		if MemberTypes.Property == member.MemberType:
			p as PropertyInfo = member
			return (p.GetGetMethod() or p.GetSetMethod()).IsPublic
		if MemberTypes.Field == member.MemberType:
			return cast(FieldInfo, member).IsPublic
		return true

	override def OnLostFocus(args as EventArgs):
		self.Visible = false
		super(args)

	override def OnKeyPress(args as KeyPressEventArgs):
		finish = def ():
			Parent.Focus()
			_currentCodeCompletion = ""
			args.Handled = true
		// Adjust listview to _currentCodeCompletion
		// Also, adjust to focus if _upsideDown!
		smartComplete = def():
			self.Items.Clear()
			self.Items.Add("${_currentCodeCompletion} (*)")
			for data as string in _codeCompletionArchive:
				if data.ToLower().StartsWith(_currentCodeCompletion.ToLower()):
					self.Items.Add(data) if not _upsideDown
					self.Items.Insert(0, data) if _upsideDown
			// Make sure one member is always selected by de funk.
			// Always show the user what he's been typing, meanwhile.
			self.SelectedIndex = (0, 1)[self.Items.Count > 1] if not _upsideDown
			self.SelectedIndex = (self.Items.Count - 1, self.Items.Count - 2)[self.Items.Count > 1] if _upsideDown

		if args.KeyChar in KeyChar.Esc, KeyChar.Back:
			par = cast(PromptBox, Parent)
			if args.KeyChar == KeyChar.Esc:
				par.Text += _currentCodeCompletion
				par.SelectionStart = par.Text.Length
				finish()
			elif _currentCodeCompletion == "":
				finish()
			else:
				_currentCodeCompletion = _currentCodeCompletion.Remove(
					_currentCodeCompletion.Length - 1, 1)
				smartComplete()
		elif KeyChar.Enter == args.KeyChar or KeyChar.Dot == args.KeyChar:
			try:
				cast(TextBox, Parent).SelectedText = /\w+/.Match(SelectedItem).Groups[0].Value
			except e: //User did NOT select an item before triggering condition.
				pass
			ensure:
				finish()
		else:
			_currentCodeCompletion += args.KeyChar
			smartComplete()
			super(args)

	private def getBooTypeName(type as System.Type):
		return _interpreter.getBooTypeName(type)

class PromptBox(TextBox):

	enum InputState:
		SingleLine = 0
		Block = 1

	_state = InputState.SingleLine

	_block = System.IO.StringWriter()

	_console = StringWriter()

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

	_completionBox = CompletionBox(_interpreter)

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

		Controls.Add(_completionBox)

		prompt()

	CaretPos:
		get:
			p as Point
			assert booish.gui.externs.User32.GetCaretPos(p)
			return p

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

	def SingleLineInputState():
		code = GetCurrentLine()

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

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

	override def OnKeyPress(args as KeyPressEventArgs):
		// These blocks of code:
		// *keeps the user from deleting the ">>>" and screwing up the interpretor.
		// *keeps the user from "going up" and editing already inputted text.
		// Keep user from selecting-and-deleting.

		currentPrompt = Text.Length - (Lines[-1][4:].Length)
		if SelectedText != "" and SelectionStart < currentPrompt:
			args.Handled = true
		//Make sure user's cursor hasn't drifted beyond ">>> "
		elif SelectedText == "" and args.KeyChar == KeyChar.Back:
			if SelectionStart <= currentPrompt:
				args.Handled = true
		elif KeyChar.Enter == args.KeyChar:
			try:
				(SingleLineInputState, BlockInputState)[_state]()
			except x:
				print(x)
			prompt()
			args.Handled = true
		//print "Did we get this far, yet?"
		elif KeyChar.Dot == args.KeyChar:
			self.SelectedText = "."
			DotComplete()
			args.Handled = true

		super(args)

	override def OnResize(args as EventArgs):
		_completionBox.Size = System.Drawing.Size(Width*.65, Height*.4)
		super(args)

	def DotComplete():
		m = /((\w|\.)+)\.$/.Match(self.Lines[-1])
		if m.Success:
			// only simple identifiers right now
			expression = m.Groups[1].Value
			if /^\w+$/.IsMatch(expression):
				type = _interpreter.Lookup(expression)
				if type is not null:
					_completionBox.Show(CaretPos)
					_completionBox.Fill(type)

	def Eval(code as string):
		saved = Console.Out
		Console.SetOut(_console)
		try:
			_interpreter.LoopEval(code)
		ensure:
			FlushConsole()
			Console.SetOut(saved)

	def FlushConsole():
		AppendText(_console.ToString())
		_console.GetStringBuilder().Length = 0

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

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

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

def chr(key as Keys):
	return cast(IConvertible, cast(int, key)).ToChar(null)

def chr(s as string):
	assert len(s) == 1
	return s[0]

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

"""
Interactive Forms-based Console
"""

namespace booish.gui

import System
import System.Text
import System.IO
import System.Reflection
import System.Windows.Forms
import System.Drawing
import System.Runtime.InteropServices
import Boo.Lang.Interpreter

class KeyChar:

	public static Enter = chr(Keys.Enter)

	public static Dot = chr('.')

	public static Back = chr(Keys.Back)

	public static Esc = chr(Keys.Escape)

class CompletionBox(ListBox):

	_interpreter as InteractiveInterpreter

	def constructor(interpreter):
		self._interpreter = interpreter
		self.Visible = false

	def Fill(type as System.Type):

		members = []
		for member in type.GetMembers():
			if IsValidSuggestion(member):
				if member isa FieldInfo:
					field = member as FieldInfo
					members.Add("${field.Name} as ${getBooTypeName(field.FieldType)}")
				elif member isa PropertyInfo:
					property = member as PropertyInfo
					getField = ("", "getter,")[property.CanRead]
					setField = ("", "setter")[property.CanWrite]
					members.Add("${member.Name} as ${getBooTypeName(property.PropertyType)} (${getField} ${setField})")
				elif member isa MethodInfo:
					method = member as MethodInfo
					params = join("${param.Name} as ${getBooTypeName(param.ParameterType)}"
										for param in method.GetParameters(), ", ")
					returnValue = ""
					returnValue = " as ${getBooTypeName(method.ReturnType)}" if method.ReturnType is not void
					members.Add("${member.Name}(${params})${returnValue}")

		Items.Clear()
		for item in members.Sort():
			Items.Add(item)

	def Show(pos as Point):
		self.Location = pos
		self.Visible = true
		self.Focus()

	private def IsValidSuggestion(member as MemberInfo):
		return false if not IsPublic(member)
		method = member as MethodBase
		if method is not null:
			return false if method.IsSpecialName
		return true

	private def IsPublic(member as MemberInfo):
		if MemberTypes.Method == member.MemberType:
			return cast(MethodBase, member).IsPublic
		if MemberTypes.Property == member.MemberType:
			p as PropertyInfo = member
			return (p.GetGetMethod() or p.GetSetMethod()).IsPublic
		if MemberTypes.Field == member.MemberType:
			return cast(FieldInfo, member).IsPublic
		return true

	override def OnLostFocus(args as EventArgs):
		self.Visible = false
		super(args)

	override def OnKeyPress(args as KeyPressEventArgs):
		finish = def ():
			Parent.Focus()
			args.Handled = true

		if args.KeyChar in KeyChar.Esc, KeyChar.Back:
			finish()
		elif KeyChar.Enter == args.KeyChar:
			cast(TextBox, Parent).SelectedText = /\w+/.Match(SelectedItem).Groups[0].Value
			finish()
		else:
			super(args)

	private def getBooTypeName(type as System.Type):
		return _interpreter.getBooTypeName(type)

class PromptBox(TextBox):

	enum InputState:
		SingleLine = 0
		Block = 1

	_state = InputState.SingleLine

	_block = System.IO.StringWriter()

	_console = StringWriter()

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

	_completionBox = CompletionBox(_interpreter)

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

		Controls.Add(_completionBox)

		prompt()

	CaretPos:
		get:
			p as Point
			assert booish.gui.externs.User32.GetCaretPos(p)
			return p

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

	def SingleLineInputState():
		code = GetCurrentLine()

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

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

	override def OnKeyPress(args as KeyPressEventArgs):
		if KeyChar.Enter == args.KeyChar:
			try:
				(SingleLineInputState, BlockInputState)[_state]()
			except x:
				print(x)
			prompt()
			args.Handled = true
		elif KeyChar.Dot == args.KeyChar:
			self.SelectedText = "."
			DotComplete()
			args.Handled = true

		super(args)

	override def OnResize(args as EventArgs):
		_completionBox.Size = System.Drawing.Size(Width*.65, Height*.4)
		super(args)

	def DotComplete():
		m = /((\w|\.)+)\.$/.Match(self.Lines[-1])
		if m.Success:
			// only simple identifiers right now
			expression = m.Groups[1].Value
			if /^\w+$/.IsMatch(expression):
				type = _interpreter.Lookup(expression)
				if type is not null:
					_completionBox.Fill(type)
					_completionBox.Show(CaretPos)

	def Eval(code as string):
		saved = Console.Out
		Console.SetOut(_console)
		try:
			_interpreter.LoopEval(code)
		ensure:
			FlushConsole()
			Console.SetOut(saved)

	def FlushConsole():
		AppendText(_console.ToString())
		_console.GetStringBuilder().Length = 0

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

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

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

def chr(key as Keys):
	return cast(IConvertible, cast(int, key)).ToChar(null)

def chr(s as string):
	assert len(s) == 1
	return s[0]


Fixed console multiline behavior (delete, history, arrows, insert and tab)

#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.Collections.Generic
import System.IO
import System.Linq
import System.Text
import System.Text.RegularExpressions
import Boo.Lang.Compiler
import Boo.Lang.Compiler.TypeSystem
import PatternMatching
import Environments

class InteractiveInterpreterConsole:

	public final static HISTORY_FILENAME = "booish_history"
	public final static HISTORY_CAPACITY = 100

	_history = System.Collections.Generic.Queue of string(HISTORY_CAPACITY)
	_historyFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), HISTORY_FILENAME)
	_historyIndex = 0
	_session = System.Collections.Generic.List of string()

	_buffer = StringBuilder()	#buffer to be executed
	_line = StringBuilder()		#line being edited
	_multiline = false			#is the current line a multi-line?

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

	[property(ShowWarnings)]
	_showWarnings = false

	QQBegin = "[|"
	QQEnd= "|]"

	_interpreter as InteractiveInterpreter

	def constructor():
		self(InteractiveInterpreter())

	def constructor(interpreter as InteractiveInterpreter):
		_interpreter = interpreter
		_interpreter.RememberLastValue = true

		DisableColors = not string.IsNullOrEmpty(Environment.GetEnvironmentVariable("BOOISH_DISABLE_COLORS"))
		if not DisableColors: #make sure setting color does not throw an exception
			try:
				Console.ForegroundColor = ConsoleColor.DarkGray
			except:
				DisableColors = true
		DisableAutocompletion = not string.IsNullOrEmpty(Environment.GetEnvironmentVariable("BOOISH_DISABLE_AUTOCOMPLETION"))

		_interpreter.SetValue("load", Load)
		_interpreter.SetValue("save", Save)
		_interpreter.SetValue("quit", Quit)

		LoadHistory()

	def SetValue(name as string, value):
		_interpreter.SetValue(name, value)

	Line:
		get: return _line.ToString()

	LineLastChar:
		get: return (_line.Chars[LineLen-1] if LineLen != 0 else char('\0'))

	LineLen:
		get: return _line.Length
		set: _line.Length = value

	LineIndentLen:
		get: return IndentChars.Length * _indent

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

	PrintModules:
		get: return _interpreter.Pipeline.Find(Boo.Lang.Compiler.Steps.PrintBoo) != -1
		set:
			if value:
				if not PrintModules:
					_interpreter.Pipeline.Add(Boo.Lang.Compiler.Steps.PrintBoo())
			else:
				_interpreter.Pipeline.Remove(Boo.Lang.Compiler.Steps.PrintBoo)

	property DefaultPrompt = ">>> "

	property BlockPrompt = "... "

	property IndentChars = "    "

	property DisableColors = false

	property DisableAutocompletion = false

	# messages from the interpreter (not from user code)
	property InterpreterColor = ConsoleColor.DarkGray

	property PromptColor = ConsoleColor.DarkGreen

	property ExceptionColor = ConsoleColor.DarkRed

	property WarningColor = ConsoleColor.Yellow

	property ErrorColor = ConsoleColor.Red

	property SuggestionsColor = ConsoleColor.DarkYellow

	property SelectedSuggestionColor = ConsoleColor.DarkMagenta

	_selectedSuggestionIndex as int?

	_suggestions as EnvironmentBoundValue[of (object)]

	CanAutoComplete as bool:
		get: return _selectedSuggestionIndex is not null

	private _builtins as (IEntity)
	private _filter as string

	def Eval(code as string):
		try:
			result = _interpreter.Eval(code)
			DisplayResults(result)
			return result
		except x as System.Reflection.TargetInvocationException:
			ConsolePrintException(x.InnerException)
		except x:
			ConsolePrintException(x)

	private def DisplayResults(results as CompilerContext):
		if ShowWarnings:
			DisplayProblems(results.Warnings, WarningColor)
		if not DisplayProblems(results.Errors, ErrorColor):
			ProcessLastValue()

	private def ConsolePrintPrompt():
		ConsolePrintPrompt(true)

	private def ConsolePrintPrompt(autoIndent as bool):
		return if _quit
		WithColor PromptColor:
			Console.Write(CurrentPrompt)
		if autoIndent and CurrentPrompt == BlockPrompt:
			for i in range(_indent):
				WriteIndent()
		originalYPosition = Console.CursorTop

	private def WithColor(color as ConsoleColor, block as System.Action):
		if DisableColors:
			block()
		else:
			Console.ForegroundColor = color
			try:
				block()
			ensure:
				Console.ResetColor()

	private def ConsolePrintMessage(msg as string):
		WithColor InterpreterColor:
			print msg

	private def ConsolePrintException(e as Exception):
		WithColor ExceptionColor:
			print e

	private def ConsolePrintError(msg as string):
		WithColor ExceptionColor:
			print msg

	private def NewLine():
		Console.Write(Environment.NewLine)

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

		i = 0

		for s in SuggestionDescriptions():
			Console.ForegroundColor = SuggestionsColor if not DisableColors
			Console.Write(", ") if i > 0
			if i > 20: #TODO: maxcandidates pref + paging?
				Console.Write("... (too many candidates)")
				break
			if i == _selectedSuggestionIndex:
				Console.ForegroundColor = SelectedSuggestionColor if not DisableColors
			Console.Write(s)
			i++

		Console.ResetColor() if not DisableColors
		#Console.CursorTop = cursorTop
		NewLine()
		ConsolePrintPrompt(false)
		Console.Write(Line)
		Console.CursorLeft = cursorLeft

	def SuggestionDescriptions():
		return _suggestions.Select(DescriptionsFor).Value

	def DescriptionsFor(suggestions as (object)):
		return array((DescriptionFor(s) for s in suggestions).Distinct())

	def DescriptionFor(s):
		match s:
			case e = IEntity():
				return Boo.Lang.Interpreter.Builtins.DescribeEntity(e)
			otherwise:
				return s.ToString()

	protected def Write(s as string):
		Console.Write(s)
		_line.Append(s)

	protected def WriteIndent():
		Write(IndentChars)

	protected def Indent():
		WriteIndent()
		_indent++

	protected def Unindent():
		return if _indent == 0
		Delete(IndentChars.Length)
		_indent--

	protected def Delete(count as int): #if count is 0, forward-delete
		cx = Console.CursorLeft+(Console.CursorTop-originalYPosition)*Console.WindowWidth-len(CurrentPrompt)-count
		return if cx < len(CurrentPrompt) and count == 0
		dcount = (count if count != 0 else 1)
		_line.Remove(cx, dcount)
		curX = Console.CursorLeft - dcount
		curY = Console.CursorTop
		if curX < 0:
			curX = Console.WindowWidth-1
			curY = Console.CursorTop-1

		Console.CursorTop = curY
		Console.CursorLeft = curX
		Console.Write("${_line.ToString(cx,LineLen-cx)} ")
		Console.CursorTop = curY
		Console.CursorLeft = curX

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

	def DisplaySuggestions():
		DisplaySuggestions(Line)

	def DisplaySuggestions(query as string):
		return if DisableAutocompletion

		#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]
			_filter = query[query.LastIndexOf('.')+1:]
			_suggestions = (
				_interpreter
				.SuggestCompletionsFor(codeToComplete+"__codecomplete__")
				.Select[of (object)](
					{ es | array(e as object for e in es if e.Name.StartsWith(_filter)) }))

		if _suggestions.Value is null or 0 == len(_suggestions.Value): #suggest a  var
			_filter = query
			_suggestions = EnvironmentBoundValue.Create(
							null,
							array(
								var.Key.ToString() as object
								for var in _interpreter.Values
								if var.ToString().StartsWith(_filter)))

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

	def AutoComplete():
		raise InvalidOperationException("no suggestions") if _suggestions.Value is null or _selectedSuggestionIndex is null

		Write(_suggestions.Select[of string]({ ss | AutoCompletionFor(ss[_selectedSuggestionIndex.Value]) }).Value)

		_selectedSuggestionIndex = null
		_suggestions = EnvironmentBoundValue[of (object)](null, null)

	def AutoCompletionFor(s):
		match s:
			case m = IMethod(Name: name):
				if len(m.GetParameters()) == 0:
					return "$(TrimFilter(name))()"
				return "$(TrimFilter(name))("
			case IEntity(Name: name):
				return TrimFilter(name)
			otherwise:
				return TrimFilter(s.ToString())

	def TrimFilter(s as string):
		return s[len(_filter):]

	private _beforeHistory = string.Empty

	private originalYPosition = 0

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

	def ReadEvalPrintLoop():
		Console.CursorVisible = true
		ConsolePrintPrompt()
		while not _quit:
			ReadEvalPrintLoopStep()
		SaveHistory()
		DisplayGoodbye()

	private def ReadEvalPrintLoopStep():
		cki = Console.ReadKey(true)
		key = cki.Key
		keyChar = cki.KeyChar
		control = false

		newLine = keyChar in Environment.NewLine

		if char.IsControl(keyChar):
			control = true
			if keyChar == char('\t'):
				if LineLen > 0 and (char.IsLetterOrDigit(LineLastChar) or LineLastChar == char('.')):
					_selectedSuggestionIndex = 0
					DisplaySuggestions()
				else:
					Indent()

			#line-editing support
			if not _multiline and LineLen > 0:
				if Console.CursorLeft+(Console.CursorTop-originalYPosition)*Console.WindowWidth > len(CurrentPrompt):
					if key == ConsoleKey.Backspace:
						if _indent > 0 and LineLen == LineIndentLen:
							Unindent()
						else:
							Delete(1)
					elif key == ConsoleKey.LeftArrow:
						if Console.CursorLeft > 0:
							Console.CursorLeft--
						else:
							Console.CursorTop--
							Console.CursorLeft = Console.WindowWidth-1
				if key == ConsoleKey.Delete:
					Delete(0)
				elif key == ConsoleKey.RightArrow:
					if Console.CursorLeft+(Console.CursorTop-originalYPosition)*Console.WindowWidth < (len(CurrentPrompt)+LineLen):
						if Console.CursorLeft < Console.WindowWidth-1:
							Console.CursorLeft++
						else:
							Console.CursorTop++
							Console.CursorLeft = 0
				elif key == ConsoleKey.Home:
					Console.CursorLeft = len(CurrentPrompt)
				elif key == ConsoleKey.End:
					Console.CursorLeft = len(CurrentPrompt) + LineLen

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

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

		_selectedSuggestionIndex = null
		cx = Console.CursorLeft+(Console.CursorTop-originalYPosition)*Console.WindowWidth-len(CurrentPrompt)
		#multi-line?
		#if cx < 0 or LineLen >= Console.WindowWidth-len(CurrentPrompt):
		#	cx = LineLen
		#	_multiline = true

		if not newLine:
			#line-editing support
			if cx < LineLen and not _multiline:
				_line.Insert(cx, keyChar) if not control
				CurX = Console.CursorLeft
				CurY = Console.CursorTop
				Console.Write(_line.ToString(cx, LineLen-cx))
				Console.CursorTop = CurY
				if CurX + 1 >= Console.WindowWidth:
					Console.CursorTop++
					Console.CursorLeft = 0
				else:
					Console.CursorLeft = CurX+1
			else:
				_line.Append(keyChar) if not control
				Console.Write(keyChar)

		if newLine:
			_multiline = false
			Console.Write(Environment.NewLine)

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

				_indent++ if LineLastChar in _blockStarters or Line.EndsWith(QQBegin)
				_indent-- if Line.EndsWith(IndentChars+"pass")
				if Line.EndsWith(QQEnd):
					CheckBooLangCompilerReferenced()
					_indent--

				if _indent <= 0:
					_indent = 0
					try:
						Eval(_buffer.ToString())
					ensure:
						_buffer.Length = 0 #truncate buffer

			LineLen = 0 #truncate line
			ConsolePrintPrompt()

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

		cmd = line.Split()

		if len(cmd) == 1:
			if cmd[0] == "/q" or cmd[0] == "/quit":
				Quit()
			elif cmd[0] == "/?" or cmd[0] == "/h" or cmd[0] == "/help":
				DisplayHelp()
			elif cmd[0] == "/g" or cmd[0] == "/globals":
				Eval("globals()")
			else:
				return false

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

		else:
			return false
		return true

	private _indent as int = 0

	def DisplayLogo():
		WithColor InterpreterColor:
			print """Welcome to booish, an interactive interpreter for the boo programming language.
Running boo ${BooVersion} on ${Boo.Lang.Runtime.RuntimeServices.RuntimeDisplayName}.

Enter boo code in the prompt below (or type /help)."""

	def DisplayHelp():
		WithColor InterpreterColor:
			print """The following builtin functions are available :
    /? or /h or /help : display this help
    dir(type) : returns the members of a type
    describe(type) : describe a type as boo code
    globals() or /g : returns names of all variables known to interpreter
    load(file) or /l file : evals an external boo file
    save(file) or /s file : writes your current booish session into file
    quit() or /q : exits the interpreter"""

	def DisplayGoodbye():	// booish is friendly
		WithColor InterpreterColor:
			print ""
			print "All your boo are belong to us!"

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

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

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

	def Load([required] path as string):
		if path.EndsWith(".boo"):
			ConsolePrintMessage("Evaluating '${path}' ...")
			DisplayResults(_interpreter.EvalCompilerInput(Boo.Lang.Compiler.IO.FileInput(path)))
		else:
			ConsolePrintMessage("Adding reference to '${path}'")
			try:
				_interpreter.References.Add(System.Reflection.Assembly.LoadFrom(path))
			except e:
				ConsolePrintException(e)

	private def ProcessLastValue():
		_ = _interpreter.LastValue
		if _ is not null:
			Console.WriteLine(Boo.Lang.Interpreter.Builtins.repr(_))
			_interpreter.SetValue("_", _)

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

	private _quit = false

	def Quit():
		_quit = true

	def DisplayProblems(problems as ICollection, color as ConsoleColor):
		return if problems is null or problems.Count == 0
		WithColor color:
			for problem as duck in problems:
				markLocation(problem.LexicalInfo)
				type = ("ERROR" if problem isa CompilerError else "WARNING")
				Console.WriteLine("${type}: ${problem.Message}")
		return true

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

	private def CheckBooLangCompilerReferenced():
		return if _blcReferenced
		_interpreter.References.Add(typeof(Boo.Lang.Compiler.CompilerContext).Assembly)
		_blcReferenced = true

	_blcReferenced = false

#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.Collections.Generic
import System.IO
import System.Linq
import System.Text
import System.Text.RegularExpressions
import Boo.Lang.Compiler
import Boo.Lang.Compiler.TypeSystem
import PatternMatching
import Environments

class InteractiveInterpreterConsole:

	public final static HISTORY_FILENAME = "booish_history"
	public final static HISTORY_CAPACITY = 100

	_history = System.Collections.Generic.Queue of string(HISTORY_CAPACITY)
	_historyFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), HISTORY_FILENAME)
	_historyIndex = 0
	_session = System.Collections.Generic.List of string()

	_buffer = StringBuilder()	#buffer to be executed
	_line = StringBuilder()		#line being edited
	_multiline = false			#is the current line a multi-line?

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

	[property(ShowWarnings)]
	_showWarnings = false

	QQBegin = "[|"
	QQEnd= "|]"

	_interpreter as InteractiveInterpreter

	def constructor():
		self(InteractiveInterpreter())

	def constructor(interpreter as InteractiveInterpreter):
		_interpreter = interpreter
		_interpreter.RememberLastValue = true

		DisableColors = not string.IsNullOrEmpty(Environment.GetEnvironmentVariable("BOOISH_DISABLE_COLORS"))
		if not DisableColors: #make sure setting color does not throw an exception
			try:
				Console.ForegroundColor = ConsoleColor.DarkGray
			except:
				DisableColors = true
		DisableAutocompletion = not string.IsNullOrEmpty(Environment.GetEnvironmentVariable("BOOISH_DISABLE_AUTOCOMPLETION"))

		_interpreter.SetValue("load", Load)
		_interpreter.SetValue("save", Save)
		_interpreter.SetValue("quit", Quit)

		LoadHistory()

	def SetValue(name as string, value):
		_interpreter.SetValue(name, value)

	Line:
		get: return _line.ToString()

	LineLastChar:
		get: return (_line.Chars[LineLen-1] if LineLen != 0 else char('\0'))

	LineLen:
		get: return _line.Length
		set: _line.Length = value

	LineIndentLen:
		get: return IndentChars.Length * _indent

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

	PrintModules:
		get: return _interpreter.Pipeline.Find(Boo.Lang.Compiler.Steps.PrintBoo) != -1
		set:
			if value:
				if not PrintModules:
					_interpreter.Pipeline.Add(Boo.Lang.Compiler.Steps.PrintBoo())
			else:
				_interpreter.Pipeline.Remove(Boo.Lang.Compiler.Steps.PrintBoo)

	property DefaultPrompt = ">>> "

	property BlockPrompt = "... "

	property IndentChars = "    "

	property DisableColors = false

	property DisableAutocompletion = false

	# messages from the interpreter (not from user code)
	property InterpreterColor = ConsoleColor.DarkGray

	property PromptColor = ConsoleColor.DarkGreen

	property ExceptionColor = ConsoleColor.DarkRed

	property WarningColor = ConsoleColor.Yellow

	property ErrorColor = ConsoleColor.Red

	property SuggestionsColor = ConsoleColor.DarkYellow

	property SelectedSuggestionColor = ConsoleColor.DarkMagenta

	_selectedSuggestionIndex as int?

	_suggestions as EnvironmentBoundValue[of (object)]

	CanAutoComplete as bool:
		get: return _selectedSuggestionIndex is not null

	private _builtins as (IEntity)
	private _filter as string

	def Eval(code as string):
		try:
			result = _interpreter.Eval(code)
			DisplayResults(result)
			return result
		except x as System.Reflection.TargetInvocationException:
			ConsolePrintException(x.InnerException)
		except x:
			ConsolePrintException(x)

	private def DisplayResults(results as CompilerContext):
		if ShowWarnings:
			DisplayProblems(results.Warnings, WarningColor)
		if not DisplayProblems(results.Errors, ErrorColor):
			ProcessLastValue()

	private def ConsolePrintPrompt():
		ConsolePrintPrompt(true)

	private def ConsolePrintPrompt(autoIndent as bool):
		return if _quit
		WithColor PromptColor:
			Console.Write(CurrentPrompt)
		if autoIndent and CurrentPrompt == BlockPrompt:
			for i in range(_indent):
				WriteIndent()

	private def WithColor(color as ConsoleColor, block as System.Action):
		if DisableColors:
			block()
		else:
			Console.ForegroundColor = color
			try:
				block()
			ensure:
				Console.ResetColor()

	private def ConsolePrintMessage(msg as string):
		WithColor InterpreterColor:
			print msg

	private def ConsolePrintException(e as Exception):
		WithColor ExceptionColor:
			print e

	private def ConsolePrintError(msg as string):
		WithColor ExceptionColor:
			print msg

	private def NewLine():
		Console.Write(Environment.NewLine)

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

		i = 0

		for s in SuggestionDescriptions():
			Console.ForegroundColor = SuggestionsColor if not DisableColors
			Console.Write(", ") if i > 0
			if i > 20: #TODO: maxcandidates pref + paging?
				Console.Write("... (too many candidates)")
				break
			if i == _selectedSuggestionIndex:
				Console.ForegroundColor = SelectedSuggestionColor if not DisableColors
			Console.Write(s)
			i++

		Console.ResetColor() if not DisableColors
		#Console.CursorTop = cursorTop
		NewLine()
		ConsolePrintPrompt(false)
		Console.Write(Line)
		Console.CursorLeft = cursorLeft

	def SuggestionDescriptions():
		return _suggestions.Select(DescriptionsFor).Value

	def DescriptionsFor(suggestions as (object)):
		return array((DescriptionFor(s) for s in suggestions).Distinct())

	def DescriptionFor(s):
		match s:
			case e = IEntity():
				return Boo.Lang.Interpreter.Builtins.DescribeEntity(e)
			otherwise:
				return s.ToString()

	protected def Write(s as string):
		Console.Write(s)
		_line.Append(s)

	protected def WriteIndent():
		Write(IndentChars)

	protected def Indent():
		WriteIndent()
		_indent++

	protected def Unindent():
		return if _indent == 0
		Delete(IndentChars.Length)
		_indent--

	protected def Delete(count as int): #if count is 0, forward-delete
		cx = Console.CursorLeft-len(CurrentPrompt)-count
		return if cx < LineLen and count == 0
		dcount = (count if count != 0 else 1)
		_line.Remove(cx, dcount)
		curX = Console.CursorLeft - dcount
		Console.CursorLeft = curX
		Console.Write("${_line.ToString(cx, LineLen-cx)} ")
		Console.CursorLeft = curX

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

	def DisplaySuggestions():
		DisplaySuggestions(Line)

	def DisplaySuggestions(query as string):
		return if DisableAutocompletion

		#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]
			_filter = query[query.LastIndexOf('.')+1:]
			_suggestions = (
				_interpreter
				.SuggestCompletionsFor(codeToComplete+"__codecomplete__")
				.Select[of (object)](
					{ es | array(e as object for e in es if e.Name.StartsWith(_filter)) }))

		if _suggestions.Value is null or 0 == len(_suggestions.Value): #suggest a  var
			_filter = query
			_suggestions = EnvironmentBoundValue.Create(
							null,
							array(
								var.Key.ToString() as object
								for var in _interpreter.Values
								if var.ToString().StartsWith(_filter)))

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

	def AutoComplete():
		raise InvalidOperationException("no suggestions") if _suggestions.Value is null or _selectedSuggestionIndex is null

		Write(_suggestions.Select[of string]({ ss | AutoCompletionFor(ss[_selectedSuggestionIndex.Value]) }).Value)

		_selectedSuggestionIndex = null
		_suggestions = EnvironmentBoundValue[of (object)](null, null)

	def AutoCompletionFor(s):
		match s:
			case m = IMethod(Name: name):
				if len(m.GetParameters()) == 0:
					return "$(TrimFilter(name))()"
				return "$(TrimFilter(name))("
			case IEntity(Name: name):
				return TrimFilter(name)
			otherwise:
				return TrimFilter(s.ToString())

	def TrimFilter(s as string):
		return s[len(_filter):]

	private _beforeHistory = string.Empty

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

	def ReadEvalPrintLoop():
		Console.CursorVisible = true
		ConsolePrintPrompt()
		while not _quit:
			ReadEvalPrintLoopStep()
		SaveHistory()
		DisplayGoodbye()

	private def ReadEvalPrintLoopStep():
		cki = Console.ReadKey(true)
		key = cki.Key
		keyChar = cki.KeyChar
		control = false

		newLine = keyChar in Environment.NewLine

		if char.IsControl(keyChar):
			control = true
			if keyChar == char('\t'):
				if LineLen > 0 and (char.IsLetterOrDigit(LineLastChar) or LineLastChar == char('.')):
					_selectedSuggestionIndex = 0
					DisplaySuggestions()
				else:
					Indent()

			#line-editing support
			if not _multiline and LineLen > 0:
				if Console.CursorLeft > len(CurrentPrompt):
					if key == ConsoleKey.Backspace:
						if _indent > 0 and LineLen == LineIndentLen:
							Unindent()
						else:
							Delete(1)
					elif key == ConsoleKey.LeftArrow:
						Console.CursorLeft--
				if key == ConsoleKey.Delete:
					Delete(0)
				elif key == ConsoleKey.RightArrow:
					if Console.CursorLeft < (len(CurrentPrompt)+LineLen):
						Console.CursorLeft++
				elif key == ConsoleKey.Home:
					Console.CursorLeft = len(CurrentPrompt)
				elif key == ConsoleKey.End:
					Console.CursorLeft = len(CurrentPrompt) + LineLen

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

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

		_selectedSuggestionIndex = null

		cx = Console.CursorLeft-len(CurrentPrompt)
		#multi-line?
		if cx < 0 or LineLen >= Console.WindowWidth-len(CurrentPrompt):
			cx = LineLen
			_multiline = true

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

		if newLine:
			_multiline = false
			Console.Write(Environment.NewLine)

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

				_indent++ if LineLastChar in _blockStarters or Line.EndsWith(QQBegin)
				_indent-- if Line.EndsWith(IndentChars+"pass")
				if Line.EndsWith(QQEnd):
					CheckBooLangCompilerReferenced()
					_indent--

				if _indent <= 0:
					_indent = 0
					try:
						Eval(_buffer.ToString())
					ensure:
						_buffer.Length = 0 #truncate buffer

			LineLen = 0 #truncate line
			ConsolePrintPrompt()

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

		cmd = line.Split()

		if len(cmd) == 1:
			if cmd[0] == "/q" or cmd[0] == "/quit":
				Quit()
			elif cmd[0] == "/?" or cmd[0] == "/h" or cmd[0] == "/help":
				DisplayHelp()
			elif cmd[0] == "/g" or cmd[0] == "/globals":
				Eval("globals()")
			else:
				return false

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

		else:
			return false
		return true

	private _indent as int = 0

	def DisplayLogo():
		WithColor InterpreterColor:
			print """Welcome to booish, an interactive interpreter for the boo programming language.
Running boo ${BooVersion} on ${Boo.Lang.Runtime.RuntimeServices.RuntimeDisplayName}.

Enter boo code in the prompt below (or type /help)."""

	def DisplayHelp():
		WithColor InterpreterColor:
			print """The following builtin functions are available :
    /? or /h or /help : display this help
    dir(type) : returns the members of a type
    describe(type) : describe a type as boo code
    globals() or /g : returns names of all variables known to interpreter
    load(file) or /l file : evals an external boo file
    save(file) or /s file : writes your current booish session into file
    quit() or /q : exits the interpreter"""

	def DisplayGoodbye():	// booish is friendly
		WithColor InterpreterColor:
			print ""
			print "All your boo are belong to us!"

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

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

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

	def Load([required] path as string):
		if path.EndsWith(".boo"):
			ConsolePrintMessage("Evaluating '${path}' ...")
			DisplayResults(_interpreter.EvalCompilerInput(Boo.Lang.Compiler.IO.FileInput(path)))
		else:
			ConsolePrintMessage("Adding reference to '${path}'")
			try:
				_interpreter.References.Add(System.Reflection.Assembly.LoadFrom(path))
			except e:
				ConsolePrintException(e)

	private def ProcessLastValue():
		_ = _interpreter.LastValue
		if _ is not null:
			Console.WriteLine(Boo.Lang.Interpreter.Builtins.repr(_))
			_interpreter.SetValue("_", _)

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

	private _quit = false

	def Quit():
		_quit = true

	def DisplayProblems(problems as ICollection, color as ConsoleColor):
		return if problems is null or problems.Count == 0
		WithColor color:
			for problem as duck in problems:
				markLocation(problem.LexicalInfo)
				type = ("ERROR" if problem isa CompilerError else "WARNING")
				Console.WriteLine("${type}: ${problem.Message}")
		return true

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

	private def CheckBooLangCompilerReferenced():
		return if _blcReferenced
		_interpreter.References.Add(typeof(Boo.Lang.Compiler.CompilerContext).Assembly)
		_blcReferenced = true

	_blcReferenced = false