A return statement without expression just stops the generator without yielding any value

namespace BooJs.Compiler.Steps

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

class ProcessGenerators(AbstractTransformerCompilerStep):
"""
    Specialized step to process generators replacing Boo's one.
"""
    class TransformGenerator(FastDepthFirstVisitor):
        [getter(States)]
        _states = StatementCollection()

        [property(State)]
        _state as int = 0

        Current as Block:
            get: return States[State]

        _statemachinelabel = ReferenceExpression('statemachine')
        _gotostatemachine = GotoStatement(Label: _statemachinelabel)

        def constructor():
            CreateState()

        def CreateState():
            block = Block()
            States.Add(block)
            return len(States)-1

        def OnExpressionStatement(node as ExpressionStatement):
            Current.Add(node)

        def OnForStatement(node as ForStatement):
            Current.Add(node)

        def OnYieldStatement(node as YieldStatement):
            # Create a re-entry state
            nextstate = CreateState()

            # Setup jump to next state and return value
            Current.Add([| __state = $(nextstate) |])
            Current.Add([| return $(node.Expression) |])

            # Continue in the re-entry state
            State = nextstate

        def OnReturnStatement(node as ReturnStatement):
            # If it has an expression just handle as a yield
            if node.Expression:
                ynode = YieldStatement(node.LexicalInfo, Expression: node.Expression)
                OnYieldStatement(ynode)
            # Otherwise just create a new state where the generator terminates
            else:
                nextstate = CreateState()
                Current.Add([| __state = $(nextstate) |])
                State = nextstate

            # At this point we want to always stop the generator
            Current.Add([| raise Boo.STOP |])

        def OnWhileStatement(node as WhileStatement):
            # Loop always starts in a new step
            State = loopstate = CreateState()

            # Create new step for exiting the loop but don't use it yet
            exitstate = CreateState()

            # Check if we should skip the loop by negating the condition
            ifs = [|
                if not $(node.Condition):
                    __state = $(exitstate)
                    $(_gotostatemachine)
            |]
            Current.Add(ifs)

            # Process the loop body
            Visit node.Block

            # Go back to the start of the loop to check if we have more iterations left
            Current.Add([| __state = $(loopstate) |])
            Current.Add(_gotostatemachine)

            # Continue in the exit step previously created
            State = exitstate

        def OnTryStatement(node as TryStatement):
            # A try block always initiates an state
            State = trystate = CreateState()

            # Register ensure block
            if node.EnsureBlock:
                Current.Add([| __final.push( $(BlockExpression(Body: node.EnsureBlock)) ) |])

            Visit node.ProtectedBlock

            # Execute ensure block after we reach the end of the protected block
            if node.EnsureBlock:
                Current.Add( [| __final.pop()() |] )

            # Create a new state for the exception handler
            exceptstate = CreateState()

            # Register the states covered by this protected block
            tryblock = States[trystate] as Block
            catchst = [| $exceptstate |]
            for state in range(trystate, exceptstate):
                catchst = [| __catch[$state] = $catchst |]
            tryblock.Insert(0, catchst)

            # Create a new state for exiting the try/except block
            afterstate = CreateState()

            # Skip the exception handler state
            Current.Add( [| __state = $afterstate |] )
            Current.Add(_gotostatemachine)

            # Produce the exception handler
            State = exceptstate
            Visit node.ExceptionHandlers

            # Continue in the after state
            State = afterstate

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

    def HasTryStatement(node as Block) as bool:
        for st in node.Statements:
            match st:
                case TryStatement():
                    return true
                case wst=WhileStatement():
                    return HasTryStatement(wst.Block)
                case ist=IfStatement():
                    return HasTryStatement(ist.TrueBlock) or HasTryStatement(ist.FalseBlock)
                case bst=Block():
                    return HasTryStatement(bst)
                otherwise:
                    continue

        return false

    override def EnterMethod(node as Method):
        # Types should be already resolved so we can just check if it was flagged as a generator
        entity = node.Entity as InternalMethod
        return entity and entity.IsGenerator

    override def LeaveMethod(node as Method):

        has_try = HasTryStatement(node.Body)

        # Transform the method body into a list of states
        transformer = TransformGenerator()
        transformer.OnBlock(node.Body)

        # If the method is a closure we look for the original method node to declare locals in it
        method = node
        if node.IsSynthetic:
            for member in (node.ParentNode as TypeDefinition).Members:
                if member.LexicalInfo.Equals(node.LexicalInfo):
                    method = member
                    break

        # Remove the original method contents
        node.Body.Clear()
        # Create a new local to keep the current step of the generator
        CodeBuilder.DeclareLocal(method, '__state', TypeSystemServices.IntType)
        if has_try:
            CodeBuilder.DeclareLocal(method, '__catch', TypeSystemServices.HashType)
            CodeBuilder.DeclareLocal(method, '__final', TypeSystemServices.ArrayType)
            node.Body.Add( CodeBuilder.CreateAssignment(ReferenceExpression('__catch'), HashLiteralExpression()) )
            node.Body.Add( CodeBuilder.CreateAssignment(ReferenceExpression('__final'), ListLiteralExpression()) )

        # Add value check to first state
        valuecheck = [|
            if __value is not Boo.UNDEF or __error is not Boo.UNDEF:
                raise TypeError('Generator not started yet, unable to process sent value/error')
        |]
        (transformer.States[0] as Block).Insert(0, valuecheck)

        # Wrap the steps into a switch/case construct
        switch = MacroStatement(Name: 'switch')
        switch.Arguments.Add(ReferenceExpression('__state'))
        for idx as int, step as Block in enumerate(transformer.States):
            case = MacroStatement()
            case.Name = 'case'
            case.Arguments.Add(IntegerLiteralExpression(idx))
            case.Body = step
            switch.Body.Add(case)

        statemachine as Statement
        statemachine = [|
            if __error:
                raise __error

            $switch

            # Set the state to an imposible value
            __state = -1
            raise Boo.STOP
        |]

        # Wrap the state machine to support exception handling
        if has_try:
            statemachine = [|
                try:
                    $statemachine
                except:
                    if __state in __catch:
                        __error = null  # Make sure any reported error is removed
                        __state = __catch[__state]
                        goto statemachine

                    # Position the generator to the terminating state
                    __state = -1

                    # Ensure all finally blocks are executed
                    while __final.length:
                        __final.pop()()

                    # Re-raise the exception
                    raise __e
            |]

        # Make the method return a generator
        generator = [|
            return Boo.generator do(__value, __error):
                :statemachine
                $statemachine
        |]
        node.Body.Add(generator)

