Updated array params to offer better support for any enumerable

namespace BooJs.Lang

import BooJs.Lang.Globals
import BooJs.Lang.Extensions

import System.Collections(IEnumerable)

class Builtins:

    [Transform( '%%COMPILER_VERSION%%' )]
    static public final BOO_COMPILER_VERSION = '%%COMPILER_VERSION%%'
    static public final BOO_RUNTIME_VERSION = 'defined in boo.js'

    static public final STOP = 'STOP'

    class Duck(Object, Boo.Lang.IQuackFu):
    """ Implementes the IQuackFu interface
    """
        def QuackGet(name as string, params as (object)) as object:
            pass

        def QuackSet(name as string, params as (object), value as object) as object:
            pass

        def QuackInvoke(name as string, args as (object)) as object:
            pass

    interface ICallable:
    """ Define the ICallable interface
    """
        def Call(args as (Object)) as Object

    class Hash(Object, IEnumerable):
    """ Simple hash/dictionary based on Javascript's object
    """
        [Transform( $2.hasOwnProperty($1) )]
        static def op_Member(lhs as object, rhs as Hash) as bool:
            pass
        [Transform( not $2.hasOwnProperty($1) )]
        static def op_NotMember(lhs as object, rhs as Hash) as bool:
            pass

        self[key as object] as object:
            [Transform( $0[$1] )]
            get: pass
            [Transform( $0[$1] = $2 )]
            set: pass

        [Transform( Boo.Hash() )]
        def constructor():
            pass
        [Transform( Boo.Hash($1) )]
        def constructor(items as object*):
            pass

        # Use a transform to ensure we can use it with plain object literals too
        [Transform( Boo.Hash.keys($0) )]
        def keys() as (string):
            pass
        [Transform( Boo.Hash.values($0) )]
        def values() as (object):
            pass
        [Transform( Boo.enumerate($0) )]
        def items() as ((object)):
            pass

    class AssertionError(Error):
    """ BooJs specific error to signal failures in assertions
    """
        def constructor():
            pass
        def constructor(msg as string):
            pass

    [Transform( Boo.ReturnValue )]
    class ReturnValue:
        public value as object

        def constructor(val as object):
            value = val

    static def range(stop as int) as (int):
        pass
    static def range(start as int, stop as int) as (int):
        pass
    static def range(start as int, stop as int, step as int) as (int):
        pass

    static def print(*list as (object)) as void:
        pass

    #static def cat(*list as (object)) as (object):
    static def cat(*list as (object)) as Array:
    """ Concatenates the given enumerable arguments """
        pass

    static def join(items as object*, separator as string) as string:
        pass
    static def join(items as object*) as string:
        pass

    #static def map(items as object*, callback as callable(object)) as object*:
    static def map(items as Array, callback as callable(object)) as Array:
        pass

    static def reduce[of T](items as (T), callback as callable) as T:
        pass
    static def reduce[of T](items as (T), callback as callable, initial as T) as T:
        pass

    static def filter(items as (object), callback as callable) as (object):
        pass

    static def zip(*arrays as (object)) as (object):
        pass

    static def reversed(items as object*) as Array:
        pass

    [TypeInferenceRule(TypeInferenceRules.ArrayOfTypeReferencedByFirstArgument)]
    static def array(type as System.Type, enumerable as Object*) as (Object): #Array:
        pass
    [TypeInferenceRule(TypeInferenceRules.ArrayOfTypeReferencedByFirstArgument)]
    static def array(type as System.Type, num as int) as Array:
        pass
    static def array[of T](enumerable as T*) as (T):
        pass

    # Allows to iterate accesing indices/keys
    static def enumerate(enumerable as object*) as Array:
        pass

namespace BooJs.Lang

import BooJs.Lang.Globals
import BooJs.Lang.Extensions

class Builtins:

    [Transform( '%%COMPILER_VERSION%%' )]
    static public final BOO_COMPILER_VERSION = '%%COMPILER_VERSION%%'
    static public final BOO_RUNTIME_VERSION = 'defined in boo.js'

    static public final STOP = 'STOP'

    class Duck(Object, Boo.Lang.IQuackFu):
    """ Implementes the IQuackFu interface
    """
        def QuackGet(name as string, params as (object)) as object:
            pass

        def QuackSet(name as string, params as (object), value as object) as object:
            pass

        def QuackInvoke(name as string, args as (object)) as object:
            pass

    interface ICallable:
    """ Define the ICallable interface
    """
        def Call(args as (Object)) as Object

    class Hash(Object):
    """ Simple hash/dictionary based on Javascript's object
    """
        [Transform( $2.hasOwnProperty($1) )]
        static def op_Member(lhs as object, rhs as Hash) as bool:
            pass
        [Transform( not $2.hasOwnProperty($1) )]
        static def op_NotMember(lhs as object, rhs as Hash) as bool:
            pass

        self[key as string] as object:
            [Transform( $0[$1] )]
            get: pass
            [Transform( $0[$1] = $2 )]
            set: pass

        [Transform( Boo.Hash() )]
        def constructor():
            pass
        [Transform( Boo.Hash($1) )]
        def constructor(items as object*):
            pass

        # Use a transform to ensure we can use it with plain object literals too
        [Transform( Boo.Hash.keys($0) )]
        def keys() as (string):
            pass
        [Transform( Boo.Hash.values($0) )]
        def values() as (object):
            pass
        [Transform( Boo.enumerate($0) )]
        def items() as ((object)):
            pass

    class AssertionError(Error):
    """ BooJs specific error to signal failures in assertions
    """
        def constructor():
            pass
        def constructor(msg as string):
            pass

    [Transform( Boo.ReturnValue )]
    class ReturnValue:
        public value as object

        def constructor(val as object):
            value = val

    static def range(stop as int) as (int):
        pass
    static def range(start as int, stop as int) as (int):
        pass
    static def range(start as int, stop as int, step as int) as (int):
        pass

    static def print(*list as (object)) as void:
        pass

    static def cat(*list as (object)) as (object):
    """ Concatenates the given enumerable arguments """
        pass

    static def join(items as (object), separator as string) as string:
        pass
    static def join(items as (object)) as string:
        pass
    #static def join(items as Array, separator as string) as string:
    #    pass
    #static def join(items as Array) as string:
    #    pass
    static def map(items as (object), callback as callable) as (object):
        pass

    static def reduce[of T](items as (T), callback as callable) as T:
        pass
    static def reduce[of T](items as (T), callback as callable, initial as T) as T:
        pass

    static def filter(items as (object), callback as callable) as (object):
        pass

    static def zip(*arrays as (object)) as (object):
        pass

    static def reversed(items as (object)) as (object):
        pass
    static def reversed(items as string) as (object):
        pass

    [TypeInferenceRule(TypeInferenceRules.ArrayOfTypeReferencedByFirstArgument)]
    static def array(type as System.Type, enumerable as Object*) as (Object): #Array:
        pass
    [TypeInferenceRule(TypeInferenceRules.ArrayOfTypeReferencedByFirstArgument)]
    static def array(type as System.Type, num as int) as (Object): #Array
        pass
    static def array[of T](enumerable as T*) as (T):
        pass

    # Allows to iterate accesing indices/keys
    static def enumerate(enumerable as (object)) as (object): #Array:
        pass


branch should be reset after build (to preserve the assembly info file state)

import System.Environment
import System.IO

import GitSharp

desc "initialization"
target init, (generate_assembly_info):
	repo = Repository(Directory.GetCurrentDirectory())
	branch = repo.CurrentBranch
	branch.Reset("HEAD" ResetBehavior.Hard)

	print 'initialized'

desc "Generating the shared assemblyInfo with version info"
target generate_assembly_info:
	majorVersion = File.ReadAllText('version').Trim()
	buildNumber = env('BUILD_NUMBER')
	buildNumber = "0" if (string.IsNullOrEmpty(buildNumber))
	with generate_assembly_info():
		.file = 'src\\SolutionAssemblyInfo.cs'
		.version = majorVersion + "." + buildNumber

desc "entry point for the teamcity build"
target default:
	repo = Repository(Directory.GetCurrentDirectory())
	print repo.Head.CurrentCommit.CommitDate
	print repo.Head.CurrentCommit.Hash
	print repo.Head.CurrentCommit.Message
	print repo.Head.CurrentCommit.IsTag
	print repo.CurrentBranch.Name
	print 'BUILD_NUMBER: ' + env('BUILD_NUMBER')
	print 'build.number: ' + env('build.number')
	print 'momo: ' + env('momo')
	print 'args:'
	for a in GetCommandLineArgs():
		print '  ' + a

import System.Environment
import System.IO

import GitSharp

desc "initialization"
target init, (generate_assembly_info):
	print 'initialized'

desc "Generating the shared assemblyInfo with version info"
target generate_assembly_info:
	majorVersion = File.ReadAllText('version').Trim()
	buildNumber = env('BUILD_NUMBER')
	buildNumber = "0" if (string.IsNullOrEmpty(buildNumber))
	with generate_assembly_info():
		.file = 'src\\SolutionAssemblyInfo'
		.version = majorVersion + "." + buildNumber

desc "entry point for the teamcity build"
target default:
	repo = Repository(System.IO.Directory.GetCurrentDirectory())
	print repo.Head.CurrentCommit.CommitDate
	print repo.Head.CurrentCommit.Hash
	print repo.Head.CurrentCommit.Message
	print repo.Head.CurrentCommit.IsTag
	print repo.CurrentBranch.Name
	print 'BUILD_NUMBER: ' + env('BUILD_NUMBER')
	print 'build.number: ' + env('build.number')
	print 'momo: ' + env('momo')
	print 'args:'
	for a in GetCommandLineArgs():
		print '  ' + a


WarningRegularExpression wasn't picking up warnings without lexical information (which is valid apparently)

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

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

namespace Boo.Microsoft.Build.Tasks

