BOO-694 patch by Snaury

BOO-694 patch by Snaury

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

import System
import System.IO
import System.Reflection
import System.Security.Permissions
import System.Threading
import Boo.Lang.Parser
import Boo.Lang.Compiler
import Boo.Lang.Compiler.IO
import Boo.Lang.Compiler.Pipelines
import Boo.Lang.Compiler.Steps

class AssemblyResolver:

	_cache = {}

	def AddAssembly([required] asm as Assembly):
		_cache[GetSimpleName(asm.FullName)] = asm

	def LoadAssembly([required] name as string):
		asm = ProbeFile(name)
		if asm is not null:
			_cache[asm.GetName().Name] = asm
		return asm

	def AssemblyResolve(sender, args as ResolveEventArgs) as Assembly:
		simpleName = GetSimpleName(args.Name)

		asm as Assembly = _cache[simpleName]
		if asm is null:
			basePath = Path.GetFullPath(simpleName)
			asm = ProbeFile(basePath + ".dll")
			if asm is null:
				asm = ProbeFile(basePath + ".exe")
			_cache[simpleName] = asm

		return asm

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

	private def ProbeFile(fname as string):
		return Assembly.LoadFrom(fname) if File.Exists(fname)

def consume(reader as TextReader):
	return join(line for line in reader, "\n")

[STAThread]
def Main(argv as (string)) as int:

	DefaultErrorCode = 255
	DefaultSuccessCode = 0

	if len(argv) < 1:
		print("booi <script.boo>")
		return DefaultErrorCode

	resolver = AssemblyResolver()
	AppDomain.CurrentDomain.AssemblyResolve += resolver.AssemblyResolve

	compiler = BooCompiler()
	compiler.Parameters.Pipeline = CompileToMemory()

	consumedArgs = 1
	asm as Assembly = null
	for arg in argv:
		if "-" == arg:
			compiler.Parameters.Input.Add(StringInput("<stdin>", consume(Console.In)))
			break
		elif "-ducky" == arg:
			compiler.Parameters.Ducky = true
		elif "-wsa" == arg:
			compiler.Parameters.Pipeline[0] = Boo.Lang.Parser.WSABooParsingStep()
		elif "-w" == arg:
			printWarnings = true
		elif arg.StartsWith("-r:"):
			//compiler.Parameters.References.Add(resolver.LoadAssembly(arg[3:]))
			try:
				asm = compiler.Parameters.LoadAssembly(arg[3:])
				if asm is null:
					print Boo.Lang.ResourceManager.Format("BooC.UnableToLoadAssembly", arg[3:])
				else:
					compiler.Parameters.References.Add(asm)
					resolver.AddAssembly(asm)
			except e:
				print e.Message
		else:
			compiler.Parameters.Input.Add(FileInput(arg))
			break
		++consumedArgs

	result = compiler.Run()
	if printWarnings and len(result.Warnings):
		print(result.Warnings.ToString())
	if len(result.Errors):
		print(result.Errors.ToString(true))
		return DefaultErrorCode
	else:
		try:
			resolver.AddAssembly(result.GeneratedAssembly)
			main = result.GeneratedAssembly.EntryPoint
			Environment.ExitCode = DefaultSuccessCode
			if len(main.GetParameters()) > 0:
				returnValue = main.Invoke(null, (argv[consumedArgs:],))
			else:
				returnValue = main.Invoke(null, null)
			Environment.ExitCode = returnValue if returnValue is not null
		except x as TargetInvocationException:
			print(x.InnerException)
			return DefaultErrorCode
	return Environment.ExitCode

[assembly: SecurityPermission(
						SecurityAction.RequestMinimum,
						ControlAppDomain: true)]

#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

import System
import System.IO
import System.Reflection
import System.Security.Permissions
import System.Threading
import Boo.Lang.Parser
import Boo.Lang.Compiler
import Boo.Lang.Compiler.IO
import Boo.Lang.Compiler.Pipelines
import Boo.Lang.Compiler.Steps

class AssemblyResolver:

	_cache = {}

	def AddAssembly([required] asm as Assembly):
		_cache[GetSimpleName(asm.FullName)] = asm

	def LoadAssembly([required] name as string):
		asm = ProbeFile(name)
		if asm is not null:
			_cache[asm.GetName().Name] = asm
		return asm

	def AssemblyResolve(sender, args as ResolveEventArgs) as Assembly:
		simpleName = GetSimpleName(args.Name)

		asm as Assembly = _cache[simpleName]
		if asm is null:
			basePath = Path.GetFullPath(simpleName)
			asm = ProbeFile(basePath + ".dll")
			if asm is null:
				asm = ProbeFile(basePath + ".exe")
			_cache[simpleName] = asm

		return asm

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

	private def ProbeFile(fname as string):
		return Assembly.LoadFrom(fname) if File.Exists(fname)

def consume(reader as TextReader):
	return join(line for line in reader, "\n")

[STAThread]
def Main(argv as (string)) as int:

	DefaultErrorCode = 255
	DefaultSuccessCode = 0

	if len(argv) < 1:
		print("booi <script.boo>")
		return DefaultErrorCode

	resolver = AssemblyResolver()
	AppDomain.CurrentDomain.AssemblyResolve += resolver.AssemblyResolve

	compiler = BooCompiler()
	compiler.Parameters.Pipeline = CompileToMemory()

	consumedArgs = 1
	asm as Assembly = null
	for arg in argv:
		if "-" == arg:
			compiler.Parameters.Input.Add(StringInput("<stdin>", consume(Console.In)))
			break
		elif "-ducky" == arg:
			compiler.Parameters.Ducky = true
		elif "-wsa" == arg:
			compiler.Parameters.Pipeline[0] = Boo.Lang.Parser.WSABooParsingStep()
		elif "-w" == arg:
			printWarnings = true
		elif arg.StartsWith("-r:"):
			//compiler.Parameters.References.Add(resolver.LoadAssembly(arg[3:]))
			try:
				asm = compiler.Parameters.LoadAssembly(arg[3:])
				if asm is null:
					print Boo.Lang.ResourceManager.Format("BooC.UnableToLoadAssembly", arg[3:])
				else:
					compiler.Parameters.References.Add(asm)
					resolver.AddAssembly(asm)
			except e:
				print e.Message
		else:
			compiler.Parameters.Input.Add(FileInput(arg))
			break
		++consumedArgs

	result = compiler.Run()
	if printWarnings and len(result.Warnings):
		print(result.Warnings.ToString())
	if len(result.Errors):
		print(result.Errors.ToString(true))
		return DefaultErrorCode
	else:
		try:
			resolver.AddAssembly(result.GeneratedAssembly)
			main = result.GeneratedAssembly.EntryPoint
			if len(main.GetParameters()) > 0:
				returnValue = main.Invoke(null, (argv[consumedArgs:],))
			else:
				returnValue = main.Invoke(null, null)
			return returnValue if returnValue is not null
		except x as TargetInvocationException:
			print(x.InnerException)
			return DefaultErrorCode
	return DefaultSuccessCode

[assembly: SecurityPermission(
						SecurityAction.RequestMinimum,
						ControlAppDomain: true)]


mono compatiblity fix

mono compatiblity fix

git-svn-id: c3b8989232654601f1acea7403f771adaaa44a81@1228 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.Tests

import System
import System.IO
import NUnit.Framework
import booish