namespace BooJs.Compiler.Steps

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

class ProcessGenerators(AbstractTransformerCompilerStep):
"""
    Specialized step to process generators replacing Boo's one.
"""
    class TransformGenerator(FastDepthFirstVisitor):
        [getter(States)]
        _states = StatementCollection()

        [property(State)]
        _state as int = 0

        Current as Block:
            get: return States[State]

        _statemachinelabel = ReferenceExpression('statemachine')
        _gotostatemachine = GotoStatement(Label: _statemachinelabel)

        def constructor():
            CreateState()

        def CreateState():
            block = Block()
            States.Add(block)
            return len(States)-1

        def OnExpressionStatement(node as ExpressionStatement):
            Current.Add(node)

        def OnForStatement(node as ForStatement):
            Current.Add(node)

        def OnYieldStatement(node as YieldStatement):
            # Create a re-entry state
            nextstate = CreateState()

            # Setup jump to next state and return value
            Current.Add([| __state = $(nextstate) |])
            Current.Add([| return $(node.Expression) |])

            # Continue in the re-entry state
            State = nextstate

        def OnReturnStatement(node as ReturnStatement):
            # Handle as a yield
            ynode = YieldStatement(node.LexicalInfo, Expression: node.Expression)
            OnYieldStatement(ynode)
            # Next state will always stop the generator
            Current.Add([| raise Boo.STOP |])

        def OnWhileStatement(node as WhileStatement):
            # Loop always starts in a new step
            State = loopstate = CreateState()

            # Create new step for exiting the loop but don't use it yet
            exitstate = CreateState()

            # Check if we should skip the loop by negating the condition
            ifs = [|
                if not $(node.Condition):
                    __state = $(exitstate)
                    $(_gotostatemachine)
            |]
            Current.Add(ifs)

            # Process the loop body
            Visit node.Block

            # Go back to the start of the loop to check if we have more iterations left
            Current.Add([| __state = $(loopstate) |])
            Current.Add(_gotostatemachine)

            # Continue in the exit step previously created
            State = exitstate

        def OnTryStatement(node as TryStatement):
            # A try block always initiates an state
            State = trystate = CreateState()

            # Register ensure block
            if node.EnsureBlock:
                Current.Add([| __final.push( $(BlockExpression(Body: node.EnsureBlock)) ) |])

            Visit node.ProtectedBlock

            # Execute ensure block after we reach the end of the protected block
            if node.EnsureBlock:
                Current.Add( [| __final.pop()() |] )

            # Create a new state for the exception handler
            exceptstate = CreateState()

            # Register the states covered by this protected block
            tryblock = States[trystate] as Block
            catchst = [| $exceptstate |]
            for state in range(trystate, exceptstate):
                catchst = [| __catch[$state] = $catchst |]
            tryblock.Insert(0, catchst)

            # Create a new state for exiting the try/except block
            afterstate = CreateState()

            # Skip the exception handler state
            Current.Add( [| __state = $afterstate |] )
            Current.Add(_gotostatemachine)

            # Produce the exception handler
            State = exceptstate
            Visit node.ExceptionHandlers

            # Continue in the after state
            State = afterstate

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

    def HasTryStatement(node as Block) as bool:
        for st in node.Statements:
            match st:
                case TryStatement():
                    return true
                case wst=WhileStatement():
                    return HasTryStatement(wst.Block)
                case ist=IfStatement():
                    return HasTryStatement(ist.TrueBlock) or HasTryStatement(ist.FalseBlock)
                case bst=Block():
                    return HasTryStatement(bst)
                otherwise:
                    continue

        return false

    override def EnterMethod(node as Method):
        # Types should be already resolved so we can just check if it was flagged as a generator
        entity = node.Entity as InternalMethod
        return entity and entity.IsGenerator

    override def LeaveMethod(node as Method):

        has_try = HasTryStatement(node.Body)

        # Transform the method body into a list of states
        transformer = TransformGenerator()
        transformer.OnBlock(node.Body)

        # If the method is a closure we look for the original method node to declare locals in it
        method = node
        if node.IsSynthetic:
            for member in (node.ParentNode as TypeDefinition).Members:
                if member.LexicalInfo.Equals(node.LexicalInfo):
                    method = member
                    break

        # Remove the original method contents
        node.Body.Clear()
        # Create a new local to keep the current step of the generator
        CodeBuilder.DeclareLocal(method, '__state', TypeSystemServices.IntType)
        if has_try:
            CodeBuilder.DeclareLocal(method, '__catch', TypeSystemServices.HashType)
            CodeBuilder.DeclareLocal(method, '__final', TypeSystemServices.ArrayType)
            node.Body.Add( CodeBuilder.CreateAssignment(ReferenceExpression('__catch'), HashLiteralExpression()) )
            node.Body.Add( CodeBuilder.CreateAssignment(ReferenceExpression('__final'), ListLiteralExpression()) )

        # Add value check to first state
        valuecheck = [|
            if __value is not Boo.UNDEF or __error is not Boo.UNDEF:
                raise TypeError('Generator not started yet, unable to process sent value/error')
        |]
        (transformer.States[0] as Block).Insert(0, valuecheck)

        # Wrap the steps into a switch/case construct
        switch = MacroStatement(Name: 'switch')
        switch.Arguments.Add(ReferenceExpression('__state'))
        for idx as int, step as Block in enumerate(transformer.States):
            case = MacroStatement()
            case.Name = 'case'
            case.Arguments.Add(IntegerLiteralExpression(idx))
            case.Body = step
            switch.Body.Add(case)

        statemachine as Statement
        statemachine = [|
            if __error:
                raise __error

            $switch

            # Set the state to an imposible value
            __state = -1
            raise Boo.STOP
        |]

        # Wrap the state machine to support exception handling
        if has_try:
            statemachine = [|
                try:
                    $statemachine
                except:
                    if __state in __catch:
                        __error = null  # Make sure any reported error is removed
                        __state = __catch[__state]
                        goto statemachine

                    # Position the generator to the terminating state
                    __state = -1

                    # Ensure all finally blocks are executed
                    while __final.length:
                        __final.pop()()

                    # Re-raise the exception
                    raise __e
            |]

        # Make the method return a generator
        generator = [|
            return Boo.generator do(__value, __error):
                :statemachine
                $statemachine
        |]
        node.Body.Add(generator)