import Microsoft.Build.Framework
import Microsoft.Build.Tasks
import Microsoft.Build.Utilities
import System
import System.Diagnostics
import System.IO
import System.Globalization
import System.Text.RegularExpressions
import System.Threading

class Booc(ManagedCompiler):
"""
Represents the Boo compiler MSBuild task.

Authors:
	Sorin Ionescu (sorin.ionescu@gmail.com)
"""
	def constructor():
		NoLogo = true

	Pipeline:
	"""
	Gets/sets a specific pipeline to add to the compiler process.
	"""
		get:
			return Bag['Pipelines'] as string
		set:
			Bag['Pipelines'] = value
	NoStandardLib:
	"""
	Gets/sets if we want to link to the standard libraries or not.
	"""
		get:
			return GetBoolParameterWithDefault("NoStandardLib", false)
		set:
			Bag['NoStandardLib'] = value
	WhiteSpaceAgnostic:
	"""
	Gets/sets if we want to use whitespace agnostic mode.
	"""
		get:
			return GetBoolParameterWithDefault("WhiteSpaceAgnostic", false)
		set:
			Bag['WhiteSpaceAgnostic'] = value
	Ducky:
	"""
	Gets/sets if we want to use ducky mode.
	"""
		get:
			return GetBoolParameterWithDefault("Ducky", false)
		set:
			Bag['Ducky'] = value
	Verbosity:
	"""
	Gets/sets the verbosity level.
	"""
		get:
			return Bag['Verbosity'] as string
		set:
			Bag['Verbosity'] = value

	Culture:
	"""
	Gets/sets the culture.
	"""
		get:
			return Bag['Culture'] as string
		set:
			Bag['Culture'] = value

	SourceDirectory:
	"""
	Gets/sets the source directory.
	"""
		get:
			return Bag['Source Directory'] as string
		set:
			Bag['Source Directory'] = value

	ToolName:
	"""
	Gets the tool name.
	"""
		get:
			return "booc.exe"

	override def Execute():
	"""
	Executes the task.

	Returns:
		true if the task completed successfully; otherwise, false.
	"""
		boocCommandLine = CommandLineBuilderExtension()
		AddResponseFileCommands(boocCommandLine)

		warningPattern = regex(
			'^(?<file>.*?)(\\((?<line>\\d+),(?<column>\\d+)\\):)?' +
				'(\\s?)(?BCW\\d{4}):(\\s)WARNING:(\\s)(?<message>.*)$',
			RegexOptions.Compiled)
		# Captures the file, line, column, code, and message from a BOO warning
		# in the form of: Program.boo(1,1): BCW0000: WARNING: This is a warning.

		errorPattern = regex(
			'^(((?<file>.*?)\\((?<line>\\d+),(?<column>\\d+)\\): )?' +
				'(?BCE\\d{4})|(?<errorType>Fatal) error):' +
				'( Boo.Lang.Compiler.CompilerError:)?' +
				' (?<message>.*?)($| --->)',
			RegexOptions.Compiled |
				RegexOptions.ExplicitCapture |
				RegexOptions.Multiline)
		/*
		 * Captures the file, line, column, code, error type, and message from a
		 * BOO error of the form of:
		 * 1. Program.boo(1,1): BCE0000: This is an error.
		 * 2. Program.boo(1,1): BCE0000: Boo.Lang.Compiler.CompilerError:
		 *    	This is an error. ---> Program.boo:4:19: This is an error
		 * 3. BCE0000: This is an error.
		 * 4. Fatal error: This is an error.
		 *
		 * The second line of the following error format is not cought because
		 * .NET does not support if|then|else in regular expressions,
		 * and the regex will be horrible complicated.
		 * The second line is as worthless as the first line.
		 * Therefore, it is not worth implementing it.
		 *
		 * 	Fatal error: This is an error.
		 * 	Parameter name: format.
		 */

		buildSuccess = true
		outputLine = String.Empty
		errorLine = String.Empty
		readingDoneEvents = (ManualResetEvent(false), ManualResetEvent(false))

		boocProcessStartInfo = ProcessStartInfo(
			FileName: GenerateFullPathToTool(),
			Arguments: boocCommandLine.ToString(),
			ErrorDialog: false,
			CreateNoWindow: true,
			RedirectStandardError: true,
			RedirectStandardInput: false,
			RedirectStandardOutput: true,
			UseShellExecute: false)

		boocProcess = Process(StartInfo: boocProcessStartInfo)

		parseOutput = def(line as string):
			warningPatternMatch = warningPattern.Match(line)
			errorPatternMatch = errorPattern.Match(line)

			if warningPatternMatch.Success:
				lineOut = 0
				columnOut = 0
				int.TryParse(warningPatternMatch.Groups['line'].Value, lineOut)
				int.TryParse(warningPatternMatch.Groups['column'].Value, columnOut)
				Log.LogWarning(
					null,
					warningPatternMatch.Groups['code'].Value,
					null,
					warningPatternMatch.Groups['file'].Value,
					lineOut,
					columnOut,
					0,
					0,
					warningPatternMatch.Groups['message'].Value)

			elif errorPatternMatch.Success:
				code = errorPatternMatch.Groups['code'].Value
				code = 'BCE0000' if string.IsNullOrEmpty(code)
				file = errorPatternMatch.Groups['file'].Value
				file = 'BOOC' if string.IsNullOrEmpty(file)

				try:
					lineNumber = int.Parse(
						errorPatternMatch.Groups['line'].Value,
						NumberStyles.Integer)

				except FormatException:
					lineNumber = 0

				try:
					columnNumber = int.Parse(
						errorPatternMatch.Groups['column'].Value,
						NumberStyles.Integer)

				except FormatException:
					columnNumber = 0

				Log.LogError(
					errorPatternMatch.Groups['errorType'].Value.ToLower(),
					code,
					null,
					file,
					lineNumber,
					columnNumber,
					0,
					0,
					errorPatternMatch.Groups['message'].Value)

				buildSuccess = false

			else:
				Log.LogMessage(MessageImportance.Normal, line)

		readStandardOutput = def():
			while true:
				outputLine = boocProcess.StandardOutput.ReadLine()

				if outputLine:
					parseOutput(outputLine)

				else:
					readingDoneEvents[0].Set()
					break

		readStandardError = def():
			while true:
				errorLine = boocProcess.StandardError.ReadLine()

				if errorLine:
					parseOutput(errorLine)

				else:
					readingDoneEvents[1].Set()
					break

		standardOutputReadingThread = Thread(readStandardOutput as ThreadStart)
		standardErrorReadingThread = Thread(readStandardError as ThreadStart)
		# Two threads are required (MSDN); otherwise, a deadlock WILL occur.

		try:
			boocProcess.Start()

			Log.LogMessage(
				MessageImportance.High,
				"${ToolName} ${boocProcess.StartInfo.Arguments}",
				null)

			standardOutputReadingThread.Start()
			standardErrorReadingThread.Start()

			WaitHandle.WaitAny((readingDoneEvents[0],))
			WaitHandle.WaitAny((readingDoneEvents[1],))
			# MSBuild runs on an STA thread, and WaitHandle.WaitAll()
			# is not supported.

		except e as Exception:
			Log.LogErrorFromException(e)
			buildSuccess = false

		ensure:
			boocProcess.Close()

		return buildSuccess

	protected override def AddCommandLineCommands(
		commandLine as CommandLineBuilderExtension):
	"""
	Adds command line commands.

	Remarks:
		It prevents <ManagedCompiler> from adding the standard commands.
	"""
			pass

	protected override def AddResponseFileCommands(
		commandLine as CommandLineBuilderExtension):
	"""
	Generates the Boo compiler command line.

	Returns:
		The Boo compiler command line.
	"""
		commandLine.AppendSwitchIfNotNull('-t:', TargetType)
		commandLine.AppendSwitchIfNotNull('-o:', OutputAssembly)
		commandLine.AppendSwitchIfNotNull('-c:', Culture)
		commandLine.AppendSwitchIfNotNull('-srcdir:', SourceDirectory)
		commandLine.AppendSwitchIfNotNull('-keyfile:', KeyFile)
		commandLine.AppendSwitchIfNotNull('-keycontainer:', KeyContainer)
		commandLine.AppendSwitchIfNotNull('-p:', Pipeline)
		commandLine.AppendSwitchIfNotNull("-lib:", AdditionalLibPaths, ",")

		if NoLogo:
			commandLine.AppendSwitch('-nologo')
		if NoConfig:
			commandLine.AppendSwitch('-noconfig')
		if NoStandardLib:
			commandLine.AppendSwitch('-nostdlib')
		if DelaySign:
			commandLine.AppendSwitch('-delaysign')
		if WhiteSpaceAgnostic:
			commandLine.AppendSwitch('-wsa')
		if Ducky:
			commandLine.AppendSwitch('-ducky')

		if EmitDebugInformation:
			commandLine.AppendSwitch('-debug')
		else:
			commandLine.AppendSwitch('-debug-')

		if ResponseFiles:
			for rsp in ResponseFiles:
				commandLine.AppendSwitchIfNotNull("@", rsp.ItemSpec)

		if References:
			for reference in References:
				commandLine.AppendSwitchIfNotNull('-r:', reference.ItemSpec)

		if Resources:
			for resource in Resources:
				commandLine.AppendSwitchIfNotNull('-resource:', resource.ItemSpec)

		if Verbosity:
			if string.Compare(
					Verbosity,
					'Normal',
					StringComparison.InvariantCultureIgnoreCase) == 0:
				pass

			elif string.Compare(
					Verbosity,
					'Warning',
					StringComparison.InvariantCultureIgnoreCase) == 0:

				commandLine.AppendSwitch('-v')

			elif string.Compare(
					Verbosity,
					'Info',
					StringComparison.InvariantCultureIgnoreCase) == 0:

				commandLine.AppendSwitch('-vv')

			elif string.Compare(
					Verbosity,
					'Verbose',
					StringComparison.InvariantCultureIgnoreCase) == 0:

				commandLine.AppendSwitch('-vvv')

			else:
				Log.LogErrorWithCodeFromResources(
					'Vbc.EnumParameterHasInvalidValue',
					'Verbosity',
					Verbosity,
					'Normal, Warning, Info, Verbose')

		commandLine.AppendFileNamesIfNotNull(Sources, ' ')

	protected override def GenerateFullPathToTool():
	"""
	Generats the full path to booc.exe.
	"""
		path = ""

		if ToolPath:
			path = Path.Combine(ToolPath, ToolName)

		return path if File.Exists(path)

		path = Path.Combine(
			Path.GetDirectoryName(typeof(Booc).Assembly.Location),
			ToolName)

		return path if File.Exists(path)

		path = ToolLocationHelper.GetPathToDotNetFrameworkFile(
			ToolName,
			TargetDotNetFrameworkVersion.VersionLatest)

		return path if File.Exists(path)

		/* //removed this error message for mono compatibility
		Log.LogErrorWithCodeFromResources(
			"General.FrameworksFileNotFound",
			ToolName,
			ToolLocationHelper.GetDotNetFrameworkVersionFolderPrefix(
				TargetDotNetFrameworkVersion.Version20))
		*/
		path = "booc"

		return path

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