[TestFixture]
class InteractiveInterpreterTestFixture:

	public static LifeTheUniverseAndEverything = 42

	class ConsoleCapture(IDisposable):
		_console = StringWriter()
		_old

		def constructor():
			_old = Console.Out
			Console.SetOut(_console)

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

		def Dispose():
			Console.SetOut(_old)

	_interpreter as InteractiveInterpreter

	[SetUp]
	def SetUp():
		_interpreter = InteractiveInterpreter()
		_interpreter.SetValue("name", "boo")
		_interpreter.SetValue("age", 3)

	[Test]
	def DefaultValues():
		assert false == _interpreter.RememberLastValue
		assert _interpreter.LastValue is null

	[Test]
	def UseInterpreterValues():
		using console=ConsoleCapture():
			Eval("print(name);print(age)")
		newLine = Environment.NewLine
		Assert.AreEqual("boo${newLine}3${newLine}", console.ToString())

	[Test]
	def Unpacking():
		Eval("a, b = 1, 2")
		assert 1 == _interpreter.GetValue("a")
		assert 2 == _interpreter.GetValue("b")

		Eval("a, b = b, a")
		assert 2 == _interpreter.GetValue("a")
		assert 1 == _interpreter.GetValue("b")

	[Test]
	def ArraySlicing():
		Eval("a = 1, 2, 3")
		Eval("a1 = a[0]")
		Eval("a12 = a[0:-1]")
		Eval("a3 = a[-1]")

		assert _interpreter.GetValue("a") == (1, 2, 3)
		assert _interpreter.GetValue("a1") == 1
		assert _interpreter.GetValue("a12") == (1, 2)
		assert _interpreter.GetValue("a3") == 3

	[Test]
	def AssignmentToSlice():
		Eval("a = 1, 2, 3")
		Eval("a[-1] = 1")
		Eval("a[0] = 3")

		assert _interpreter.GetValue("a") == (3, 2, 1)

	[Test]
	def ChangeInterpreterValues():
		Eval("age = 4")
		assert 4 == _interpreter.GetValue("age")

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

	[Test]
	def Import():
		Eval("import Booish.Tests from Booish.Tests")
		Eval("value = InteractiveInterpreterTestFixture.LifeTheUniverseAndEverything")
		assert 42 == _interpreter.GetValue("value")

	[Test]
	def ImportingTwiceIsNoProblem():
		Eval("import Booish.Tests from Booish.Tests")
		Eval("import Booish.Tests from Booish.Tests")

		Eval("value = InteractiveInterpreterTestFixture.LifeTheUniverseAndEverything")
		assert 42 == _interpreter.GetValue("value")

	[Test]
	def AssignmentPreservesType():
		Eval("value = (a = 3).ToString()")
		Eval("a2 = a*2")
		Eval("value2 = value*2")
		assert 3 == _interpreter.GetValue("a")
		assert "3" == _interpreter.GetValue("value")
		assert 6 == _interpreter.GetValue("a2")
		assert "33" == _interpreter.GetValue("value2")

	[Test]
	[ExpectedException(System.Reflection.TargetInvocationException)]
	def RaiseInsideEval():
		Eval("raise System.ApplicationException()")

	[Test]
	def TreatObjectsAsDucks():
		Eval("""
class Person:
	[property(Name)] _name = ''

p as object = Person(Name: 'John')
""")
		Eval("name = p.Name")
		assert "John" == _interpreter.GetValue("name")

	[Test]
	def InterpreterRememberDeclaredTypes():
		Eval("i as object = 3")
		assert 3 == _interpreter.GetValue("i")
		Eval("i = '3' # is is typed object")
		assert "3" == _interpreter.GetValue("i")

	[Test]
	def MethodDef():

		_interpreter.SetValue("eggs", "eggs")

		Eval("""
def spam():
	return eggs
""")

		Eval("value = spam()")
		assert 'eggs' == _interpreter.GetValue("value")

	[Test]
	def InterpreterReference():

		assert _interpreter.GetValue("interpreter") is _interpreter

	[Test]
	def Reset():

		_interpreter.RememberLastValue = true

		Eval("a as int = 3")
		Eval("a")

		assert 3 == _interpreter.LastValue
		assert 3 == _interpreter.GetValue("a")
		assert _interpreter.Lookup("a") is int

		_interpreter.Reset()

		assert _interpreter.LastValue is null
		assert _interpreter.GetValue("a") is null
		assert _interpreter.Lookup("a") is null

	[Test]
	def TypeDef():

		_interpreter.SetValue("DefaultName", "boo")

		Eval("""
class Language:
	[property(Name)] _name = DefaultName
""")

		Eval("language = Language()")
		language as duck = _interpreter.GetValue("language")
		assert 'boo' == language.Name

		Eval("language = Language(Name: 'portuguese')")
		language = _interpreter.GetValue("language")
		assert 'portuguese' == language.Name

	[Test]
	def ExpressionWithoutSideEffectsNotAllowedInDefaultMode():
		_interpreter.RememberLastValue = false
		errors = _interpreter.Eval("2+2").Errors

		assert 1 == len(errors)
		assert "BCE0034" == errors[0].Code

	[Test]
	def BeginInvokeEndInvoke():
		Eval("""
def foo():
	return 42

handle = foo.BeginInvoke()
result = foo.EndInvoke(handle)
""")

		handle as duck = _interpreter.GetValue("handle")
		assert handle is not null
		assert handle.IsCompleted
		assert handle.EndInvokeCalled

		Assert.AreEqual(42, _interpreter.GetValue("result"))

	[Test]
	def BeginInvokeEndInvokeInSequentialEvals():
		Eval("""
def foo():
	return 42

handle = foo.BeginInvoke()
""")
		handle as duck = _interpreter.GetValue("handle")
		assert handle is not null
		assert not handle.EndInvokeCalled

		Eval("result = foo.EndInvoke(handle)")

		Assert.AreEqual(42, _interpreter.GetValue("result"))

		assert handle.IsCompleted
		assert handle.EndInvokeCalled

	[Test]
	def MethodReferences():
		Eval("""
def foo():
	return 42""")

		Eval("f1 = foo")
		Eval("f2 = foo")

		f1 as callable = _interpreter.GetValue("f1")
		f2 as callable = _interpreter.GetValue("f2")
		assert f1 is not null
		assert 42 == f1()

		assert f2 is not null
		assert 42 == f2()

	[Test]
	def EvalSimpleLiterals():

		_interpreter.RememberLastValue = true
		Eval("true")
		assert true == _interpreter.LastValue

		Eval("false")
		assert false == _interpreter.LastValue

		Eval("null")
		assert _interpreter.LastValue is null

	[Test]
	def RememberLastValue():
		_interpreter.RememberLastValue = true

		Eval("2+2")
		assert 4 == _interpreter.LastValue

		Eval("3")
		assert 3 == _interpreter.LastValue

	[Test]
	def DisableRememberLastValue():
		_interpreter.RememberLastValue = false
		Eval("a=2+2")
		assert _interpreter.LastValue is null

	[Test]
	def MethodVariablesAreNotGlobalToTheInterpreter():
		Eval("""
def foo():
	a = 3

b = 4""")
		assert _interpreter.Lookup("b") is int
		assert _interpreter.Lookup("a") is null

	[Test]
	def EvaluateClosure():
		_interpreter.RememberLastValue = true
		Eval("{ return 42 }")
		assert 42 == cast(callable, _interpreter.LastValue)()

	[Test]
	def EvaluateVoidFunctionSetsValueToNull():

		_interpreter.RememberLastValue = true
		Eval("""
def dummy():
	pass
42
dummy()""")
		assert _interpreter.LastValue is null

	[Test]
	def Closures():

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

		x2 as callable = _interpreter.GetValue("x2")
		assert x2 is not null
		assert 4 == x2(2)

	[Test]
	def EvalSimpleReferenceGetsItsValue():

		_interpreter.RememberLastValue = true

		Eval("name")
		assert "boo" == _interpreter.LastValue

		Eval("age")
		assert 3 == _interpreter.LastValue

	[Test]
	def GeneratorExpressions():

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

	[Test]
	def LoopEval():

		value = ""
		_interpreter.RememberLastValue = true
		_interpreter.Print = { item | value = item }
		_interpreter.LoopEval("3+3")
		assert "6" == value
		assert 6 == _interpreter.GetValue("_")

		_interpreter.LoopEval("'42'*3")
		assert "'424242'" == value
		assert "424242" == _interpreter.GetValue("_")

	class Customer:

		[property(FirstName)]
		_fname as string

		public LastName as string

		def constructor(name as string):
			_name = name

		def constructor():
			pass

	[Test]
	def Help():

		buffer = System.IO.StringWriter()
		buffer.WriteLine()
		_interpreter.Print = { item | buffer.WriteLine(item) }
		_interpreter.help(Customer)

		expected = """
class Customer(object):

    def constructor(name as string)

    def constructor()

    public LastName as string

    FirstName as string:
        get
        set

    def Equals(obj as object) as bool

    def GetHashCode() as int

    def GetType() as System.Type

    def ToString() as string

"""
		actual = buffer.ToString().Replace("\r\n", "\n")

		# mono compatibility fix
		# object.Equals arg on mono is called o
		actual = actual.Replace("o as object", "obj as object")

		Assert.AreEqual(expected, actual)

	[Test]
	def Loop():

		# let's loop
		Eval("""
l = []
for i in range(3):
	l.Add(i*2)
""")
		Assert.AreEqual([0, 2, 4], _interpreter.GetValue("l"))

	[Test]
	def UnpackingLoop():
		Eval("""
l = []
for i, j in ((1, 2), (3, 4)):
	l.Add((j, i))
""")

		Assert.AreEqual([(2, 1), (4, 3)], _interpreter.GetValue("l"))

	def Eval(code as string):
		result = _interpreter.Eval(code)
		assert 0 == len(result.Errors), result.Errors.ToString(true)

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