add support for more options in boo msbuild task

git-svn-id: fcce7d6ce0ac15bf24f68871b5145d46fc068279@2155 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

	Pipelines:
	"""
	Gets/sets the aditional pipelines to add to the compiler process.
	"""
		get:
			return Bag['Pipelines'] as (string)
		set:
			Bag['Pipelines'] = 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.Low, 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("-lib:", AdditionalLibPaths, ",")

		if NoLogo:
			commandLine.AppendSwitch('-nologo')
		if NoConfig:
			commandLine.AppendSwitch('-noconfig')
		if DelaySign:
			commandLine.AppendSwitch('-delaysign')

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

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

		if Pipelines:
			for pipeline in Pipelines:
				commandLine.AppendSwitchIfNotNull('-p:', pipeline)

		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)

		if not File.Exists(path):
			path = Path.Combine(
				Path.GetDirectoryName(typeof(Booc).Assembly.Location),
				ToolName)

		if not File.Exists(path):
			path = ToolLocationHelper.GetPathToDotNetFrameworkFile(
				ToolName,
				TargetDotNetFrameworkVersion.Version20)

		if not File.Exists(path):
			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)
"""
	Pipelines:
	"""
	Gets/sets the aditional pipelines to add to the compiler process.
	"""
		get:
			return Bag['Pipelines'] as (string)
		set:
			Bag['Pipelines'] = 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.Low, 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.AppendSwitch('-nologo')
		commandLine.AppendSwitchIfNotNull('-t:', TargetType)
		commandLine.AppendSwitchIfNotNull('-o:', OutputAssembly)
		commandLine.AppendSwitchIfNotNull('-c:', Culture)
		commandLine.AppendSwitchIfNotNull('-srcdir:', SourceDirectory)
		commandLine.AppendSwitchIfNotNull('-keyfile:', KeyFile)
		commandLine.AppendSwitchIfNotNull('-keycontainer:', KeyContainer)

		if Pipelines:
			for pipeline in Pipelines:
				commandLine.AppendSwitchIfNotNull('-p:', pipeline)

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

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

		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.
	"""
		toolPath as string = ToolPath
		if toolPath is not null:
			path = Path.Combine(toolPath, ToolName)

		if path is null or not File.Exists(path):
			path = Path.Combine(
				Path.GetDirectoryName(typeof(Booc).Assembly.Location),
				ToolName)

		unless File.Exists(path):
			path = ToolLocationHelper.GetPathToDotNetFrameworkFile(
				ToolName,
				TargetDotNetFrameworkVersion.Version20)

		unless File.Exists(path):
			Log.LogErrorWithCodeFromResources(
				"General.FrameworksFileNotFound",
				ToolName,
				ToolLocationHelper.GetDotNetFrameworkVersionFolderPrefix(
					TargetDotNetFrameworkVersion.Version20))

		return path