namespace Boo.Microsoft.Build.Tasks

import Microsoft.Build.Framework
import Microsoft.Build.Tasks
import Microsoft.Build.Utilities
import System
import System.Diagnostics
import System.IO
import System.Globalization
import System.Text.RegularExpressions
import System.Threading

class Booc(ManagedCompiler):
"""
Represents the Boo compiler MSBuild task.

Authors:
	Sorin Ionescu (sorin.ionescu@gmail.com)
"""
	def constructor():
		NoLogo = true

	Pipeline:
	"""
	Gets/sets a specific pipeline to add to the compiler process.
	"""
		get:
			return Bag['Pipelines'] as string
		set:
			Bag['Pipelines'] = value
	NoStandardLib:
	"""
	Gets/sets if we want to link to the standard libraries or not.
	"""
		get:
			return GetBoolParameterWithDefault("NoStandardLib", false)
		set:
			Bag['NoStandardLib'] = value
	WhiteSpaceAgnostic:
	"""
	Gets/sets if we want to use whitespace agnostic mode.
	"""
		get:
			return GetBoolParameterWithDefault("WhiteSpaceAgnostic", false)
		set:
			Bag['WhiteSpaceAgnostic'] = value
	Ducky:
	"""
	Gets/sets if we want to use ducky mode.
	"""
		get:
			return GetBoolParameterWithDefault("Ducky", false)
		set:
			Bag['Ducky'] = value
	Verbosity:
	"""
	Gets/sets the verbosity level.
	"""
		get:
			return Bag['Verbosity'] as string
		set:
			Bag['Verbosity'] = value

	Culture:
	"""
	Gets/sets the culture.
	"""
		get:
			return Bag['Culture'] as string
		set:
			Bag['Culture'] = value

	SourceDirectory:
	"""
	Gets/sets the source directory.
	"""
		get:
			return Bag['Source Directory'] as string
		set:
			Bag['Source Directory'] = value

	ToolName:
	"""
	Gets the tool name.
	"""
		get:
			return "booc.exe"

	override def Execute():
	"""
	Executes the task.

	Returns:
		true if the task completed successfully; otherwise, false.
	"""
		boocCommandLine = CommandLineBuilderExtension()
		AddResponseFileCommands(boocCommandLine)

		warningPattern = regex(
			'^(?<file>.*?)\\((?<line>\\d+),(?<column>\\d+)\\):' +
				' (?BCW\\d{4}): WARNING: (?<message>.*)$',
			RegexOptions.Compiled)
		# Captures the file, line, column, code, and message from a BOO warning
		# in the form of: Program.boo(1,1): BCW0000: WARNING: This is a warning.

		errorPattern = regex(
			'^(((?<file>.*?)\\((?<line>\\d+),(?<column>\\d+)\\): )?' +
				'(?BCE\\d{4})|(?<errorType>Fatal) error):' +
				'( Boo.Lang.Compiler.CompilerError:)?' +
				' (?<message>.*?)($| --->)',
			RegexOptions.Compiled |
				RegexOptions.ExplicitCapture |
				RegexOptions.Multiline)
		/*
		 * Captures the file, line, column, code, error type, and message from a
		 * BOO error of the form of:
		 * 1. Program.boo(1,1): BCE0000: This is an error.
		 * 2. Program.boo(1,1): BCE0000: Boo.Lang.Compiler.CompilerError:
		 *    	This is an error. ---> Program.boo:4:19: This is an error
		 * 3. BCE0000: This is an error.
		 * 4. Fatal error: This is an error.
		 *
		 * The second line of the following error format is not cought because
		 * .NET does not support if|then|else in regular expressions,
		 * and the regex will be horrible complicated.
		 * The second line is as worthless as the first line.
		 * Therefore, it is not worth implementing it.
		 *
		 * 	Fatal error: This is an error.
		 * 	Parameter name: format.
		 */

		buildSuccess = true
		outputLine = String.Empty
		errorLine = String.Empty
		readingDoneEvents = (ManualResetEvent(false), ManualResetEvent(false))

		boocProcessStartInfo = ProcessStartInfo(
			FileName: GenerateFullPathToTool(),
			Arguments: boocCommandLine.ToString(),
			ErrorDialog: false,
			CreateNoWindow: true,
			RedirectStandardError: true,
			RedirectStandardInput: false,
			RedirectStandardOutput: true,
			UseShellExecute: false)

		boocProcess = Process(StartInfo: boocProcessStartInfo)

		parseOutput = def(line as string):
			warningPatternMatch = warningPattern.Match(line)
			errorPatternMatch = errorPattern.Match(line)

			if warningPatternMatch.Success:
				Log.LogWarning(
					null,
					warningPatternMatch.Groups['code'].Value,
					null,
					warningPatternMatch.Groups['file'].Value,
					int.Parse(warningPatternMatch.Groups['line'].Value),
					int.Parse(warningPatternMatch.Groups['column'].Value),
					0,
					0,
					warningPatternMatch.Groups['message'].Value)

			elif errorPatternMatch.Success:
				code = errorPatternMatch.Groups['code'].Value
				code = 'BCE0000' if string.IsNullOrEmpty(code)
				file = errorPatternMatch.Groups['file'].Value
				file = 'BOOC' if string.IsNullOrEmpty(file)

				try:
					lineNumber = int.Parse(
						errorPatternMatch.Groups['line'].Value,
						NumberStyles.Integer)

				except FormatException:
					lineNumber = 0

				try:
					columnNumber = int.Parse(
						errorPatternMatch.Groups['column'].Value,
						NumberStyles.Integer)

				except FormatException:
					columnNumber = 0

				Log.LogError(
					errorPatternMatch.Groups['errorType'].Value.ToLower(),
					code,
					null,
					file,
					lineNumber,
					columnNumber,
					0,
					0,
					errorPatternMatch.Groups['message'].Value)

				buildSuccess = false

			else:
				Log.LogMessage(MessageImportance.Normal, line)

		readStandardOutput = def():
			while true:
				outputLine = boocProcess.StandardOutput.ReadLine()

				if outputLine:
					parseOutput(outputLine)

				else:
					readingDoneEvents[0].Set()
					break

		readStandardError = def():
			while true:
				errorLine = boocProcess.StandardError.ReadLine()

				if errorLine:
					parseOutput(errorLine)

				else:
					readingDoneEvents[1].Set()
					break

		standardOutputReadingThread = Thread(readStandardOutput as ThreadStart)
		standardErrorReadingThread = Thread(readStandardError as ThreadStart)
		# Two threads are required (MSDN); otherwise, a deadlock WILL occur.

		try:
			boocProcess.Start()

			Log.LogMessage(
				MessageImportance.High,
				"${ToolName} ${boocProcess.StartInfo.Arguments}",
				null)

			standardOutputReadingThread.Start()
			standardErrorReadingThread.Start()

			WaitHandle.WaitAny((readingDoneEvents[0],))
			WaitHandle.WaitAny((readingDoneEvents[1],))
			# MSBuild runs on an STA thread, and WaitHandle.WaitAll()
			# is not supported.

		except e as Exception:
			Log.LogErrorFromException(e)
			buildSuccess = false

		ensure:
			boocProcess.Close()

		return buildSuccess

	protected override def AddCommandLineCommands(
		commandLine as CommandLineBuilderExtension):
	"""
	Adds command line commands.

	Remarks:
		It prevents <ManagedCompiler> from adding the standard commands.
	"""
			pass

	protected override def AddResponseFileCommands(
		commandLine as CommandLineBuilderExtension):
	"""
	Generates the Boo compiler command line.

	Returns:
		The Boo compiler command line.
	"""
		commandLine.AppendSwitchIfNotNull('-t:', TargetType)
		commandLine.AppendSwitchIfNotNull('-o:', OutputAssembly)
		commandLine.AppendSwitchIfNotNull('-c:', Culture)
		commandLine.AppendSwitchIfNotNull('-srcdir:', SourceDirectory)
		commandLine.AppendSwitchIfNotNull('-keyfile:', KeyFile)
		commandLine.AppendSwitchIfNotNull('-keycontainer:', KeyContainer)
		commandLine.AppendSwitchIfNotNull('-p:', Pipeline)
		commandLine.AppendSwitchIfNotNull("-lib:", AdditionalLibPaths, ",")

		if NoLogo:
			commandLine.AppendSwitch('-nologo')
		if NoConfig:
			commandLine.AppendSwitch('-noconfig')
		if NoStandardLib:
			commandLine.AppendSwitch('-nostdlib')
		if DelaySign:
			commandLine.AppendSwitch('-delaysign')
		if WhiteSpaceAgnostic:
			commandLine.AppendSwitch('-wsa')
		if Ducky:
			commandLine.AppendSwitch('-ducky')

		if EmitDebugInformation:
			commandLine.AppendSwitch('-debug')
		else:
			commandLine.AppendSwitch('-debug-')

		if ResponseFiles:
			for rsp in ResponseFiles:
				commandLine.AppendSwitchIfNotNull("@", rsp.ItemSpec)

		if References:
			for reference in References:
				commandLine.AppendSwitchIfNotNull('-r:', reference.ItemSpec)

		if Resources:
			for resource in Resources:
				commandLine.AppendSwitchIfNotNull('-resource:', resource.ItemSpec)

		if Verbosity:
			if string.Compare(
					Verbosity,
					'Normal',
					StringComparison.InvariantCultureIgnoreCase) == 0:
				pass

			elif string.Compare(
					Verbosity,
					'Warning',
					StringComparison.InvariantCultureIgnoreCase) == 0:

				commandLine.AppendSwitch('-v')

			elif string.Compare(
					Verbosity,
					'Info',
					StringComparison.InvariantCultureIgnoreCase) == 0:

				commandLine.AppendSwitch('-vv')

			elif string.Compare(
					Verbosity,
					'Verbose',
					StringComparison.InvariantCultureIgnoreCase) == 0:

				commandLine.AppendSwitch('-vvv')

			else:
				Log.LogErrorWithCodeFromResources(
					'Vbc.EnumParameterHasInvalidValue',
					'Verbosity',
					Verbosity,
					'Normal, Warning, Info, Verbose')

		commandLine.AppendFileNamesIfNotNull(Sources, ' ')

	protected override def GenerateFullPathToTool():
	"""
	Generats the full path to booc.exe.
	"""
		path = ""

		if ToolPath:
			path = Path.Combine(ToolPath, ToolName)

		return path if File.Exists(path)

		path = Path.Combine(
			Path.GetDirectoryName(typeof(Booc).Assembly.Location),
			ToolName)

		return path if File.Exists(path)

		path = ToolLocationHelper.GetPathToDotNetFrameworkFile(
			ToolName,
			TargetDotNetFrameworkVersion.VersionLatest)

		return path if File.Exists(path)

		/* //removed this error message for mono compatibility
		Log.LogErrorWithCodeFromResources(
			"General.FrameworksFileNotFound",
			ToolName,
			ToolLocationHelper.GetDotNetFrameworkVersionFolderPrefix(
				TargetDotNetFrameworkVersion.Version20))
		*/
		path = "booc"

		return path