import System
import System.IO
import NUnit.Framework
import booish

[TestFixture]
class InteractiveInterpreterTestFixture:

	public static LifeTheUniverseAndEverything = 42

	class ConsoleCapture(IDisposable):
		_console = StringWriter()
		_old

		def constructor():
			_old = Console.Out
			Console.SetOut(_console)

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

		def Dispose():
			Console.SetOut(_old)

	_interpreter as InteractiveInterpreter

	[SetUp]
	def SetUp():
		_interpreter = InteractiveInterpreter()
		_interpreter.SetValue("name", "boo")
		_interpreter.SetValue("age", 3)

	[Test]
	def DefaultValues():
		assert false == _interpreter.RememberLastValue
		assert _interpreter.LastValue is null

	[Test]
	def UseInterpreterValues():
		using console=ConsoleCapture():
			Eval("print(name);print(age)")
		newLine = Environment.NewLine
		Assert.AreEqual("boo${newLine}3${newLine}", console.ToString())

	[Test]
	def Unpacking():
		Eval("a, b = 1, 2")
		assert 1 == _interpreter.GetValue("a")
		assert 2 == _interpreter.GetValue("b")

		Eval("a, b = b, a")
		assert 2 == _interpreter.GetValue("a")
		assert 1 == _interpreter.GetValue("b")

	[Test]
	def ArraySlicing():
		Eval("a = 1, 2, 3")
		Eval("a1 = a[0]")
		Eval("a12 = a[0:-1]")
		Eval("a3 = a[-1]")

		assert _interpreter.GetValue("a") == (1, 2, 3)
		assert _interpreter.GetValue("a1") == 1
		assert _interpreter.GetValue("a12") == (1, 2)
		assert _interpreter.GetValue("a3") == 3

	[Test]
	def AssignmentToSlice():
		Eval("a = 1, 2, 3")
		Eval("a[-1] = 1")
		Eval("a[0] = 3")

		assert _interpreter.GetValue("a") == (3, 2, 1)

	[Test]
	def ChangeInterpreterValues():
		Eval("age = 4")
		assert 4 == _interpreter.GetValue("age")

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

	[Test]
	def Import():
		Eval("import Booish.Tests from Booish.Tests")
		Eval("value = InteractiveInterpreterTestFixture.LifeTheUniverseAndEverything")
		assert 42 == _interpreter.GetValue("value")

	[Test]
	def ImportingTwiceIsNoProblem():
		Eval("import Booish.Tests from Booish.Tests")
		Eval("import Booish.Tests from Booish.Tests")

		Eval("value = InteractiveInterpreterTestFixture.LifeTheUniverseAndEverything")
		assert 42 == _interpreter.GetValue("value")

	[Test]
	def AssignmentPreservesType():
		Eval("value = (a = 3).ToString()")
		Eval("a2 = a*2")
		Eval("value2 = value*2")
		assert 3 == _interpreter.GetValue("a")
		assert "3" == _interpreter.GetValue("value")
		assert 6 == _interpreter.GetValue("a2")
		assert "33" == _interpreter.GetValue("value2")

	[Test]
	[ExpectedException(System.Reflection.TargetInvocationException)]
	def RaiseInsideEval():
		Eval("raise System.ApplicationException()")

	[Test]
	def TreatObjectsAsDucks():
		Eval("""
class Person:
	[property(Name)] _name = ''

p as object = Person(Name: 'John')
""")
		Eval("name = p.Name")
		assert "John" == _interpreter.GetValue("name")

	[Test]
	def InterpreterRememberDeclaredTypes():
		Eval("i as object = 3")
		assert 3 == _interpreter.GetValue("i")
		Eval("i = '3' # is is typed object")
		assert "3" == _interpreter.GetValue("i")

	[Test]
	def MethodDef():

		_interpreter.SetValue("eggs", "eggs")

		Eval("""
def spam():
	return eggs
""")

		Eval("value = spam()")
		assert 'eggs' == _interpreter.GetValue("value")

	[Test]
	def InterpreterReference():

		assert _interpreter.GetValue("interpreter") is _interpreter

	[Test]
	def Reset():

		_interpreter.RememberLastValue = true

		Eval("a as int = 3")
		Eval("a")

		assert 3 == _interpreter.LastValue
		assert 3 == _interpreter.GetValue("a")
		assert _interpreter.Lookup("a") is int

		_interpreter.Reset()

		assert _interpreter.LastValue is null
		assert _interpreter.GetValue("a") is null
		assert _interpreter.Lookup("a") is null

	[Test]
	def TypeDef():

		_interpreter.SetValue("DefaultName", "boo")

		Eval("""
class Language:
	[property(Name)] _name = DefaultName
""")

		Eval("language = Language()")
		language as duck = _interpreter.GetValue("language")
		assert 'boo' == language.Name

		Eval("language = Language(Name: 'portuguese')")
		language = _interpreter.GetValue("language")
		assert 'portuguese' == language.Name

	[Test]
	def ExpressionWithoutSideEffectsNotAllowedInDefaultMode():
		_interpreter.RememberLastValue = false
		errors = _interpreter.Eval("2+2").Errors

		assert 1 == len(errors)
		assert "BCE0034" == errors[0].Code

	[Test]
	def BeginInvokeEndInvoke():
		Eval("""
def foo():
	return 42

handle = foo.BeginInvoke()
result = foo.EndInvoke(handle)
""")

		handle as duck = _interpreter.GetValue("handle")
		assert handle is not null
		assert handle.IsCompleted
		assert handle.EndInvokeCalled

		Assert.AreEqual(42, _interpreter.GetValue("result"))

	[Test]
	def BeginInvokeEndInvokeInSequentialEvals():
		Eval("""
def foo():
	return 42

handle = foo.BeginInvoke()
""")
		handle as duck = _interpreter.GetValue("handle")
		assert handle is not null
		assert not handle.EndInvokeCalled

		Eval("result = foo.EndInvoke(handle)")

		Assert.AreEqual(42, _interpreter.GetValue("result"))

		assert handle.IsCompleted
		assert handle.EndInvokeCalled

	[Test]
	def MethodReferences():
		Eval("""
def foo():
	return 42""")

		Eval("f1 = foo")
		Eval("f2 = foo")

		f1 as callable = _interpreter.GetValue("f1")
		f2 as callable = _interpreter.GetValue("f2")
		assert f1 is not null
		assert 42 == f1()

		assert f2 is not null
		assert 42 == f2()

	[Test]
	def EvalSimpleLiterals():

		_interpreter.RememberLastValue = true
		Eval("true")
		assert true == _interpreter.LastValue

		Eval("false")
		assert false == _interpreter.LastValue

		Eval("null")
		assert _interpreter.LastValue is null

	[Test]
	def RememberLastValue():
		_interpreter.RememberLastValue = true

		Eval("2+2")
		assert 4 == _interpreter.LastValue

		Eval("3")
		assert 3 == _interpreter.LastValue

	[Test]
	def DisableRememberLastValue():
		_interpreter.RememberLastValue = false
		Eval("a=2+2")
		assert _interpreter.LastValue is null

	[Test]
	def MethodVariablesAreNotGlobalToTheInterpreter():
		Eval("""
def foo():
	a = 3

b = 4""")
		assert _interpreter.Lookup("b") is int
		assert _interpreter.Lookup("a") is null

	[Test]
	def EvaluateClosure():
		_interpreter.RememberLastValue = true
		Eval("{ return 42 }")
		assert 42 == cast(callable, _interpreter.LastValue)()

	[Test]
	def EvaluateVoidFunctionSetsValueToNull():

		_interpreter.RememberLastValue = true
		Eval("""
def dummy():
	pass
42
dummy()""")
		assert _interpreter.LastValue is null

	[Test]
	def Closures():

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

		x2 as callable = _interpreter.GetValue("x2")
		assert x2 is not null
		assert 4 == x2(2)

	[Test]
	def EvalSimpleReferenceGetsItsValue():

		_interpreter.RememberLastValue = true

		Eval("name")
		assert "boo" == _interpreter.LastValue

		Eval("age")
		assert 3 == _interpreter.LastValue

	[Test]
	def GeneratorExpressions():

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

	[Test]
	def LoopEval():

		value = ""
		_interpreter.RememberLastValue = true
		_interpreter.Print = { item | value = item }
		_interpreter.LoopEval("3+3")
		assert "6" == value
		assert 6 == _interpreter.GetValue("_")

		_interpreter.LoopEval("'42'*3")
		assert "'424242'" == value
		assert "424242" == _interpreter.GetValue("_")

	class Customer:

		[property(FirstName)]
		_fname as string

		public LastName as string

		def constructor(name as string):
			_name = name

		def constructor():
			pass

	[Test]
	def Help():

		buffer = System.IO.StringWriter()
		buffer.WriteLine()
		_interpreter.Print = { item | buffer.WriteLine(item) }
		_interpreter.help(Customer)

		expected = """
class Customer(object):

    def constructor(name as string)

    def constructor()

    public LastName as string

    FirstName as string:
        get
        set

    def Equals(obj as object) as bool

    def GetHashCode() as int

    def GetType() as System.Type

    def ToString() as string

"""
		Assert.AreEqual(expected, buffer.ToString().Replace("\r\n", "\n"))

	[Test]
	def Loop():

		# let's loop
		Eval("""
l = []
for i in range(3):
	l.Add(i*2)
""")
		Assert.AreEqual([0, 2, 4], _interpreter.GetValue("l"))

	[Test]
	def UnpackingLoop():
		Eval("""
l = []
for i, j in ((1, 2), (3, 4)):
	l.Add((j, i))
""")

		Assert.AreEqual([(2, 1), (4, 3)], _interpreter.GetValue("l"))

	def Eval(code as string):
		result = _interpreter.Eval(code)
		assert 0 == len(result.Errors), result.Errors.ToString(true)