unused namespace

unused namespace

namespace Boo.PatternMatching

import Boo.Lang.Compiler.Ast

def caseList(node as MacroStatement) as List:
	list as List = node["caseList"]
	if list is null:
		node["caseList"] = list = []
	return list

def parentMatch(node as MacroStatement):
	match = node.ParentNode.ParentNode as MacroStatement
	assert match.Name == "match"
	return match

macro case:
	match = parentMatch(case)
	caseList(match).Add(case)

macro otherwise:
	match = parentMatch(otherwise)
	assert match["otherwise"] is null
	match["otherwise"] = otherwise

namespace Boo.PatternMatching

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

def caseList(node as MacroStatement) as List:
	list as List = node["caseList"]
	if list is null:
		node["caseList"] = list = []
	return list

def parentMatch(node as MacroStatement):
	match = node.ParentNode.ParentNode as MacroStatement
	assert match.Name == "match"
	return match

macro case:
	match = parentMatch(case)
	caseList(match).Add(case)

macro otherwise:
	match = parentMatch(otherwise)
	assert match["otherwise"] is null
	match["otherwise"] = otherwise


make Empty a method

make Empty a method

namespace Boo.OMeta

import System
import System.Collections

class OMetaInput:

	static def For(enumerable as IEnumerable) as OMetaInput:
		return ForEnumerator(enumerable.GetEnumerator())

	static def ForEnumerator(enumerator as IEnumerator) as OMetaInput:
		return ForEnumerator(enumerator, 0)

	static def ForEnumerator(enumerator as IEnumerator, position as int) as OMetaInput:
		if enumerator.MoveNext():
			return EnumeratorInput(enumerator, position)
		return Empty()

	static def Prepend(argument, input as OMetaInput):
		return OMetaInputCons(argument, input)

	static def Singleton(o):
		return Prepend(o, Empty())

	static def Empty():
		return OMetaInput()

	protected def constructor():
		pass

	virtual IsEmpty as bool:
		get: return true

	virtual Head as object:
		get: raise InvalidOperationException()

	virtual Tail as OMetaInput:
		get: raise InvalidOperationException()

	virtual Position:
		get: return int.MaxValue

	virtual def SetMemo(key as string, value) as OMetaInput:
		return OMetaInputWithMemo(key, value, self)

	virtual def GetMemo(key as string):
		return null

	override def ToString():
		return "OMetaInput()"

internal class DelegatingInput(OMetaInput):

	final _input as OMetaInput

	def constructor(input as OMetaInput):
		_input = input

	override Head:
		get: return _input.Head

	override IsEmpty:
		get: return _input.IsEmpty

	override Tail:
		get: return _input.Tail

	override Position:
		get: return _input.Position

	override def SetMemo(key as string, value):
		return _input.SetMemo(key, value)

	override def GetMemo(key as string):
		return _input.GetMemo(key)

	override def ToString():
		return _input.ToString()

internal class OMetaInputWithMemo(DelegatingInput):

	final _key as string
	final _value
	_tail as OMetaInput

	def constructor(key as string, value, input as OMetaInput):
		super(input)
		_key = key
		_value = value

	override Tail:
		get:
			if _tail is null:
				_tail = OMetaInputMemoTail(self, _input.Tail)
			return _tail

	override def GetMemo(key as string):
		if key is _key: return _value
		return super(key)