CTRL+F5 accelerator for Execute

CTRL+F5 accelerator for Execute

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

import System
import Boo.IO.TextFile
import Gdk from "gdk-sharp" as Gdk
import Gtk from "gtk-sharp"
import GtkSourceView from "gtksourceview-sharp"

class BooEditor(ScrolledWindow):

	static _booSourceLanguage = SourceLanguagesManager().GetLanguageFromMimeType("text/x-boo")

	_buffer = SourceBuffer(_booSourceLanguage, Highlight: true)
	_view = SourceView(_buffer, ShowLineNumbers: true, AutoIndent: true)

	[getter(FileName)]
	_fname as string

	def constructor([required] fname as string):
		_fname = fname
		self.SetPolicy(PolicyType.Automatic, PolicyType.Automatic)
		self.Add(_view)

	Text:
		get:
			return _buffer.Text

		set:
			_buffer.Text = value

	def Redo():
		_buffer.Redo()

	def Undo():
		_buffer.Undo()

class MainWindow(Window):

	_status = Statusbar(HasResizeGrip: false)
	_notebookEditors = Notebook(TabPos: PositionType.Top, Scrollable: true)
	_notebookHelpers = Notebook(TabPos: PositionType.Top, Scrollable: true)
	_notebookOutline = Notebook(TabPos: PositionType.Bottom, Scrollable: true)
	_documentOutline = TreeView()
	_accelGroup = AccelGroup()
	_editors = [] # workaround for gtk# bug #61703

	def constructor():
		super("Boo Explorer")

		self.AddAccelGroup(_accelGroup)
		self.SetDefaultSize(600, 400)
		self.DeleteEvent += OnDelete

		_notebookOutline.AppendPage(_documentOutline, Label("Document Outline"))

		vbox = VBox(false, 1)
		vbox.PackStart(CreateMenuBar(), false, false, 0)

		editPanel = VPaned()
		editPanel.Pack1(_notebookEditors, true, true)
		editPanel.Pack2(_notebookHelpers, false, true)

		mainPanel = HPaned()
		mainPanel.Pack2(_notebookOutline, false, false)
		mainPanel.Pack1(editPanel, true, true)

		vbox.PackStart(mainPanel, true, true, 0)
		vbox.PackStart(_status, false, false, 0)

		self.Add(vbox)

		self.NewDocument()

	private def AppendEditor(editor as BooEditor):
		_notebookEditors.AppendPage(editor, Label(editor.FileName))
		_editors.Add(editor)
		editor.ShowAll()

	def NewDocument():
		self.AppendEditor(BooEditor("unnamed.boo"))

	def OpenDocument(fname as string):
		self.AppendEditor(BooEditor(fname, Text: ReadFile(fname)))

	private def CreateMenuBar():
		mb = MenuBar()

		file = Menu()
		file.Append(ImageMenuItem(Stock.New, _accelGroup, Activated: _menuItemNew_Activated))
		file.Append(ImageMenuItem(Stock.Open, _accelGroup, Activated: _menuItemOpen_Activated))
		file.Append(ImageMenuItem(Stock.Save, _accelGroup, Activated: _menuItemSave_Activated))
		file.Append(SeparatorMenuItem())
		file.Append(ImageMenuItem(Stock.Quit, _accelGroup, Activated: _menuItemExit_Activated))

		edit = Menu()
		edit.Append(ImageMenuItem(Stock.Undo, _accelGroup, Activated: _menuItemUndo_Activated))
		edit.Append(ImageMenuItem(Stock.Redo, _accelGroup, Activated: _menuItemRedo_Activated))
		edit.Append(SeparatorMenuItem())
		edit.Append(ImageMenuItem(Stock.Cut, _accelGroup))
		edit.Append(ImageMenuItem(Stock.Copy, _accelGroup))
		edit.Append(ImageMenuItem(Stock.Paste, _accelGroup))
		edit.Append(ImageMenuItem(Stock.Delete, _accelGroup))
		edit.Append(SeparatorMenuItem())
		edit.Append(ImageMenuItem(Stock.Preferences, _accelGroup))

		tools = Menu()
		tools.Append(mi=ImageMenuItem(Stock.Execute, _accelGroup, Activated: _menuItemExecute_Activated))
		mi.AddAccelerator("activate", _accelGroup, AccelKey(Gdk.Key.F5, Gdk.ModifierType.ControlMask, AccelFlags.Visible))

		documents = Menu()
		documents.Append(ImageMenuItem(Stock.Close, _accelGroup))

		mb.Append(MenuItem("_File", Submenu: file))
		mb.Append(MenuItem("_Edit", Submenu: edit))
		mb.Append(MenuItem("_Tools", Submenu: tools))
		mb.Append(MenuItem("_Documents", Submenu: documents))
		return mb

	CurrentEditor as BooEditor:
		get:
			// can't do the simpler:
			// editor as BooEditor = _notebookEditors.CurrentPageWidget
			// because of gtk# bug #61703
			return _editors[_notebookEditors.CurrentPage]

	private def _menuItemExecute_Activated(sender, args as EventArgs):

		compiler = Boo.Lang.Compiler.BooCompiler()
		compiler.Parameters.Input.Add(
				Boo.Lang.Compiler.IO.StringInput("<script>", CurrentEditor.Text))
		compiler.Parameters.Pipeline = Boo.Lang.Compiler.Pipelines.Run()

		result = compiler.Run()
		print(result.Errors.ToString()) if len(result.Errors)

	private def _menuItemNew_Activated(sender, args as EventArgs):
		self.NewDocument()

	private def _menuItemOpen_Activated(sender, args as EventArgs):
		fs = FileSelection("Select a file", SelectMultiple: true)
		try:
			if cast(int, ResponseType.Ok) == fs.Run():
				for fname in fs.Selections:
					self.OpenDocument(fname)
		ensure:
			fs.Hide()

	private def _menuItemSave_Activated(sender, args as EventArgs):
		pass

	private def _menuItemUndo_Activated(sender, args as EventArgs):
		CurrentEditor.Undo()

	private def _menuItemRedo_Activated(sender, args as EventArgs):
		CurrentEditor.Redo()

	private def _menuItemExit_Activated(sender, args as EventArgs):
		Application.Quit()

	def OnDelete(sender, args as DeleteEventArgs):
		Application.Quit()
		args.RetVal = true

Application.Init()

MainWindow().ShowAll()

Application.Run()

import System
import Boo.IO.TextFile
import Gtk from "gtk-sharp"
import GtkSourceView from "gtksourceview-sharp"

class BooEditor(ScrolledWindow):

	static _booSourceLanguage = SourceLanguagesManager().GetLanguageFromMimeType("text/x-boo")

	_buffer = SourceBuffer(_booSourceLanguage, Highlight: true)
	_view = SourceView(_buffer, ShowLineNumbers: true, AutoIndent: true)

	[getter(FileName)]
	_fname as string

	def constructor([required] fname as string):
		_fname = fname
		self.SetPolicy(PolicyType.Automatic, PolicyType.Automatic)
		self.Add(_view)

	Text:
		get:
			return _buffer.Text

		set:
			_buffer.Text = value

	def Redo():
		_buffer.Redo()

	def Undo():
		_buffer.Undo()