minor formatting change

minor formatting change

"""
BCE0017-4.boo(18,4): BCE0017: The best overload for the method 'BCE0017_4Module.Foo(System.Collections.IEnumerable)' is not compatible with the argument list '(Test)'.
"""

import System.Collections

macro enableStrict:
	Parameters.Strict = true

enableStrict

class Test:
	pass

def Foo(x as IEnumerable):
	pass

Foo(Test())


refactor logic in GenerateFullPathToTool()

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

		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

	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


Fix for WSABooParserTestFixture.try_1, try_2, try_3

namespace Boo.OMeta.Parser

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

ometa WSABooParser < BooParser:
	scanner = (empty_lines ^ makeToken("eol")) | ((--space, tokens >> t) ^ t)

	keywords = ~"pass", ("end" | super)

	begin_block = COLON, eol
	end_block = (keyword["end"], eol) | (~~ELSE) | (~~ELIF) | (~~(OR, COLON)) | (~~THEN) | (~~EXCEPT) | (~~ENSURE)
	empty_block = (begin_block, end_block) ^ Block()

	member_reference = (((member_reference >> e,  DOT, enterWhitespaceAgnosticRegion, ID >> name, leaveWhitespaceAgnosticRegion) \
		^ newMemberReference(e, name)) | slicing) >> e, (INCREMENT | DECREMENT | "") >> postOp ^ addSuffixUnaryOperator(e, postOp)

	INDENT = eol | ""
	DEDENT = eol | ""

	class_body = (--class_member >> members ^ members)
	struct_body = (--struct_member >> members ^ members)
	interface_body = (--interface_member >> members ^ members)

namespace Boo.OMeta.Parser

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

ometa WSABooParser < BooParser:
	scanner = (empty_lines ^ makeToken("eol")) | ((--space, tokens >> t) ^ t)

	keywords = ~"pass", ("end" | super)

	begin_block = COLON, eol
	end_block = (keyword["end"], eol) | (~~ELSE) | (~~ELIF) | (~~(OR, COLON)) | (~~THEN)
	empty_block = (begin_block, end_block) ^ Block()

	member_reference = (((member_reference >> e,  DOT, enterWhitespaceAgnosticRegion, ID >> name, leaveWhitespaceAgnosticRegion) \
		^ newMemberReference(e, name)) | slicing) >> e, (INCREMENT | DECREMENT | "") >> postOp ^ addSuffixUnaryOperator(e, postOp)

	INDENT = eol | ""
	DEDENT = eol | ""

	class_body = (--class_member >> members ^ members)
	struct_body = (--struct_member >> members ^ members)
	interface_body = (--interface_member >> members ^ members)


support for single data constructor with base type

namespace Boo.Adt

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

macro data:
"""
Produces a complete data type hierarchy from a declaration.

All the types produced are immutable (unless instructed otherwise) and have
meaningful implementations for Equals, GetHashCode and ToString.

Usage:

	data Expression = Const(Value as int) | Add(Left as Expression, Right as Expression)

	data SingleValue(Value as int)

	data TypeWithMutableField(@MutableValue as int) // @ makes the field mutable
"""

	DataMacroExpansion(data)

class DataMacroExpansion:

	_module as TypeDefinition
	_baseType as TypeDefinition

	def constructor(node as MacroStatement):

		assert 1 == len(node.Arguments)
		_module = enclosingModule(node)

		match node.Arguments[0]:
			case [| $left = $right |]:
				_baseType = createBaseType(left)
				expandDataConstructors(right)

			case [| $(ctor=MethodInvocationExpression()) < $baseType |]:
				_baseType = [|
					class $baseType:
						pass
				|]
				expandDataConstructor(ctor)

			case ctor=MethodInvocationExpression():
				_baseType = [|
					class object:
						pass
				|]
				expandDataConstructor(ctor)

	def createBaseType(node as Expression):
		type = baseTypeForExpression(node)
		registerType(type)
		return type

	def abstractType(name as string):
		type = [|
			partial abstract class $name:
				pass
		|]
		return type

	def baseTypeForExpression(node as Expression):
		match node:
			case ReferenceExpression(Name: name):
				return abstractType(name)

			case [| $(ReferenceExpression(Name: name)) < $(ReferenceExpression(Name: baseType)) |]:
				type = [|
					partial abstract class $name($baseType):
						pass
				|]
				return type

			case mie=MethodInvocationExpression(Target: ReferenceExpression(Name: name)):
				type = abstractType(name)
				fields = fieldsFrom(mie)
				type.Members.Extend(fields)
				type.Members.Add(constructorForFields(fields))
				return type

			case gre=SlicingExpression(
						Target: ReferenceExpression(Name: name)):
				type = [|
					partial abstract class $name[of T]:
						pass
				|]
				type.GenericParameters.Clear()
				for index in gre.Indices:
					match index:
						case Slice(
							Begin: ReferenceExpression(Name: name),
							End: null,
							Step: null):
							type.GenericParameters.Add(
								GenericParameterDeclaration(Name: name))
				return type

	def expandDataConstructors(node as Expression):
		match node:
			case [| $left | $right |]:
				expandDataConstructors(left)
				expandDataConstructors(right)
			case MethodInvocationExpression():
				expandDataConstructor(node)

	def expandDataConstructor(node as MethodInvocationExpression):
		type = dataConstructorTypeForExpression(node.Target)
		type.LexicalInfo = node.LexicalInfo
		type.Members.Extend(fields = fieldsFrom(node))
		type.Members.Add(toStringForType(type))
		type.Members.Add(equalsForType(type))

		ctor = constructorForFields(fields)
		if len(_baseType.Members):
			superInvocation = [| super() |]
			i = 0
			for field as Field in fieldsOf(_baseType):
				ctor.Parameters.Insert(i++,
					ParameterDeclaration(Name: field.Name, Type: field.Type))
				superInvocation.Arguments.Add(ReferenceExpression(field.Name))
			ctor.Body.Insert(0, superInvocation)
		type.Members.Add(ctor)

		registerType(type)

	def dataConstructorTypeForExpression(node as Expression):
		match node:
			case ReferenceExpression(Name: name):
				type = [|
					partial class $name($_baseType):
						pass
				|]
				for arg in _baseType.GenericParameters:
					type.GenericParameters.Add(
								GenericParameterDeclaration(Name: arg.Name))
				return type

	def equalsForType(type as TypeDefinition):

		allFields = List(fieldsIncludingBaseType(type))
		if len(allFields) == 0:
			method = [|
				override def Equals(o):
					if o is null: return false
					return self.GetType() is o.GetType()
			|]
			return method

		method = [|
			override def Equals(o):
				if o is null: return false
				if self.GetType() is not o.GetType(): return false
				other as $type = o
		|]

		for field as Field in allFields:
			comparison = [|
				if self.$(field.Name) != other.$(field.Name):
					return false
			|]
			method.Body.Add(comparison)

		method.Body.Add([| return true |])
		return method

	def toStringForType(type as TypeDefinition):
		expression = ExpressionInterpolationExpression()
		items = expression.Expressions
		items.Add([| $("${type.Name}(") |])

		comma = false
		for field in fieldsIncludingBaseType(type):
			if comma: items.Add([| ", " |])
			items.Extend(toStringForField(field))

			comma = true

		items.Add([| $(")") |])
		return [|
			override def ToString():
				return $expression
		|]

	def toStringForField(field as Field):
		match field.Type:
			case ArrayTypeReference():
				yield [| "[" |]
				yield [| join(self.$(field.Name), ', ') |]
				yield [| "]" |]
			otherwise:
				yield [| self.$(field.Name) |]

	def fieldsIncludingBaseType(type as TypeDefinition):
		return cat(fieldsOf(_baseType), fieldsOf(type))

	def fieldsOf(type as TypeDefinition):
		return type.Members.Select(NodeType.Field)

	def constructorForFields(fields as (Field)):
		ctor = [|
			def constructor():
				pass
		|]
		for f in fields:
			ctor.Parameters.Add(p=ParameterDeclaration(Name: f.Name, Type: f.Type))
			ctor.Body.Add([| self.$(f.Name) = $p |])
		ctor.Parameters.HasParamArray = len(fields) > 0 and fields[-1].ContainsAnnotation("*")
		return ctor

	def fieldsFrom(node as MethodInvocationExpression):
		return array(fieldFrom(expression) for expression in node.Arguments)

	def fieldFrom(node as Expression):
		match node:
			case [| *$(ReferenceExpression(Name: name)) as $(arrayType = ArrayTypeReference()) |]:
				return arrayField(name, arrayType)
			case [| *$(ReferenceExpression(Name: name)) |]:
				return arrayField(name, ArrayTypeReference(baseTypeRef()))
			case [| $(ReferenceExpression(Name: name)) as $type |]:
				return fieldWith(name, type)
			case ReferenceExpression(Name: name):
				return fieldWith(name, baseTypeRef())

	def arrayField(name as string, arrayType as ArrayTypeReference):
		field = fieldWith(name, arrayType)
		field.Annotate("*")
		return field

	def baseTypeRef():
		return TypeReference.Lift(_baseType)

	def fieldWith(name as string, type as TypeReference):
		if name.StartsWith("@"): // mutable field
			return [|
				public $(fieldName(name)) as $type
			|]
		return [|
			public final $name as $type
		|]

	def fieldName(name as string):
		return (name[1:] if name.StartsWith("@") else name)

	def registerType(type as TypeDefinition):
		_module.Members.Add(type)