internal class OMetaInputMemoTail(DelegatingInput):

	final _parent as OMetaInput
	_tail as OMetaInput

	def constructor(parent as OMetaInput, input as OMetaInput):
		super(input)
		_parent = parent

	override Tail:
		get:
			if _tail is null:
				_tail = OMetaInputMemoTail(self, _input.Tail)
			return _tail

	override def GetMemo(key as string):
		return _parent.GetMemo(key)

internal class OMetaInputCons(OMetaInput):
	[getter(Head)] _argument
	[getter(Tail)] _tail as OMetaInput

	def constructor(argument, tail as OMetaInput):
		_argument = argument
		_tail = tail

	override IsEmpty:
		get: return false

internal class EnumeratorInput(OMetaInput):

	final _position as int
	final _input as IEnumerator
	final _head
	_tail as OMetaInput

	internal def constructor(input as IEnumerator, position as int):
		_input = input
		_head = input.Current
		_position = position

	override Position:
		get: return _position

	override IsEmpty:
		get: return false

	override Head:
		get: return _head

	override Tail:
		get:
			if _tail is null:
				_tail = ForEnumerator(_input, _position + 1)
			return _tail

	override def ToString():
		return "OMetaInput(Head: ${Head}, Position: ${Position})"

namespace Boo.OMeta

import System
import System.Collections

class OMetaInput:

	public static final Empty as OMetaInput = OMetaInput()

	static def For(enumerable as IEnumerable) as OMetaInput:
		return ForEnumerator(enumerable.GetEnumerator())

	static def ForEnumerator(enumerator as IEnumerator) as OMetaInput:
		return ForEnumerator(enumerator, 0)

	static def ForEnumerator(enumerator as IEnumerator, position as int) as OMetaInput:
		if enumerator.MoveNext():
			return EnumeratorInput(enumerator, position)
		return Empty

	static def Prepend(argument, input as OMetaInput):
		return OMetaInputCons(argument, input)

	static def Singleton(o):
		return Prepend(o, Empty)

	protected def constructor():
		pass

	virtual IsEmpty as bool:
		get: return true

	virtual Head as object:
		get: raise InvalidOperationException()

	virtual Tail as OMetaInput:
		get: raise InvalidOperationException()

	virtual Position:
		get: return int.MaxValue

	virtual def SetMemo(key as string, value) as OMetaInput:
		return OMetaInputWithMemo(key, value, self)

	virtual def GetMemo(key as string):
		return null

	override def ToString():
		return "OMetaInput()"

internal class DelegatingInput(OMetaInput):

	final _input as OMetaInput

	def constructor(input as OMetaInput):
		_input = input

	override Head:
		get: return _input.Head

	override IsEmpty:
		get: return _input.IsEmpty

	override Tail:
		get: return _input.Tail

	override Position:
		get: return _input.Position

	override def SetMemo(key as string, value):
		return _input.SetMemo(key, value)

	override def GetMemo(key as string):
		return _input.GetMemo(key)

	override def ToString():
		return _input.ToString()

internal class OMetaInputWithMemo(DelegatingInput):

	final _key as string
	final _value
	_tail as OMetaInput

	def constructor(key as string, value, input as OMetaInput):
		super(input)
		_key = key
		_value = value

	override Tail:
		get:
			if _tail is null:
				_tail = OMetaInputMemoTail(self, _input.Tail)
			return _tail

	override def GetMemo(key as string):
		if key is _key: return _value
		return super(key)

internal class OMetaInputMemoTail(DelegatingInput):

	final _parent as OMetaInput
	_tail as OMetaInput

	def constructor(parent as OMetaInput, input as OMetaInput):
		super(input)
		_parent = parent

	override Tail:
		get:
			if _tail is null:
				_tail = OMetaInputMemoTail(self, _input.Tail)
			return _tail

	override def GetMemo(key as string):
		return _parent.GetMemo(key)

internal class OMetaInputCons(OMetaInput):
	[getter(Head)] _argument
	[getter(Tail)] _tail as OMetaInput

	def constructor(argument, tail as OMetaInput):
		_argument = argument
		_tail = tail

	override IsEmpty:
		get: return false

internal class EnumeratorInput(OMetaInput):

	final _position as int
	final _input as IEnumerator
	final _head
	_tail as OMetaInput

	internal def constructor(input as IEnumerator, position as int):
		_input = input
		_head = input.Current
		_position = position

	override Position:
		get: return _position

	override IsEmpty:
		get: return false

	override Head:
		get: return _head

	override Tail:
		get:
			if _tail is null:
				_tail = ForEnumerator(_input, _position + 1)
			return _tail

	override def ToString():
		return "OMetaInput(Head: ${Head}, Position: ${Position})"


work around nunit list collision with boo one

import NUnit.Framework

import System.IO(Path, Directory, StreamReader)
import System.Reflection(Assembly)
import System.Timers(Timer)

import Boo.Lang.Compiler.IO
import BooJs.Compiler