class MainWindow(Window):

	_status = Statusbar(HasResizeGrip: false)
	_notebookEditors = Notebook(TabPos: PositionType.Top, Scrollable: true)
	_notebookHelpers = Notebook(TabPos: PositionType.Top, Scrollable: true)
	_notebookOutline = Notebook(TabPos: PositionType.Bottom, Scrollable: true)
	_documentOutline = TreeView()
	_accelGroup = AccelGroup()
	_editors = [] # workaround for gtk# bug #61703

	def constructor():
		super("Boo Explorer")

		self.AddAccelGroup(_accelGroup)
		self.SetDefaultSize(600, 400)
		self.DeleteEvent += OnDelete

		_notebookOutline.AppendPage(_documentOutline, Label("Document Outline"))

		vbox = VBox(false, 1)
		vbox.PackStart(CreateMenuBar(), false, false, 0)

		editPanel = VPaned()
		editPanel.Pack1(_notebookEditors, true, true)
		editPanel.Pack2(_notebookHelpers, false, true)

		mainPanel = HPaned()
		mainPanel.Pack2(_notebookOutline, false, false)
		mainPanel.Pack1(editPanel, true, true)

		vbox.PackStart(mainPanel, true, true, 0)
		vbox.PackStart(_status, false, false, 0)

		self.Add(vbox)

		self.NewDocument()

	private def AppendEditor(editor as BooEditor):
		_notebookEditors.AppendPage(editor, Label(editor.FileName))
		_editors.Add(editor)
		editor.ShowAll()

	def NewDocument():
		self.AppendEditor(BooEditor("unnamed.boo"))

	def OpenDocument(fname as string):
		self.AppendEditor(BooEditor(fname, Text: ReadFile(fname)))

	private def CreateMenuBar():
		mb = MenuBar()

		file = Menu()
		file.Append(ImageMenuItem(Stock.New, _accelGroup, Activated: _menuItemNew_Activated))
		file.Append(ImageMenuItem(Stock.Open, _accelGroup, Activated: _menuItemOpen_Activated))
		file.Append(ImageMenuItem(Stock.Save, _accelGroup, Activated: _menuItemSave_Activated))
		file.Append(SeparatorMenuItem())
		file.Append(ImageMenuItem(Stock.Quit, _accelGroup, Activated: _menuItemExit_Activated))

		edit = Menu()
		edit.Append(ImageMenuItem(Stock.Undo, _accelGroup, Activated: _menuItemUndo_Activated))
		edit.Append(ImageMenuItem(Stock.Redo, _accelGroup, Activated: _menuItemRedo_Activated))
		edit.Append(SeparatorMenuItem())
		edit.Append(ImageMenuItem(Stock.Cut, _accelGroup))
		edit.Append(ImageMenuItem(Stock.Copy, _accelGroup))
		edit.Append(ImageMenuItem(Stock.Paste, _accelGroup))
		edit.Append(ImageMenuItem(Stock.Delete, _accelGroup))
		edit.Append(SeparatorMenuItem())
		edit.Append(ImageMenuItem(Stock.Preferences, _accelGroup))

		tools = Menu()
		tools.Append(ImageMenuItem(Stock.Execute, _accelGroup, Activated: _menuItemExecute_Activated))

		mb.Append(MenuItem("_File", Submenu: file))
		mb.Append(MenuItem("_Edit", Submenu: edit))
		mb.Append(MenuItem("_Tools", Submenu: tools))
		return mb

	CurrentEditor as BooEditor:
		get:
			// can't do the simpler:
			// editor as BooEditor = _notebookEditors.CurrentPageWidget
			// because of gtk# bug #61703
			return _editors[_notebookEditors.CurrentPage]

	private def _menuItemExecute_Activated(sender, args as EventArgs):

		compiler = Boo.Lang.Compiler.BooCompiler()
		compiler.Parameters.Input.Add(
				Boo.Lang.Compiler.IO.StringInput("<script>", CurrentEditor.Text))
		compiler.Parameters.Pipeline = Boo.Lang.Compiler.Pipelines.Run()

		result = compiler.Run()
		print(result.Errors.ToString()) if len(result.Errors)

	private def _menuItemNew_Activated(sender, args as EventArgs):
		self.NewDocument()

	private def _menuItemOpen_Activated(sender, args as EventArgs):
		fs = FileSelection("Select a file", SelectMultiple: true)
		if cast(int, ResponseType.Ok) == fs.Run():
			for fname in fs.Selections:
				self.OpenDocument(fname)

	private def _menuItemSave_Activated(sender, args as EventArgs):
		pass

	private def _menuItemUndo_Activated(sender, args as EventArgs):
		CurrentEditor.Undo()

	private def _menuItemRedo_Activated(sender, args as EventArgs):
		CurrentEditor.Redo()

	private def _menuItemExit_Activated(sender, args as EventArgs):
		Application.Quit()

	def OnDelete(sender, args as DeleteEventArgs):
		Application.Quit()
		args.RetVal = true

Application.Init()

MainWindow().ShowAll()

Application.Run()


Support slicing expressions

Support slicing expressions

namespace BooJs.Lang.Extensions

import System

import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast
import Boo.Lang.Parser(BooParser)

[AttributeUsage(AttributeTargets.All ^ AttributeTargets.Assembly)]
class TransformAttribute(AbstractAstAttribute):
"""
    Allows to transform the AST of the annotated member when referencing it.

    [Transform( parseInt($2 / $1) )]
    def int_divide(a as int, b as int):
        pass

    int_divide(10, Math.floor(val))
    ---
    parseInt(Math.floor(val) / 10)
"""
    class TransformAsmAttribute(System.Attribute):
        public Type as string
        public Ast as string

        def constructor(type as string, ast as string):
            Type = type
            Ast = ast

    internal class PlaceholderResolver(DepthFirstTransformer):
    """ Looks for splice expressions, replacing them with the given arguments
        based on the number used. 0 references the target while 1-N reference
        the arguments.
    """
        _target as Expression
        _args as ExpressionCollection

        def constructor(target as Expression, args as ExpressionCollection):
            _target = target
            _args = args

        def OnSpliceExpression(node as SpliceExpression):
            number = node.Expression as IntegerLiteralExpression
            return if not number
            idx = number.Value
            if idx == 0:
                if _target isa MemberReferenceExpression:
                    ReplaceCurrentNode((_target as MemberReferenceExpression).Target)
                else:
                    ReplaceCurrentNode _target
            elif idx > 0 and idx <= len(_args):
                ReplaceCurrentNode _args[idx - 1]
            else:
                raise 'Invalid placeholder number ' + idx

    expr as Expression

    static supported = {
        NodeType.StringLiteralExpression: typeof(StringLiteralExpression),
        NodeType.IntegerLiteralExpression: typeof(IntegerLiteralExpression),
        NodeType.DoubleLiteralExpression: typeof(DoubleLiteralExpression),
        NodeType.UnaryExpression: typeof(UnaryExpression),
        NodeType.BinaryExpression: typeof(BinaryExpression),
        NodeType.ReferenceExpression: typeof(ReferenceExpression),
        NodeType.MemberReferenceExpression: typeof(MemberReferenceExpression),
        NodeType.MethodInvocationExpression: typeof(MethodInvocationExpression),
        NodeType.SlicingExpression: typeof(SlicingExpression),
    }

    def constructor(expr as Expression):
        if expr is null:
            raise ArgumentNullException('expr')
        self.expr = expr

    override def Apply(node as Node):
        anode = node as INodeWithAttributes
        if not anode:
            raise 'Unsupported attribute target: ' + node.NodeType

        if expr.NodeType not in supported:
            raise 'Transforms to type {0} are not supported' % (expr.NodeType, )

        # Attach an attribute with the serialization
        attr = Ast.Attribute()
        attr.Name = self.GetType().FullName + '.TransformAsmAttribute'
        attr.Arguments.Add(StringLiteralExpression(expr.NodeType.ToString()))
        attr.Arguments.Add(StringLiteralExpression(expr.ToCodeString()))

        anode.Attributes.Add(attr)

    static def HasAttribute(node as Node) as bool:
        entity = node.Entity as TypeSystem.IExternalEntity
        if entity and entity.MemberInfo:
            attr = System.Attribute.GetCustomAttribute(entity.MemberInfo, typeof(TransformAsmAttribute), false)
        return attr is not null

    static def Deserialize(node as Node) as Node:
        entity = node.Entity as TypeSystem.IExternalEntity
        raise 'Node does not contain the transform attribute metadata' if not entity

        attr = System.Attribute.GetCustomAttribute(entity.MemberInfo, typeof(TransformAsmAttribute), false)
        return Deserialize(attr)

    static def Deserialize(attr as TransformAsmAttribute) as Node:
        type as NodeType = Enum.Parse(NodeType, attr.Type)
        if not type or type not in supported:
            raise 'Unsupported Transform serialized node type: {0}' % (attr.Type,)

        # TODO: Cache deserialization results

        # Use Boo's Parser to obtain an AST back from the attribute
        cu = BooParser.ParseString('code', attr.Ast)
        st as ExpressionStatement = cu.Modules[0].Globals.FirstStatement
        return st.Expression

    static def Resolve(ast as Node, target as Expression, args as ExpressionCollection) as Node:
        # Parse the expression replacing placeholders
        resolver = PlaceholderResolver(target, args)
        ast.LexicalInfo = target.LexicalInfo
        resolver.Visit(ast)
        return ast

    static def Resolve(target as Expression, args as ExpressionCollection) as Node:
        ast = Deserialize(target)
        return Resolve(ast, target, args)

namespace BooJs.Lang.Extensions

import System

import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast
import Boo.Lang.Parser(BooParser)