def enclosingModule(node as Node) as Module:
	return node.GetAncestor(NodeType.Module)

namespace Boo.Adt

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

macro data:
"""
Produces a complete data type hierarchy from a declaration.

All the types produced are immutable (unless instructed otherwise) and have
meaningful implementations for Equals, GetHashCode and ToString.

Usage:

	data Expression = Const(Value as int) | Add(Left as Expression, Right as Expression)

	data SingleValue(Value as int)

	data TypeWithMutableField(@MutableValue as int) // @ makes the field mutable
"""

	DataMacroExpansion(data)

class DataMacroExpansion:

	_module as TypeDefinition
	_baseType as TypeDefinition

	def constructor(node as MacroStatement):

		assert 1 == len(node.Arguments)
		_module = enclosingModule(node)

		match node.Arguments[0]:
			case [| $left = $right |]:
				_baseType = createBaseType(left)
				expandDataConstructors(right)

			case ctor=MethodInvocationExpression():
				_baseType = [|
					class object:
						pass
				|]
				expandDataConstructor(ctor)

	def createBaseType(node as Expression):
		type = baseTypeForExpression(node)
		registerType(type)
		return type

	def abstractType(name as string):
		type = [|
			partial abstract class $name:
				pass
		|]
		return type

	def baseTypeForExpression(node as Expression):
		match node:
			case ReferenceExpression(Name: name):
				return abstractType(name)

			case [| $(ReferenceExpression(Name: name)) < $(ReferenceExpression(Name: baseType)) |]:
				type = [|
					partial abstract class $name($baseType):
						pass
				|]
				return type

			case mie=MethodInvocationExpression(Target: ReferenceExpression(Name: name)):
				type = abstractType(name)
				fields = fieldsFrom(mie)
				type.Members.Extend(fields)
				type.Members.Add(constructorForFields(fields))
				return type

			case gre=SlicingExpression(
						Target: ReferenceExpression(Name: name)):
				type = [|
					partial abstract class $name[of T]:
						pass
				|]
				type.GenericParameters.Clear()
				for index in gre.Indices:
					match index:
						case Slice(
							Begin: ReferenceExpression(Name: name),
							End: null,
							Step: null):
							type.GenericParameters.Add(
								GenericParameterDeclaration(Name: name))
				return type

	def expandDataConstructors(node as Expression):
		match node:
			case [| $left | $right |]:
				expandDataConstructors(left)
				expandDataConstructors(right)
			case MethodInvocationExpression():
				expandDataConstructor(node)

	def expandDataConstructor(node as MethodInvocationExpression):
		type = dataConstructorTypeForExpression(node.Target)
		type.LexicalInfo = node.LexicalInfo
		type.Members.Extend(fields = fieldsFrom(node))
		type.Members.Add(toStringForType(type))
		type.Members.Add(equalsForType(type))

		ctor = constructorForFields(fields)
		if len(_baseType.Members):
			superInvocation = [| super() |]
			i = 0
			for field as Field in fieldsOf(_baseType):
				ctor.Parameters.Insert(i++,
					ParameterDeclaration(Name: field.Name, Type: field.Type))
				superInvocation.Arguments.Add(ReferenceExpression(field.Name))
			ctor.Body.Insert(0, superInvocation)
		type.Members.Add(ctor)

		registerType(type)

	def dataConstructorTypeForExpression(node as Expression):
		match node:
			case ReferenceExpression(Name: name):
				type = [|
					partial class $name($_baseType):
						pass
				|]
				for arg in _baseType.GenericParameters:
					type.GenericParameters.Add(
								GenericParameterDeclaration(Name: arg.Name))
				return type

	def equalsForType(type as TypeDefinition):

		allFields = List(fieldsIncludingBaseType(type))
		if len(allFields) == 0:
			method = [|
				override def Equals(o):
					if o is null: return false
					return self.GetType() is o.GetType()
			|]
			return method

		method = [|
			override def Equals(o):
				if o is null: return false
				if self.GetType() is not o.GetType(): return false
				other as $type = o
		|]

		for field as Field in allFields:
			comparison = [|
				if self.$(field.Name) != other.$(field.Name):
					return false
			|]
			method.Body.Add(comparison)

		method.Body.Add([| return true |])
		return method

	def toStringForType(type as TypeDefinition):
		expression = ExpressionInterpolationExpression()
		items = expression.Expressions
		items.Add([| $("${type.Name}(") |])

		comma = false
		for field in fieldsIncludingBaseType(type):
			if comma: items.Add([| ", " |])
			items.Extend(toStringForField(field))

			comma = true

		items.Add([| $(")") |])
		return [|
			override def ToString():
				return $expression
		|]

	def toStringForField(field as Field):
		match field.Type:
			case ArrayTypeReference():
				yield [| "[" |]
				yield [| join(self.$(field.Name), ', ') |]
				yield [| "]" |]
			otherwise:
				yield [| self.$(field.Name) |]

	def fieldsIncludingBaseType(type as TypeDefinition):
		return cat(fieldsOf(_baseType), fieldsOf(type))

	def fieldsOf(type as TypeDefinition):
		return type.Members.Select(NodeType.Field)

	def constructorForFields(fields as (Field)):
		ctor = [|
			def constructor():
				pass
		|]
		for f in fields:
			ctor.Parameters.Add(p=ParameterDeclaration(Name: f.Name, Type: f.Type))
			ctor.Body.Add([| self.$(f.Name) = $p |])
		ctor.Parameters.HasParamArray = len(fields) > 0 and fields[-1].ContainsAnnotation("*")
		return ctor

	def fieldsFrom(node as MethodInvocationExpression):
		return array(fieldFrom(expression) for expression in node.Arguments)

	def fieldFrom(node as Expression):
		match node:
			case [| *$(ReferenceExpression(Name: name)) as $(arrayType = ArrayTypeReference()) |]:
				return arrayField(name, arrayType)
			case [| *$(ReferenceExpression(Name: name)) |]:
				return arrayField(name, ArrayTypeReference(baseTypeRef()))
			case [| $(ReferenceExpression(Name: name)) as $type |]:
				return fieldWith(name, type)
			case ReferenceExpression(Name: name):
				return fieldWith(name, baseTypeRef())

	def arrayField(name as string, arrayType as ArrayTypeReference):
		field = fieldWith(name, arrayType)
		field.Annotate("*")
		return field

	def baseTypeRef():
		return TypeReference.Lift(_baseType)

	def fieldWith(name as string, type as TypeReference):
		if name.StartsWith("@"): // mutable field
			return [|
				public $(fieldName(name)) as $type
			|]
		return [|
			public final $name as $type
		|]

	def fieldName(name as string):
		return (name[1:] if name.StartsWith("@") else name)

	def registerType(type as TypeDefinition):
		_module.Members.Add(type)