# The JavaScript interpretter
import Jurassic(ScriptEngine, JavaScriptException, CompatibilityMode)
import Jurassic.Library(ObjectInstance, FunctionInstance, JSFunctionAttribute)

class ConsoleMock(ObjectInstance):
    _output = []

    def constructor(engine as ScriptEngine):
        super(engine)
        PopulateFunctions()

    [JSFunction(Name:'log')]
    def log(*msg as (object)):
        _output.Add(join(msg, ' '))

    def output():
        return _output.Join("\n")

    def reset():
        _output.Clear()

static class Window:
    timers = Boo.Lang.List[of Timer]()

    def hasTimers():
        return len(timers) > 0

    def clearTimers():
        for timer in timers:
            timer.Stop()
        timers.Clear()

    def waitTimers():
        while hasTimers():
            System.Threading.Thread.Sleep(200)

    [JSFunction(Name:'setTimeout')]
    def setTimeout(callback as FunctionInstance, millisec as int) as int:
        timer = Timer(System.Math.Max(millisec, 1))
        timer.AutoReset = false
        timer.Enabled = true
        timer.Elapsed += do:
            try:
                callback.Call(callback)
            ensure:
                timer.Stop()
                timers.Remove(timer)

        timer.Start()
        timers.Add(timer)
        return len(timers)

class FixtureRunner:

    static _comp as BooJsCompiler
    static _engine as ScriptEngine

    static _tests_path as string

    static def fixture_path(fname):
      if not _tests_path:
        # Find the path where the assembly resides
        path = Assembly.GetExecutingAssembly().CodeBase

        # Look for the tests directory in once of its parent paths
        while path = Path.GetFullPath(Path.Combine(path, '..')):
          if Directory.Exists(Path.Combine(path, 'tests')):
            _tests_path = path
            break
          elif path == Path.GetPathRoot(path):
            raise 'Unable to find tests directory'

      return Path.Combine(_tests_path, fname)

    static def run(file as string):
      file = fixture_path(file)

      timer = System.Diagnostics.Stopwatch()

      comp = setupCompiler()
      comp.Parameters.Ducky = false
      comp.Parameters.Strict = false

      # Enable compiler debug messages
      #comp.Parameters.TraceLevel = System.Diagnostics.TraceLevel.Verbose

      #print 'Setup: ', timer.ElapsedMilliseconds

      # Check if we want to ignore this fixture
      fp = StreamReader(file)
      while (line = fp.ReadLine()) != null:
        if line.IndexOf('#IGNORE') == 0:
          Assert.Ignore(line.Substring(len('#IGNORE')+1))
        elif line.IndexOf('#DUCKY') == 0:
          comp.Parameters.Ducky = true

      comp.Parameters.Input.Clear()
      comp.Parameters.Input.Add(FileInput(file))

      # Add the test runner assembly so we can resolve imports for supporting types
      comp.Parameters.References.Add(Assembly.GetExecutingAssembly())

      timer.Restart()

      result = comp.Run()

      #print 'Compilation: ', timer.ElapsedMilliseconds

      if len(result.Warnings):
        for warn as Boo.Lang.Compiler.CompilerWarning in result.Warnings:
            li = warn.LexicalInfo
            name = System.IO.Path.GetFileName(li.FileName)
            print "$(warn.Code) $(warn.Message) [$name:$(li.Line),$(li.Column)]"

      if len(result.Errors):
        for err as Boo.Lang.Compiler.CompilerError in result.Errors:
            li = err.LexicalInfo
            name = System.IO.Path.GetFileName(li.FileName)
            print "$(err.Code): $(err.Message) [$name:$(li.Line),$(li.Column)]"
        print '-----------------------------------------------------'
        print result.CompileUnit.ToCodeString()
        raise result.Errors[0]

      module = result.CompileUnit.Modules[0]
      expected = module.Documentation or ''

      code = comp.Parameters.OutputWriter.ToString()
      runTest(code, expected)

    static def setupCompiler():
        if not _comp:
            pipeline = Pipelines.PrintJs()
            #pipeline = Pipelines.PrintAst()
            #pipeline = Pipelines.PrintBoo()

            timer = System.Diagnostics.Stopwatch()
            pipeline.BeforeStep += def(pipeline, args):
                //print '-------------------------------------------'
                //print 'Step:', args.Step
                //print args.Context.CompileUnit
                timer.Restart()

            pipeline.AfterStep += def(pipeline, args):
                if timer.ElapsedMilliseconds > 100:
                    print 'Slow Step {0}: {1}ms' % (args.Step.GetType().Name, timer.ElapsedMilliseconds)

            _comp = newBooJsCompiler(pipeline)
            _comp.Parameters.Debug = true
            _comp.Parameters.GenerateInMemory = true

        # Reset the output writer
        _comp.Parameters.OutputWriter = System.IO.StringWriter()
        return _comp

    static def setupInterpreter():
        if not self._engine:
            self._engine = ScriptEngine()
            # Try to target older browser with ES3 (JS 1.5)
            self._engine.CompatibilityMode = CompatibilityMode.ECMAScript3
            # Force strict mode
            self._engine.ForceStrictMode = true

            # TODO: Does this help us somehow to get better error reports?
            #engine.EnableDebugging = true

            # Configure the global environment
            self._engine.SetGlobalFunction('setTimeout', Window.setTimeout)

            console = ConsoleMock(self._engine)
            self._engine.SetGlobalValue('console', console)

            # Load runtime
            stream = typeof(FixtureRunner).Assembly.GetManifestResourceStream('Boo.js')
            reader = System.IO.StreamReader(stream)
            self._engine.Execute(reader.ReadToEnd())

            # Patch the runtime to be compatible with Jurassic
            self._engine.Execute('Boo.AssertionError.prototype.toString = function(){ return this.message; };')

            # Load tests support types
            stream = typeof(FixtureRunner).Assembly.GetManifestResourceStream('BooJs.Tests.Support.js')
            reader = System.IO.StreamReader(stream)
            self._engine.Execute(reader.ReadToEnd())

            self._engine.ExecuteFile('/Users/drslump/www/boojs/src/Boo.Async.js')

        return self._engine

    static def runTest(code as string, expected as string):

        # TODO: Check if we can reuse the same engine
        using engine = setupInterpreter():

          console as ConsoleMock = engine.GetGlobalValue('console')
          console.reset()

          try:
            engine.Execute(code)
            Window.waitTimers()
            Assert.AreEqual(expected, console.output())
          except ex:
            Window.clearTimers()

            jsex = ex as Jurassic.JavaScriptException
            if jsex:
                jsobj = jsex.ErrorObject as Jurassic.Library.ObjectInstance
                if jsobj and jsobj.HasProperty('boo_filename'):
                  print join(('Exception: "', ex.Message, '" at ',
                          jsobj.GetPropertyValue('boo_filename'), ':',
                          jsobj.GetPropertyValue('boo_line')
                      ), '')

            print '----------------------------------------------[code]-'
            print code.Trim()
            print '--------------------------------------------[output]-'
            print console.output()
            print '-----------------------------------------------------'

            raise