[AttributeUsage(AttributeTargets.All ^ AttributeTargets.Assembly)]
class TransformAttribute(AbstractAstAttribute):
"""
    Allows to transform the AST of the annotated member when referencing it.

    [Transform( parseInt($2 / $1) )]
    def int_divide(a as int, b as int):
        pass

    int_divide(10, Math.floor(val))
    ---
    parseInt(Math.floor(val) / 10)
"""
    class TransformAsmAttribute(System.Attribute):
        public Type as string
        public Ast as string

        def constructor(type as string, ast as string):
            Type = type
            Ast = ast

    internal class PlaceholderResolver(DepthFirstTransformer):
    """ Looks for splice expressions, replacing them with the given arguments
        based on the number used. 0 references the target while 1-N reference
        the arguments.
    """
        _target as Expression
        _args as ExpressionCollection

        def constructor(target as Expression, args as ExpressionCollection):
            _target = target
            _args = args

        def OnSpliceExpression(node as SpliceExpression):
            number = node.Expression as IntegerLiteralExpression
            return if not number
            idx = number.Value
            if idx == 0:
                if _target isa MemberReferenceExpression:
                    ReplaceCurrentNode((_target as MemberReferenceExpression).Target)
                else:
                    ReplaceCurrentNode _target
            elif idx > 0 and idx <= len(_args):
                ReplaceCurrentNode _args[idx - 1]
            else:
                raise 'Invalid placeholder number ' + idx

    expr as Expression

    static supported = {
        NodeType.StringLiteralExpression: typeof(StringLiteralExpression),
        NodeType.IntegerLiteralExpression: typeof(IntegerLiteralExpression),
        NodeType.DoubleLiteralExpression: typeof(DoubleLiteralExpression),
        NodeType.UnaryExpression: typeof(UnaryExpression),
        NodeType.BinaryExpression: typeof(BinaryExpression),
        NodeType.ReferenceExpression: typeof(ReferenceExpression),
        NodeType.MemberReferenceExpression: typeof(MemberReferenceExpression),
        NodeType.MethodInvocationExpression: typeof(MethodInvocationExpression),
    }

    def constructor(expr as Expression):
        if expr is null:
            raise ArgumentNullException('expr')
        self.expr = expr

    override def Apply(node as Node):
        anode = node as INodeWithAttributes
        if not anode:
            raise 'Unsupported attribute target: ' + node.NodeType

        if expr.NodeType not in supported:
            raise 'Transforms to type {0} are not supported' % (expr.NodeType, )

        # Attach an attribute with the serialization
        attr = Ast.Attribute()
        attr.Name = self.GetType().FullName + '.TransformAsmAttribute'
        attr.Arguments.Add(StringLiteralExpression(expr.NodeType.ToString()))
        attr.Arguments.Add(StringLiteralExpression(expr.ToCodeString()))

        anode.Attributes.Add(attr)

    static def HasAttribute(node as Node) as bool:
        entity = node.Entity as TypeSystem.IExternalEntity
        if entity and entity.MemberInfo:
            attr = System.Attribute.GetCustomAttribute(entity.MemberInfo, typeof(TransformAsmAttribute), false)
        return attr is not null

    static def Deserialize(node as Node) as Node:
        entity = node.Entity as TypeSystem.IExternalEntity
        raise 'Node does not contain the transform attribute metadata' if not entity

        attr = System.Attribute.GetCustomAttribute(entity.MemberInfo, typeof(TransformAsmAttribute), false)
        return Deserialize(attr)

    static def Deserialize(attr as TransformAsmAttribute) as Node:
        type as NodeType = Enum.Parse(NodeType, attr.Type)
        if not type or type not in supported:
            raise 'Unsupported Transform serialized node type: {0}' % (attr.Type,)

        # TODO: Cache deserialization results

        # Use Boo's Parser to obtain an AST back from the attribute
        cu = BooParser.ParseString('code', attr.Ast)
        st as ExpressionStatement = cu.Modules[0].Globals.FirstStatement
        return st.Expression

    static def Resolve(ast as Node, target as Expression, args as ExpressionCollection) as Node:
        # Parse the expression replacing placeholders
        resolver = PlaceholderResolver(target, args)
        ast.LexicalInfo = target.LexicalInfo
        resolver.Visit(ast)
        return ast

    static def Resolve(target as Expression, args as ExpressionCollection) as Node:
        ast = Deserialize(target)
        return Resolve(ast, target, args)


booish loop example

booish loop example

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

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 ProcessAssignment(node as BinaryExpression):
		# prevent 'Expression can't be assigned to' error
		super(node) unless InterpreterEntity.IsInterpreterEntity(node.Left)

	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
	_namespace as InterpreterNamespace
	_interpreter as InteractiveInterpreter

	def constructor(interpreter):
		_interpreter = interpreter

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

	override def OnModule(node as Module):

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

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

		super(node)

	override def OnReferenceExpression(node as ReferenceExpression):
		super(node)
		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()

	_values = {}

	def constructor():
		pipeline = Pipelines.CompileToMemory()
		index = pipeline.Find(Steps.ProcessMethodBodiesWithDuckTyping)
		pipeline[index] = ProcessVariableDeclarations(self)
		pipeline.InsertBefore(Steps.EmitAssembly, ProcessInterpreterReferences(self))
		//pipeline.Add(Steps.PrintBoo())

		_compiler.Parameters.Pipeline = pipeline

	def Eval(code as string):
		_compiler.Parameters.Input.Clear()
		_compiler.Parameters.Input.Add(StringInput("src", code))

		result = _compiler.Run()
		assert 0 == len(result.Errors), "\n" + result.Errors.ToString(true)

		entry = result.GeneratedAssemblyEntryPoint
		entry.DeclaringType.GetField("ParentInterpreter").SetValue(null, self)
		entry.Invoke(null, (null,))

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

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

interpreter = InteractiveInterpreter()
interpreter.SetValue("name", "boo")
interpreter.SetValue("age", 3)

interpreter.Eval("""
print(name)
print(age)
age += 1
""")

assert 4 == interpreter.GetValue("age")

interpreter.Eval("age = 42")
assert 42 == interpreter.GetValue("age")

interpreter.Eval("""
value = 3
print(value*2)
""")

assert 3 == interpreter.GetValue("value")

interpreter.Eval("x2 = { v as int | return v*2 }")

x2 as callable = interpreter.GetValue("x2")
assert 4 == x2(2)

interpreter.Eval("e = i*2 for i in range(value)")
assert array(interpreter.GetValue("e")) == (0, 2, 4)

# let's loop
interpreter.Eval("""
for i in range(3):
	print(i*2)
""")
print("i: ${interpreter.GetValue('i')}")

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 ProcessAssignment(node as BinaryExpression):
		super(node) unless InterpreterEntity.IsInterpreterEntity(node.Left)

	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
	_namespace as InterpreterNamespace
	_interpreter as InteractiveInterpreter

	def constructor(interpreter):
		_interpreter = interpreter

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

	override def OnModule(node as Module):

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

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

		super(node)

	override def OnReferenceExpression(node as ReferenceExpression):
		super(node)
		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()

	_values = {}

	def constructor():
		pipeline = Pipelines.CompileToMemory()
		index = pipeline.Find(Steps.ProcessMethodBodiesWithDuckTyping)
		pipeline[index] = ProcessVariableDeclarations(self)
		pipeline.InsertBefore(Steps.EmitAssembly, ProcessInterpreterReferences(self))
		//pipeline.Add(Steps.PrintBoo())

		_compiler.Parameters.Pipeline = pipeline

	def Eval(code as string):
		_compiler.Parameters.Input.Clear()
		_compiler.Parameters.Input.Add(StringInput("src", code))

		result = _compiler.Run()
		assert 0 == len(result.Errors), "\n" + result.Errors.ToString(true)

		entry = result.GeneratedAssemblyEntryPoint
		entry.DeclaringType.GetField("ParentInterpreter").SetValue(null, self)
		entry.Invoke(null, (null,))

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

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

interpreter = InteractiveInterpreter()
interpreter.SetValue("name", "boo")
interpreter.SetValue("age", 3)

interpreter.Eval("""
print(name)
print(age)
age += 1
""")

assert 4 == interpreter.GetValue("age")

interpreter.Eval("age = 42")
assert 42 == interpreter.GetValue("age")

interpreter.Eval("""
value = 3
print(value*2)
""")

assert 3 == interpreter.GetValue("value")

interpreter.Eval("x2 = { v as int | return v*2 }")

x2 as callable = interpreter.GetValue("x2")
assert 4 == x2(2)

interpreter.Eval("e = i*2 for i in range(value)")
assert array(interpreter.GetValue("e")) == (0, 2, 4)


repr implementation based on a provided Representer strategy

git-svn-id: c3b8989232654601f1acea7403f771adaaa44a81@1138 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 System
import System.Collections
import System.IO
import Boo.Lang
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.TypeSystem
import Boo.Lang.Compiler.IO