def enclosingModule(node as Node) as Module:
	return node.GetAncestor(NodeType.Module)


Only initialize lookup paths for XML docs if not IsMono

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

namespace Boo.Lang.Useful.Doc

import System
import System.Xml
import System.Collections
import System.IO

import Boo.Lang.Useful.PlatformInformation

static class DocDB:
"""
	A singleton implementing a database of XML documentation on assemblies
	(probably in Sandcastle style). The database provides automatic reload
	w.r.t. some predefined or user defined look up paths.
"""
	[Getter(LookupPaths)]
	_lookupPaths as Generic.ICollection[of string] = List[of string]()

	def constructor():
		if not IsMono:
			p=Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)
			if Directory.Exists(p):
				p=Path.Combine(p, "Reference Assemblies\\Microsoft")
				if Directory.Exists(p):
					_lookupPaths.Add(p)
			p=Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86)
			if Directory.Exists(p):
				p=Path.Combine(p, "Reference Assemblies\\Microsoft")
				if Directory.Exists(p):
					_lookupPaths.Add(p)

	_db = Generic.SortedList[of string, AssemblyDoc]()

	public def ToDesignator(ns as string):
	"""
		Returns the designator of a namespace.
	"""
		return "N:"+ns

	public def ToDesignator(t as Type):
	"""
		Returns the designator that an be used to retrieve docs on
		a certain type.
	"""
		return "T:"+t.ToString()

	public def ToDesignator(a as Reflection.Assembly):
	"""
		Returns a designator that can be used to retrieve docs on
		a certain assembly.
	"""
		return a.GetName().Name

	public def ToDesignator(mi as Reflection.MethodInfo, withArguments as bool):
	"""
		Creates a designator for a particular method.
	"""
		r = "M:"+mi.DeclaringType.ToString()+"."+mi.Name
		parameters = mi.GetParameters()
		if withArguments and parameters != null and parameters.Length > 0:
			r+="("
			i=0
			for p in parameters:
				r+="," if i > 0
				r+=p.ParameterType.ToString()
				i+=1
			r+=")"
		return r

	public def ToDesignator(mi as Reflection.ConstructorInfo, withArguments as bool):
	"""
		Creates a designator for a particular ctor.
	"""
		r = "M:"+mi.DeclaringType.ToString()+".#ctor"
		parameters = mi.GetParameters()
		if withArguments and parameters != null and parameters.Length > 0:
			r+="("
			i=0
			for p in parameters:
				r+="," if i > 0
				r+=p.ParameterType.ToString()
				i+=1
			r+=")"
		return r

	public def ToDesignator(pi as Reflection.PropertyInfo):
	"""
		Creates a designator for a particular property.
	"""
		return "P:"+pi.DeclaringType.ToString()+"."+pi.Name

	public def ToDesignator(fi as Reflection.FieldInfo):
	"""
		Creates a designator for a particular field.
	"""
		return "F:"+fi.DeclaringType.ToString()+"."+fi.Name

	public def ToDesignator(evt as Reflection.EventInfo):
	"""
		Creates a designator for a particular event.
	"""
		return "E:"+evt.DeclaringType.ToString()+"."+evt.Name

	public def Find(assembly as Reflection.Assembly, elementDesignator):
	"""
		Find documentation of a particular element that refers to
		a particular assembly. This will return null if nothing has
		been found.
	"""
		assemblyDesignator = ToDesignator(assembly)
		assemblyDoc as AssemblyDoc
		if _db.TryGetValue(assemblyDesignator, assemblyDoc):
			return assemblyDoc.Find(elementDesignator)
		try:
			uri = Uri(assembly.CodeBase)
			path=uri.LocalPath
			path=Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path))+".xml"
			if File.Exists(path):
				assemblyDoc=AssemblyDoc(path)
				_db.Add(assemblyDesignator, assemblyDoc)
				return assemblyDoc.Find(elementDesignator)
		except:
			pass
		for d in _lookupPaths:
			try:
				path = FindFile(d, assemblyDesignator+".xml")
				if not string.IsNullOrEmpty(path):
					assemblyDoc=AssemblyDoc(path)
					_db.Add(assemblyDesignator, assemblyDoc)
					return assemblyDoc.Find(elementDesignator)
			except:
				pass
		return null

	def FindFile(dir as string, fileName as string) as string:
		path = Path.Combine(dir, fileName)
		return path if File.Exists(path)
		for subdir in Directory.GetDirectories(dir):
			path = FindFile(subdir, fileName)
			if not string.IsNullOrEmpty(path):
				return path
		return null

	public def Find(t as Type):
	"""
		Return the XML nodes comprising the documentation on
		a particular type or null if the documentation cannot
		be found.
	"""
		return Find(t.Assembly, ToDesignator(t))

	public def Find(mi as Reflection.MethodInfo):
	"""
		Return the XML nodes comprising the documentation on
		a particular method or null if the documentation cannot
		be found.
	"""
		result = Find(mi.DeclaringType.Assembly, ToDesignator(mi, true))
		result = Find(mi.DeclaringType.Assembly, ToDesignator(mi, false)) if result == null
		return result

	public def Find(ctor as Reflection.ConstructorInfo):
	"""
		Return the XML nodes comprising the documentation on
		a particular constructor or null if the documentation cannot
		be found.
	"""
		result = Find(ctor.DeclaringType.Assembly, ToDesignator(ctor, true))
		result = Find(ctor.DeclaringType.Assembly, ToDesignator(ctor, false)) if result == null
		return result

	public def Find(pi as Reflection.PropertyInfo):
	"""
		Return the XML nodes comprising the documentation on
		a particular property or null if the documentation cannot
		be found.
	"""
		return Find(pi.DeclaringType.Assembly, ToDesignator(pi))

	public def Find(fi as Reflection.FieldInfo):
	"""
		Return the XML nodes comprising the documentation on
		a particular property or null if the documentation cannot
		be found.
	"""
		return Find(fi.DeclaringType.Assembly, ToDesignator(fi))

	public def Find(evt as Reflection.EventInfo):
	"""
		Return the XML nodes comprising the documentation on
		a particular event or null if the documentation cannot
		be found.
	"""
		return Find(evt.DeclaringType.Assembly, ToDesignator(evt))

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

namespace Boo.Lang.Useful.Doc

import System
import System.Xml
import System.Collections
import System.IO