import NUnit.Framework

import System.IO(Path, Directory, StreamReader)
import System.Reflection(Assembly)
import System.Timers(Timer)

import Boo.Lang.Compiler.IO
import BooJs.Compiler

# The JavaScript interpretter
import Jurassic(ScriptEngine, JavaScriptException, CompatibilityMode)
import Jurassic.Library(ObjectInstance, FunctionInstance, JSFunctionAttribute)

class ConsoleMock(ObjectInstance):
    _output = []

    def constructor(engine as ScriptEngine):
        super(engine)
        PopulateFunctions()

    [JSFunction(Name:'log')]
    def log(*msg as (object)):
        _output.Add(join(msg, ' '))

    def output():
        return _output.Join("\n")

    def reset():
        _output.Clear()

static class Window:
    timers = List[of Timer]()

    def hasTimers():
        return len(timers) > 0

    def clearTimers():
        for timer in timers:
            timer.Stop()
        timers.Clear()

    def waitTimers():
        while hasTimers():
            System.Threading.Thread.Sleep(200)

    [JSFunction(Name:'setTimeout')]
    def setTimeout(callback as FunctionInstance, millisec as int) as int:
        timer = Timer(System.Math.Max(millisec, 1))
        timer.AutoReset = false
        timer.Enabled = true
        timer.Elapsed += do:
            try:
                callback.Call(callback)
            ensure:
                timer.Stop()
                timers.Remove(timer)

        timer.Start()
        timers.Add(timer)
        return len(timers)