class InteractiveInterpreter:

	_compiler = BooCompiler()

	_parser = BooCompiler()

	_values = {}

	_declarations = {}

	_imports = ImportCollection()

	_referenceProcessor = ProcessInterpreterReferences(self)

	_representers = []

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

	[property(RememberLastValue)]
	_rememberLastValue = false

	def constructor():

		pipeline = Pipelines.CompileToMemory()
		pipeline.RemoveAt(0)

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

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

		_compiler.Parameters.Pipeline = pipeline
		_parser.Parameters.Pipeline = Pipelines.Parse()

		SetValue("dir", dir)
		SetValue("help", help)
		SetValue("print", { value | _print(value) })

	LastValue:
		get:
			return GetValue("@value")

	Pipeline:
		get:
			return _compiler.Parameters.Pipeline

	References:
		get:
			return _compiler.Parameters.References

	def LoopEval(code as string):
		result = self.Eval(code)
		if len(result.Errors):
			self.DisplayErrors(result.Errors)
		else:
			_ = self.LastValue
			if _ is not null:
				_print(repr(_))
				SetValue("_", _)

	def Eval(code as string):

		result = Parse(code)
		return result if len(result.Errors)

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

		# remember the state of the module as of after parsing
		hasStatements = module.Globals.Statements.Count > 0
		hasMembers = module.Members.Count > 0

		module.Imports.Reject() do (item as Import):
			for existing in _imports:
				return true if existing.Namespace == item.Namespace

		if ((not hasStatements) and
			(not hasMembers) and
			0 == len(module.Imports)):
			return result

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

		if hasStatements:
			if IsSimpleReference(code):
				# simple references will be parsed as macros
				# but we want them to be evaluated
				# as references...
				ms as MacroStatement = module.Globals.Statements[0]
				module.Globals.Statements.ReplaceAt(0,
					ExpressionStatement(
							ReferenceExpression(ms.LexicalInfo, ms.Name)))
			_compiler.Parameters.OutputType = CompilerOutputType.ConsoleApplication
		else:
			_compiler.Parameters.OutputType = CompilerOutputType.Library

		SetValue("@value", null)
		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("interactive", code))
		return _parser.Run()

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

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

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

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

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

	def DisplayErrors(errors as CompilerErrorCollection):
		for error in errors:
			pos = error.LexicalInfo.StartColumn
			_print("---" + "-" * pos + "^") if pos > 0
			_print("ERROR: ${error.Message}")

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

	def help(obj):
		type = (obj as Type) or obj.GetType()
		baseTypes = [type.BaseType].Extend(type.GetInterfaces())
		_print("class ${type.Name}(${join(baseTypes, ', ')}):")
		for member in dir(obj):
			_print("    ${member}")

	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)

	private def IsSimpleReference(s as string):
		return /^\s*[_a-zA-Z][_a-zA-Z\d]*\s*$/.IsMatch(s)

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

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

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

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

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

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

		AddRepresenter(IDictionary) do (value as IDictionary, writer as TextWriter):
			writer.Write("{")
			i = 0
			for item as DictionaryEntry in value:
				writer.Write(", ") if i
				repr(item.Key, writer)
				writer.Write(": ")
				repr(item.Value, writer)
				++i
			writer.Write("}")

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

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

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

	callable Representer(value, writer as TextWriter)

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

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

	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):
			entity = InterpreterEntity(name, type)
			_declarations.Add(name, entity)
			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:
				type = _interpreter.Lookup(name)
				if type is not null:
					if type is object:
						entity = Declare(name, _tss.DuckType)
					else:
						entity = Declare(name, _tss.Map(type))

			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
		_isEntryPoint = false

		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 OnConstructor(node as Constructor):
			OnMethod(node)

		override def OnMethod(node as Method):
			_isEntryPoint = node is Steps.ContextAnnotations.GetEntryPoint(Context);
			super(node)

		override def LeaveExpressionStatement(node as ExpressionStatement):
			# force standalone method references types to be completely
			# resolved
			GetConcreteExpressionType(node.Expression)
			super(node)

		override def HasSideEffect(node as Expression):
			return true if _interpreter.RememberLastValue
			return super(node)

		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 or not _isEntryPoint

			external = type as ExternalType
			_interpreter.Declare(name, external.ActualType) if external

			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
		_isEntryPoint = false

		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 OnConstructor(node as Constructor):
			OnMethod(node)

		override def OnMethod(node as Method):
			_isEntryPoint = node is Steps.ContextAnnotations.GetEntryPoint(Context);
			super(node)

		override def OnReferenceExpression(node as ReferenceExpression):

			if (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))

		override def LeaveExpressionStatement(node as ExpressionStatement):

			return unless _interpreter.RememberLastValue and _isEntryPoint

			if node.Expression.ExpressionType is not TypeSystemServices.VoidType:
				node.Expression = CreateInterpreterInvocation(
								InteractiveInterpreter_SetValue,
								"@value",
								node.Expression)
			else:
				eval = CodeBuilder.CreateEvalInvocation(node.LexicalInfo)
				eval.Arguments.Add(node.Expression)
				eval.Arguments.Add(
						CreateInterpreterInvocation(
							InteractiveInterpreter_SetValue,
							"@value",
							CodeBuilder.CreateNullLiteral()))
				node.Expression = eval

		def CreateInterpreterInvocation(method as System.Reflection.MethodInfo,
										name as string,
										value as Expression):
			mie = CreateInterpreterInvocation(method, name)
			mie.Arguments.Add(value)
			return mie

		def CreateInterpreterReference():
			return CodeBuilder.CreateReference(_interpreterField)

		def CreateInterpreterInvocation(method as System.Reflection.MethodInfo,
										name as string):
			return CodeBuilder.CreateMethodInvocation(
						CreateInterpreterReference(),
						TypeSystemServices.Map(method),
						CodeBuilder.CreateStringLiteral(name))

		def CreateGetValue(node as ReferenceExpression):
			return CastIfNeeded(
					node,
					CreateInterpreterInvocation(
						InteractiveInterpreter_GetValue,
						node.Name))

		def CreateSetValue(node as BinaryExpression):
			return CastIfNeeded(
					node,
					CreateInterpreterInvocation(
						InteractiveInterpreter_SetValue,
						cast(ReferenceExpression, node.Left).Name,
						node.Right))

		def CastIfNeeded(srcNode as Expression, expression as Expression):
			if NodeType.ExpressionStatement == srcNode.ParentNode.NodeType:
				return expression

			return CodeBuilder.CreateCast(srcNode.ExpressionType, expression)

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(RememberLastValue: true)

if "--print-modules" in argv:
	interpreter.Pipeline.Add(Steps.PrintBoo())

while line=prompt(">>> "):
	try:
		line = ReadBlock(line) if line[-1:] in ":", "\\"
		interpreter.LoopEval(line)
	except x as System.Reflection.TargetInvocationException:
		print(x.InnerException)
	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 System
import Boo.Lang
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.TypeSystem
import Boo.Lang.Compiler.IO

class InteractiveInterpreter:

	_compiler = BooCompiler()

	_parser = BooCompiler()

	_values = {}

	_declarations = {}

	_imports = ImportCollection()

	_referenceProcessor = ProcessInterpreterReferences(self)

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

	[property(RememberLastValue)]
	_rememberLastValue = false

	def constructor():

		pipeline = Pipelines.CompileToMemory()
		pipeline.RemoveAt(0)

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

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

		_compiler.Parameters.Pipeline = pipeline
		_parser.Parameters.Pipeline = Pipelines.Parse()

		SetValue("dir", dir)
		SetValue("help", help)
		SetValue("print", { value | _print(value) })

	LastValue:
		get:
			return GetValue("@value")

	Pipeline:
		get:
			return _compiler.Parameters.Pipeline

	References:
		get:
			return _compiler.Parameters.References

	def LoopEval(code as string):
		result = self.Eval(code)
		if len(result.Errors):
			self.DisplayErrors(result.Errors)
		else:
			_ = self.LastValue
			if _ is not null:
				_print(repr(_))
				SetValue("_", _)

	def Eval(code as string):

		result = Parse(code)
		return result if len(result.Errors)

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

		# remember the state of the module as of after parsing
		hasStatements = module.Globals.Statements.Count > 0
		hasMembers = module.Members.Count > 0

		module.Imports.Reject() do (item as Import):
			for existing in _imports:
				return true if existing.Namespace == item.Namespace

		if ((not hasStatements) and
			(not hasMembers) and
			0 == len(module.Imports)):
			return result

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

		if hasStatements:
			if IsSimpleReference(code):
				# simple references will be parsed as macros
				# but we want them to be evaluated
				# as references...
				ms as MacroStatement = module.Globals.Statements[0]
				module.Globals.Statements.ReplaceAt(0,
					ExpressionStatement(
							ReferenceExpression(ms.LexicalInfo, ms.Name)))
			_compiler.Parameters.OutputType = CompilerOutputType.ConsoleApplication
		else:
			_compiler.Parameters.OutputType = CompilerOutputType.Library

		SetValue("@value", null)
		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("interactive", code))
		return _parser.Run()

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

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

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

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

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

	def DisplayErrors(errors as CompilerErrorCollection):
		for error in errors:
			pos = error.LexicalInfo.StartColumn
			_print("---" + "-" * pos + "^") if pos > 0
			_print("ERROR: ${error.Message}")

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

	def help(obj):
		for member in dir(obj):
			_print(member)

	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)

	private def IsSimpleReference(s as string):
		return /^\s*[_a-zA-Z][_a-zA-Z\d]*\s*$/.IsMatch(s)

	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):
			entity = InterpreterEntity(name, type)
			_declarations.Add(name, entity)
			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:
				type = _interpreter.Lookup(name)
				if type is not null:
					if type is object:
						entity = Declare(name, _tss.DuckType)
					else:
						entity = Declare(name, _tss.Map(type))

			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
		_isEntryPoint = false

		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 OnConstructor(node as Constructor):
			OnMethod(node)

		override def OnMethod(node as Method):
			_isEntryPoint = node is Steps.ContextAnnotations.GetEntryPoint(Context);
			super(node)

		override def LeaveExpressionStatement(node as ExpressionStatement):
			# force standalone method references types to be completely
			# resolved
			GetConcreteExpressionType(node.Expression)
			super(node)

		override def HasSideEffect(node as Expression):
			return true if _interpreter.RememberLastValue
			return super(node)

		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 or not _isEntryPoint

			external = type as ExternalType
			_interpreter.Declare(name, external.ActualType) if external

			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
		_isEntryPoint = false

		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 OnConstructor(node as Constructor):
			OnMethod(node)

		override def OnMethod(node as Method):
			_isEntryPoint = node is Steps.ContextAnnotations.GetEntryPoint(Context);
			super(node)

		override def OnReferenceExpression(node as ReferenceExpression):

			if (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))

		override def LeaveExpressionStatement(node as ExpressionStatement):

			return unless _interpreter.RememberLastValue and _isEntryPoint

			if node.Expression.ExpressionType is not TypeSystemServices.VoidType:
				node.Expression = CreateInterpreterInvocation(
								InteractiveInterpreter_SetValue,
								"@value",
								node.Expression)
			else:
				eval = CodeBuilder.CreateEvalInvocation(node.LexicalInfo)
				eval.Arguments.Add(node.Expression)
				eval.Arguments.Add(
						CreateInterpreterInvocation(
							InteractiveInterpreter_SetValue,
							"@value",
							CodeBuilder.CreateNullLiteral()))
				node.Expression = eval

		def CreateInterpreterInvocation(method as System.Reflection.MethodInfo,
										name as string,
										value as Expression):
			mie = CreateInterpreterInvocation(method, name)
			mie.Arguments.Add(value)
			return mie

		def CreateInterpreterReference():
			return CodeBuilder.CreateReference(_interpreterField)

		def CreateInterpreterInvocation(method as System.Reflection.MethodInfo,
										name as string):
			return CodeBuilder.CreateMethodInvocation(
						CreateInterpreterReference(),
						TypeSystemServices.Map(method),
						CodeBuilder.CreateStringLiteral(name))

		def CreateGetValue(node as ReferenceExpression):
			return CastIfNeeded(
					node,
					CreateInterpreterInvocation(
						InteractiveInterpreter_GetValue,
						node.Name))

		def CreateSetValue(node as BinaryExpression):
			return CastIfNeeded(
					node,
					CreateInterpreterInvocation(
						InteractiveInterpreter_SetValue,
						cast(ReferenceExpression, node.Left).Name,
						node.Right))

		def CastIfNeeded(srcNode as Expression, expression as Expression):
			if NodeType.ExpressionStatement == srcNode.ParentNode.NodeType:
				return expression

			return CodeBuilder.CreateCast(srcNode.ExpressionType, expression)

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