static class DocDB:
"""
	A singleton implementing a database of XML documentation on assemblies
	(probably in Sandcastle style). The database provides automatic reload
	w.r.t. some predefined or user defined look up paths.
"""
	[Getter(LookupPaths)]
	_lookupPaths as Generic.ICollection[of string] = List[of string]()

	def constructor():
		p=Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)
		if Directory.Exists(p):
			p=Path.Combine(p, "Reference Assemblies\\Microsoft")
			if Directory.Exists(p):
				_lookupPaths.Add(p)
		p=Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86)
		if Directory.Exists(p):
			p=Path.Combine(p, "Reference Assemblies\\Microsoft")
			if Directory.Exists(p):
				_lookupPaths.Add(p)

	_db = Generic.SortedList[of string, AssemblyDoc]()

	public def ToDesignator(ns as string):
	"""
		Returns the designator of a namespace.
	"""
		return "N:"+ns

	public def ToDesignator(t as Type):
	"""
		Returns the designator that an be used to retrieve docs on
		a certain type.
	"""
		return "T:"+t.ToString()

	public def ToDesignator(a as Reflection.Assembly):
	"""
		Returns a designator that can be used to retrieve docs on
		a certain assembly.
	"""
		return a.GetName().Name

	public def ToDesignator(mi as Reflection.MethodInfo, withArguments as bool):
	"""
		Creates a designator for a particular method.
	"""
		r = "M:"+mi.DeclaringType.ToString()+"."+mi.Name
		parameters = mi.GetParameters()
		if withArguments and parameters != null and parameters.Length > 0:
			r+="("
			i=0
			for p in parameters:
				r+="," if i > 0
				r+=p.ParameterType.ToString()
				i+=1
			r+=")"
		return r

	public def ToDesignator(mi as Reflection.ConstructorInfo, withArguments as bool):
	"""
		Creates a designator for a particular ctor.
	"""
		r = "M:"+mi.DeclaringType.ToString()+".#ctor"
		parameters = mi.GetParameters()
		if withArguments and parameters != null and parameters.Length > 0:
			r+="("
			i=0
			for p in parameters:
				r+="," if i > 0
				r+=p.ParameterType.ToString()
				i+=1
			r+=")"
		return r

	public def ToDesignator(pi as Reflection.PropertyInfo):
	"""
		Creates a designator for a particular property.
	"""
		return "P:"+pi.DeclaringType.ToString()+"."+pi.Name

	public def ToDesignator(fi as Reflection.FieldInfo):
	"""
		Creates a designator for a particular field.
	"""
		return "F:"+fi.DeclaringType.ToString()+"."+fi.Name

	public def ToDesignator(evt as Reflection.EventInfo):
	"""
		Creates a designator for a particular event.
	"""
		return "E:"+evt.DeclaringType.ToString()+"."+evt.Name

	public def Find(assembly as Reflection.Assembly, elementDesignator):
	"""
		Find documentation of a particular element that refers to
		a particular assembly. This will return null if nothing has
		been found.
	"""
		assemblyDesignator = ToDesignator(assembly)
		assemblyDoc as AssemblyDoc
		if _db.TryGetValue(assemblyDesignator, assemblyDoc):
			return assemblyDoc.Find(elementDesignator)
		try:
			uri = Uri(assembly.CodeBase)
			path=uri.LocalPath
			path=Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path))+".xml"
			if File.Exists(path):
				assemblyDoc=AssemblyDoc(path)
				_db.Add(assemblyDesignator, assemblyDoc)
				return assemblyDoc.Find(elementDesignator)
		except:
			pass
		for d in _lookupPaths:
			try:
				path = FindFile(d, assemblyDesignator+".xml")
				if not string.IsNullOrEmpty(path):
					assemblyDoc=AssemblyDoc(path)
					_db.Add(assemblyDesignator, assemblyDoc)
					return assemblyDoc.Find(elementDesignator)
			except:
				pass
		return null

	def FindFile(dir as string, fileName as string) as string:
		path = Path.Combine(dir, fileName)
		return path if File.Exists(path)
		for subdir in Directory.GetDirectories(dir):
			path = FindFile(subdir, fileName)
			if not string.IsNullOrEmpty(path):
				return path
		return null

	public def Find(t as Type):
	"""
		Return the XML nodes comprising the documentation on
		a particular type or null if the documentation cannot
		be found.
	"""
		return Find(t.Assembly, ToDesignator(t))

	public def Find(mi as Reflection.MethodInfo):
	"""
		Return the XML nodes comprising the documentation on
		a particular method or null if the documentation cannot
		be found.
	"""
		result = Find(mi.DeclaringType.Assembly, ToDesignator(mi, true))
		result = Find(mi.DeclaringType.Assembly, ToDesignator(mi, false)) if result == null
		return result

	public def Find(ctor as Reflection.ConstructorInfo):
	"""
		Return the XML nodes comprising the documentation on
		a particular constructor or null if the documentation cannot
		be found.
	"""
		result = Find(ctor.DeclaringType.Assembly, ToDesignator(ctor, true))
		result = Find(ctor.DeclaringType.Assembly, ToDesignator(ctor, false)) if result == null
		return result

	public def Find(pi as Reflection.PropertyInfo):
	"""
		Return the XML nodes comprising the documentation on
		a particular property or null if the documentation cannot
		be found.
	"""
		return Find(pi.DeclaringType.Assembly, ToDesignator(pi))

	public def Find(fi as Reflection.FieldInfo):
	"""
		Return the XML nodes comprising the documentation on
		a particular property or null if the documentation cannot
		be found.
	"""
		return Find(fi.DeclaringType.Assembly, ToDesignator(fi))

	public def Find(evt as Reflection.EventInfo):
	"""
		Return the XML nodes comprising the documentation on
		a particular event or null if the documentation cannot
		be found.
	"""
		return Find(evt.DeclaringType.Assembly, ToDesignator(evt))


stock menu items added; notebook for multiple editors; basic execute functionality

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

import System
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)

	def constructor():
		super(Adjustment(IntPtr.Zero), Adjustment(IntPtr.Zero))
		self.SetPolicy(PolicyType.Automatic, PolicyType.Automatic)
		self.Add(_view)

	Text:
		get:
			return _buffer.Text