class FixtureRunner:

    static _comp as BooJsCompiler
    static _engine as ScriptEngine

    static _tests_path as string

    static def fixture_path(fname):
      if not _tests_path:
        # Find the path where the assembly resides
        path = Assembly.GetExecutingAssembly().CodeBase

        # Look for the tests directory in once of its parent paths
        while path = Path.GetFullPath(Path.Combine(path, '..')):
          if Directory.Exists(Path.Combine(path, 'tests')):
            _tests_path = path
            break
          elif path == Path.GetPathRoot(path):
            raise 'Unable to find tests directory'

      return Path.Combine(_tests_path, fname)

    static def run(file as string):
      file = fixture_path(file)

      timer = System.Diagnostics.Stopwatch()

      comp = setupCompiler()
      comp.Parameters.Ducky = false
      comp.Parameters.Strict = false

      # Enable compiler debug messages
      #comp.Parameters.TraceLevel = System.Diagnostics.TraceLevel.Verbose

      #print 'Setup: ', timer.ElapsedMilliseconds

      # Check if we want to ignore this fixture
      fp = StreamReader(file)
      while (line = fp.ReadLine()) != null:
        if line.IndexOf('#IGNORE') == 0:
          Assert.Ignore(line.Substring(len('#IGNORE')+1))
        elif line.IndexOf('#DUCKY') == 0:
          comp.Parameters.Ducky = true

      comp.Parameters.Input.Clear()
      comp.Parameters.Input.Add(FileInput(file))

      # Add the test runner assembly so we can resolve imports for supporting types
      comp.Parameters.References.Add(Assembly.GetExecutingAssembly())

      timer.Restart()

      result = comp.Run()

      #print 'Compilation: ', timer.ElapsedMilliseconds

      if len(result.Warnings):
        for warn as Boo.Lang.Compiler.CompilerWarning in result.Warnings:
            li = warn.LexicalInfo
            name = System.IO.Path.GetFileName(li.FileName)
            print "$(warn.Code) $(warn.Message) [$name:$(li.Line),$(li.Column)]"

      if len(result.Errors):
        for err as Boo.Lang.Compiler.CompilerError in result.Errors:
            li = err.LexicalInfo
            name = System.IO.Path.GetFileName(li.FileName)
            print "$(err.Code): $(err.Message) [$name:$(li.Line),$(li.Column)]"
        print '-----------------------------------------------------'
        print result.CompileUnit.ToCodeString()
        raise result.Errors[0]

      module = result.CompileUnit.Modules[0]
      expected = module.Documentation or ''

      code = comp.Parameters.OutputWriter.ToString()
      runTest(code, expected)

    static def setupCompiler():
        if not _comp:
            pipeline = Pipelines.PrintJs()
            #pipeline = Pipelines.PrintAst()
            #pipeline = Pipelines.PrintBoo()

            timer = System.Diagnostics.Stopwatch()
            pipeline.BeforeStep += def(pipeline, args):
                //print '-------------------------------------------'
                //print 'Step:', args.Step
                //print args.Context.CompileUnit
                timer.Restart()

            pipeline.AfterStep += def(pipeline, args):
                if timer.ElapsedMilliseconds > 100:
                    print 'Slow Step {0}: {1}ms' % (args.Step.GetType().Name, timer.ElapsedMilliseconds)

            _comp = newBooJsCompiler(pipeline)
            _comp.Parameters.Debug = true
            _comp.Parameters.GenerateInMemory = true

        # Reset the output writer
        _comp.Parameters.OutputWriter = System.IO.StringWriter()
        return _comp

    static def setupInterpreter():
        if not self._engine:
            self._engine = ScriptEngine()
            # Try to target older browser with ES3 (JS 1.5)
            self._engine.CompatibilityMode = CompatibilityMode.ECMAScript3
            # Force strict mode
            self._engine.ForceStrictMode = true

            # TODO: Does this help us somehow to get better error reports?
            #engine.EnableDebugging = true

            # Configure the global environment
            self._engine.SetGlobalFunction('setTimeout', Window.setTimeout)

            console = ConsoleMock(self._engine)
            self._engine.SetGlobalValue('console', console)

            # Load runtime
            stream = typeof(FixtureRunner).Assembly.GetManifestResourceStream('Boo.js')
            reader = System.IO.StreamReader(stream)
            self._engine.Execute(reader.ReadToEnd())

            # Patch the runtime to be compatible with Jurassic
            self._engine.Execute('Boo.AssertionError.prototype.toString = function(){ return this.message; };')

            # Load tests support types
            stream = typeof(FixtureRunner).Assembly.GetManifestResourceStream('BooJs.Tests.Support.js')
            reader = System.IO.StreamReader(stream)
            self._engine.Execute(reader.ReadToEnd())

            self._engine.ExecuteFile('/Users/drslump/www/boojs/src/Boo.Async.js')

        return self._engine

    static def runTest(code as string, expected as string):

        # TODO: Check if we can reuse the same engine
        using engine = setupInterpreter():

          console as ConsoleMock = engine.GetGlobalValue('console')
          console.reset()

          try:
            engine.Execute(code)
            Window.waitTimers()
            Assert.AreEqual(expected, console.output())
          except ex:
            Window.clearTimers()

            jsex = ex as Jurassic.JavaScriptException
            if jsex:
                jsobj = jsex.ErrorObject as Jurassic.Library.ObjectInstance
                if jsobj and jsobj.HasProperty('boo_filename'):
                  print join(('Exception: "', ex.Message, '" at ',
                          jsobj.GetPropertyValue('boo_filename'), ':',
                          jsobj.GetPropertyValue('boo_line')
                      ), '')

            print '----------------------------------------------[code]-'
            print code.Trim()
            print '--------------------------------------------[output]-'
            print console.output()
            print '-----------------------------------------------------'

            raise