def WriteRepr(writer as System.IO.TextWriter, value):
	code = Type.GetTypeCode(value.GetType())
	if TypeCode.String == code:
		Visitors.BooPrinterVisitor.WriteStringLiteral(value, writer)
	elif TypeCode.Object == code:
		a = value as Array
		if a is not null:
			writer.Write("(")
			for i in range(len(a)):
				writer.Write(", ") if i > 0
				WriteRepr(writer, a.GetValue(i))
			writer.Write(", ") if 1 == len(a)
			writer.Write(")")
		else:
			c = value as Delegate
			if c is not null:
				method = c.Method
				writer.Write(method.DeclaringType.FullName)
				writer.Write(".")
				writer.Write(method.Name)
			else:
				writer.Write(value)
	else:
		writer.Write(value)

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(RememberLastValue: true)

if "--print-modules" in argv:
	interpreter.Pipeline.Add(Steps.PrintBoo())

while line=prompt(">>> "):
	try:
		line = ReadBlock(line) if line[-1:] in ":", "\\"
		interpreter.LoopEval(line)
	except x as System.Reflection.TargetInvocationException:
		print(x.InnerException)
	except x:
		print(x)


Changing behavior of growing involved rules in indirect left recursion.

namespace Boo.OMeta

import Boo.Adt
import System.Collections.Generic
import System.Collections.Specialized

interface OMetaEvaluationContext:
	def Eval(rule as string, input as OMetaInput) as OMetaMatch

class NullEvaluationContext(OMetaEvaluationContext):

	final _grammar as OMetaGrammar

	def constructor([required] grammar as OMetaGrammar):
		_grammar = grammar

	def Eval(rule as string, input as OMetaInput) as OMetaMatch:
		return _grammar.Apply(self, rule, input)

class RuleSet(Set[of string]):
	def constructor():
		super()
	def constructor(other as RuleSet):
		super(other)

data LeftRecursion(@seed as OMetaMatch, rule as string, @head as Head)
data Head(rule as string, involvedSet as RuleSet, @evalSet as RuleSet)

class OMetaEvaluationContextImpl(OMetaEvaluationContext):
"""
Evaluation context with full support
for indirectly/mutually left recursive rules.
"""

	final _memo = HybridDictionary()

	final _lrStack = Stack[of LeftRecursion]()

	final _heads = HybridDictionary()

	final _grammar as OMetaGrammar

	def constructor([required] grammar as OMetaGrammar):
		_grammar = grammar

	def Eval(rule as string, input as OMetaInput) as OMetaMatch:
		memoKey = MemoKey(rule, input)
		m = Recall(memoKey)
		if m is null:
			lr = LeftRecursion(Fail(input), rule, null)
			_lrStack.Push(lr)
			_memo[memoKey] = lr
			ans = Apply(rule, input)
			_lrStack.Pop()
			if lr.head is not null:
				lr.seed = ans
				return LRAnswer(memoKey, lr)
			else:
				_memo[memoKey] = ans
				return ans
		else:
			lr = m as LeftRecursion
			if lr is not null:
				SetupLR(rule, lr)
				return lr.seed
			else:
				return m

	private def Apply(rule as string, input as OMetaInput):
		return _grammar.Apply(self, rule, input)

	def Fail(input as OMetaInput):
		return FailedMatch(input, LeftRecursionFailure())

	def SetupLR(rule as string, lr as LeftRecursion):
		if lr.head is null:
			lr.head = Head(rule, RuleSet(), RuleSet())

		head = lr.head
		for s in _lrStack:
			break if s.head is head
			s.head = head
			head.involvedSet.Add(s.rule)

	def LRAnswer(memoKey as MemoKey, lr as LeftRecursion):
		h = lr.head
		r = memoKey.rule
		if h.rule != r:
			return lr.seed
		_memo[memoKey] = lr.seed
		if lr.seed isa FailedMatch:
			return lr.seed
		return GrowLR(memoKey, lr)

	def Recall(memoKey as MemoKey):
		m = _memo[memoKey]
		input = memoKey.input
		h as Head = _heads[input]
		if h is null:
			return m

		r = memoKey.rule
		if m is null and h.rule != r and not h.involvedSet.Contains(r):
			return Fail(input)
		if h.evalSet.Contains(r):
			h.evalSet.Remove(r)
			m = Apply(r, input)
			if _memo[memoKey] is null or not (m isa FailedMatch) or not(_memo[memoKey] isa SuccessfulMatch):
				_memo[memoKey] = m
		return m

	def GrowLR(memoKey as MemoKey, lr as LeftRecursion):
		rule = memoKey.rule

		input = memoKey.input
		h = lr.head
		lastSuccessfulMatch as OMetaMatch = lr.seed

		_heads[input] = h
		while true:
			h.evalSet = RuleSet(h.involvedSet)
			m = Apply(rule, input)
			if m isa FailedMatch or m.Input.Position <= lastSuccessfulMatch.Input.Position:
				break
			_memo[memoKey] = lastSuccessfulMatch = m

		_heads.Remove(input)
		return lastSuccessfulMatch

namespace Boo.OMeta

import Boo.Adt
import System.Collections.Generic
import System.Collections.Specialized

interface OMetaEvaluationContext:
	def Eval(rule as string, input as OMetaInput) as OMetaMatch

class NullEvaluationContext(OMetaEvaluationContext):

	final _grammar as OMetaGrammar

	def constructor([required] grammar as OMetaGrammar):
		_grammar = grammar

	def Eval(rule as string, input as OMetaInput) as OMetaMatch:
		return _grammar.Apply(self, rule, input)

class RuleSet(Set[of string]):
	def constructor():
		super()
	def constructor(other as RuleSet):
		super(other)

data LeftRecursion(@seed as OMetaMatch, rule as string, @head as Head)
data Head(rule as string, involvedSet as RuleSet, @evalSet as RuleSet)

class OMetaEvaluationContextImpl(OMetaEvaluationContext):
"""
Evaluation context with full support
for indirectly/mutually left recursive rules.
"""

	final _memo = HybridDictionary()

	final _lrStack = Stack[of LeftRecursion]()

	final _heads = HybridDictionary()

	final _grammar as OMetaGrammar

	def constructor([required] grammar as OMetaGrammar):
		_grammar = grammar

	def Eval(rule as string, input as OMetaInput) as OMetaMatch:
		memoKey = MemoKey(rule, input)
		m = Recall(memoKey)
		if m is null:
			lr = LeftRecursion(Fail(input), rule, null)
			_lrStack.Push(lr)
			_memo[memoKey] = lr
			ans = Apply(rule, input)
			_lrStack.Pop()
			if lr.head is not null:
				lr.seed = ans
				return LRAnswer(memoKey, lr)
			else:
				_memo[memoKey] = ans
				return ans
		else:
			lr = m as LeftRecursion
			if lr is not null:
				SetupLR(rule, lr)
				return lr.seed
			else:
				return m

	private def Apply(rule as string, input as OMetaInput):
		return _grammar.Apply(self, rule, input)

	def Fail(input as OMetaInput):
		return FailedMatch(input, LeftRecursionFailure())

	def SetupLR(rule as string, lr as LeftRecursion):
		if lr.head is null:
			lr.head = Head(rule, RuleSet(), RuleSet())

		head = lr.head
		for s in _lrStack:
			break if s.head is head
			s.head = head
			head.involvedSet.Add(s.rule)

	def LRAnswer(memoKey as MemoKey, lr as LeftRecursion):
		h = lr.head
		r = memoKey.rule
		if h.rule != r:
			return lr.seed
		_memo[memoKey] = lr.seed
		if lr.seed isa FailedMatch:
			return lr.seed
		return GrowLR(memoKey, lr)

	def Recall(memoKey as MemoKey):
		m = _memo[memoKey]
		input = memoKey.input
		h as Head = _heads[input]
		if h is null:
			return m

		r = memoKey.rule
		if m is null and h.rule != r and not h.involvedSet.Contains(r):
			return Fail(input)
		if h.evalSet.Contains(r):
			h.evalSet.Remove(r)
			m = Apply(r, input)
			if _memo[memoKey] is null or (not (m isa FailedMatch and _memo[memoKey] isa SuccessfulMatch) ):
				_memo[memoKey] = m
			else:
				return _memo[memoKey]
		return m

	def GrowLR(memoKey as MemoKey, lr as LeftRecursion):
		rule = memoKey.rule

		input = memoKey.input
		h = lr.head
		lastSuccessfulMatch as OMetaMatch = lr.seed

		_heads[input] = h
		while true:
			h.evalSet = RuleSet(h.involvedSet)
			m = Apply(rule, input)
			if m isa FailedMatch or m.Input.Position <= lastSuccessfulMatch.Input.Position:
				break
			_memo[memoKey] = lastSuccessfulMatch = m

		_heads.Remove(input)
		return lastSuccessfulMatch