class MainWindow(Window):

	_status = Statusbar(HasResizeGrip: false)
	_notebook = Notebook(TabPos: PositionType.Top, Scrollable: true)
	_accelGroup = AccelGroup()

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

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

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

		self.Add(vbox)

		self.NewDocument()

	def NewDocument():
		editor = BooEditor()
		_notebook.AppendPage(editor, Label("unnamed.boo"))
		editor.ShowAll()

	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))
		edit.Append(ImageMenuItem(Stock.Redo, _accelGroup))
		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

	private def _menuItemExecute_Activated(sender, args as EventArgs):
		// trying to track down a gtk-sharp bug
		// after a few executions (garbage collection?)
		// CurrentPageWidget will start to return a simple
		// ScrolledWindow (no longer a BooEditor)
		print(_notebook.CurrentPage)
		print(_notebook.CurrentPageWidget)
		editor = _notebook.CurrentPageWidget as BooEditor
		return if editor is null

		compiler = Boo.Lang.Compiler.BooCompiler()
		compiler.Parameters.Input.Add(Boo.Lang.Compiler.IO.StringInput("<script>", editor.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):
		pass

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

	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 Gtk from "gtk-sharp"
import GtkSourceView from "gtksourceview-sharp"

class MainWindow(Window):

	_status = Statusbar(HasResizeGrip: false)

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

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

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

		buffer = SourceBuffer(_booSourceLanguage, Highlight: true)
		sourceView = SourceView(buffer, ShowLineNumbers: true, AutoIndent: true)

		scrolledWindow = ScrolledWindow(Adjustment(IntPtr.Zero), Adjustment(IntPtr.Zero))
		scrolledWindow.SetPolicy(PolicyType.Automatic, PolicyType.Automatic)
		scrolledWindow.Add(sourceView)

		vbox = VBox(false, 1)
		vbox.Add(CreateMenuBar())
		vbox.Add(scrolledWindow)
		vbox.Add(_status)

		self.Add(vbox)

	private def CreateMenuBar():
		mb = MenuBar()

		file = Menu()
		file.Append(MenuItem("Exit", Activated: _menuItemExit_Activated))

		mb.Append(MenuItem("File", Submenu: file))
		return mb

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


ability to categorize test cases (in the nunit sense)

#!/usr/bin/env booi
"""
Generates test fixtures from files under tests/
"""
import System
import System.IO
import Boo.Lang.PatternMatching

//def MapPath(path):
//	return Path.Combine(Project.BaseDirectory, path)

def GetTestCaseName(fname as string):
	return Path.GetFileNameWithoutExtension(fname).Replace("-", "_")

def WriteTestCases(writer as TextWriter, baseDir as string):
	count = 0
	for fname in Directory.GetFiles(baseDir):
		continue unless fname.EndsWith(".js")
		++count
		categoryAttribute = CategoryAttributeFor(fname)
		writer.Write("""
	${categoryAttribute}
	[Test] def ${GetTestCaseName(fname)}():
		RunTestCase("${fname.Replace('\\', '/')}")
		""")
	print("${count} test cases found in ${baseDir}.")

def CategoryAttributeFor(testFile as string):
"""
If the first line of the test case file starts with // category CategoryName
then return a suitable [CategoryName()] attribute.
"""
	match FirstLineOf(testFile):
		case /\/\/\s*ignore\s+(?<reason>.*)/:
			return "[Ignore(\"${reason[0].Value.Trim()}\")]"
		case /\/\/\s*category\s+(?<name>.*)/:
			return "[Category(\"${name[0].Value.Trim()}\")]"
		otherwise:
			return ""

def FirstLineOf(fname as string):
	using reader=File.OpenText(fname):
		return reader.ReadLine()

def GenerateTestFixture(srcDir as string, targetFile as string, header as string):
	contents = GenerateTestFixtureSource(srcDir, header)
	if ShouldReplaceContent(targetFile, contents):
		WriteAllText(targetFile, contents)

def ShouldReplaceContent(fname as string, contents as string):
	if not File.Exists(fname): return true
	return ns(ReadAllText(fname)) != ns(contents)

def ReadAllText(fname as string):
	using reader=File.OpenText(fname):
		return reader.ReadToEnd()

def WriteAllText(fname as string, contents as string):
	using writer=StreamWriter(fname):
		writer.Write(contents)

def ns(s as string):
"""
Normalize string.
"""
	return s.Trim().Replace("\r\n", Environment.NewLine)

def GenerateTestFixtureSource(srcDir as string, header as string):
	writer=StringWriter()
	writer.Write(header)
	WriteTestCases(writer, srcDir)
	return writer.ToString()

GenerateTestFixture("tests/parser", "src/UnityScript.Tests/ParserTestFixture.Generated.boo", """
namespace UnityScript.Tests

import NUnit.Framework

partial class ParserTestFixture:
""")

GenerateTestFixture("tests/semantics", "src/UnityScript.Tests/SemanticsTestFixture.boo", """
namespace UnityScript.Tests

import NUnit.Framework

[TestFixture]
class SemanticsTestFixture(AbstractSemanticsTestFixture):
""")

GenerateTestFixture("tests/integration", "src/UnityScript.Tests/IntegrationTestFixture.boo", """
namespace UnityScript.Tests

import NUnit.Framework

[TestFixture]
class IntegrationTestFixture(AbstractIntegrationTestFixture):
""")

GenerateTestFixture("tests/pragma", "src/UnityScript.Tests/PragmaTestFixture.boo", """
namespace UnityScript.Tests

import NUnit.Framework

[TestFixture]
class PragmaTestFixture(AbstractIntegrationTestFixture):
""")

GenerateTestFixture("tests/generics", "src/UnityScript.Tests/GenericsTestFixture.Generated.boo", """
namespace UnityScript.Tests

import NUnit.Framework

[TestFixture]
class GenericsTestFixture(AbstractIntegrationTestFixture):
""")

GenerateTestFixture("tests/eval", "src/UnityScript.Tests/EvalTestFixture.boo", """
namespace UnityScript.Tests

import NUnit.Framework

[TestFixture]
class EvalTestFixture(AbstractIntegrationTestFixture):
""")

GenerateTestFixture("tests/expando", "src/UnityScript.Tests/ExpandoTestFixture.boo", """
namespace UnityScript.Tests

import NUnit.Framework

[TestFixture]
class ExpandoTestFixture(AbstractIntegrationTestFixture):

	override def CreateCompiler():
		compiler = super()
		compiler.Parameters.Expando = true
		return compiler
""")

GenerateTestFixture("tests/error-messages", "src/UnityScript.Tests/ErrorMessagesTestFixture.Generated.boo", """
namespace UnityScript.Tests

import NUnit.Framework

partial class ErrorMessagesTestFixture:
""")

GenerateTestFixture("tests/stacktrace", "src/UnityScript.Tests/StackTraceTestFixture.Generated.boo", """
namespace UnityScript.Tests

import NUnit.Framework

partial class StackTraceTestFixture:
""")

#!/usr/bin/env booi
"""
Generates test fixtures from files under tests/
"""
import System
import System.IO

//def MapPath(path):
//	return Path.Combine(Project.BaseDirectory, path)

def GetTestCaseName(fname as string):
	return Path.GetFileNameWithoutExtension(fname).Replace("-", "_")

def WriteTestCases(writer as TextWriter, baseDir as string):
	count = 0
	for fname in Directory.GetFiles(baseDir):
		continue unless fname.EndsWith(".js")
		++count
		ignoreAttribute = IgnoreAttributeFor(fname)
		writer.Write("""
	[Test]${ignoreAttribute}
	def ${GetTestCaseName(fname)}():
		RunTestCase("${fname.Replace('\\', '/')}")
		""")
	print("${count} test cases found in ${baseDir}.")

def IgnoreAttributeFor(testFile as string):
"""
If the first line of the test case file starts with // ignore
then return a suitable [Ignore()] attribute.
"""
	m = /\/\/\s*ignore\s+(.*)/.Match(FirstLineOf(testFile))
	if not m.Success: return string.Empty
	reason = m.Groups[1].Value.Trim()
	return """[Ignore("${reason}")]"""

def FirstLineOf(fname as string):
	using reader=File.OpenText(fname):
		return reader.ReadLine()

def GenerateTestFixture(srcDir as string, targetFile as string, header as string):
	contents = GenerateTestFixtureSource(srcDir, header)
	if ShouldReplaceContent(targetFile, contents):
		WriteAllText(targetFile, contents)

def ShouldReplaceContent(fname as string, contents as string):
	if not File.Exists(fname): return true
	return ns(ReadAllText(fname)) != ns(contents)

def ReadAllText(fname as string):
	using reader=File.OpenText(fname):
		return reader.ReadToEnd()

def WriteAllText(fname as string, contents as string):
	using writer=StreamWriter(fname):
		writer.Write(contents)

def ns(s as string):
"""
Normalize string.
"""
	return s.Trim().Replace("\r\n", Environment.NewLine)

def GenerateTestFixtureSource(srcDir as string, header as string):
	writer=StringWriter()
	writer.Write(header)
	WriteTestCases(writer, srcDir)
	return writer.ToString()

GenerateTestFixture("tests/parser", "src/UnityScript.Tests/ParserTestFixture.Generated.boo", """
namespace UnityScript.Tests

import NUnit.Framework

partial class ParserTestFixture:
""")

GenerateTestFixture("tests/semantics", "src/UnityScript.Tests/SemanticsTestFixture.boo", """
namespace UnityScript.Tests

import NUnit.Framework

[TestFixture]
class SemanticsTestFixture(AbstractSemanticsTestFixture):
""")

GenerateTestFixture("tests/integration", "src/UnityScript.Tests/IntegrationTestFixture.boo", """
namespace UnityScript.Tests

import NUnit.Framework

[TestFixture]
class IntegrationTestFixture(AbstractIntegrationTestFixture):
""")

GenerateTestFixture("tests/pragma", "src/UnityScript.Tests/PragmaTestFixture.boo", """
namespace UnityScript.Tests

import NUnit.Framework

[TestFixture]
class PragmaTestFixture(AbstractIntegrationTestFixture):
""")

GenerateTestFixture("tests/generics", "src/UnityScript.Tests/GenericsTestFixture.Generated.boo", """
namespace UnityScript.Tests

import NUnit.Framework

[TestFixture]
class GenericsTestFixture(AbstractIntegrationTestFixture):
""")

GenerateTestFixture("tests/eval", "src/UnityScript.Tests/EvalTestFixture.boo", """
namespace UnityScript.Tests

import NUnit.Framework

[TestFixture]
class EvalTestFixture(AbstractIntegrationTestFixture):
""")

GenerateTestFixture("tests/expando", "src/UnityScript.Tests/ExpandoTestFixture.boo", """
namespace UnityScript.Tests

import NUnit.Framework

[TestFixture]
class ExpandoTestFixture(AbstractIntegrationTestFixture):

	override def CreateCompiler():
		compiler = super()
		compiler.Parameters.Expando = true
		return compiler
""")

GenerateTestFixture("tests/error-messages", "src/UnityScript.Tests/ErrorMessagesTestFixture.Generated.boo", """
namespace UnityScript.Tests

import NUnit.Framework

partial class ErrorMessagesTestFixture:
""")

GenerateTestFixture("tests/stacktrace", "src/UnityScript.Tests/StackTraceTestFixture.Generated.boo", """
namespace UnityScript.Tests

import NUnit.Framework

partial class StackTraceTestFixture:
""")