work on issues pc bender found, also refactoring and prettier code generated

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

// authors:
// Arron Washington
// Ian MacLean (original C# version)

//See also:
//http://svn.myrealbox.com/viewcvs/trunk/mcs/class/System/Microsoft.CSharp/CSharpCodeGenerator.cs?view=auto
//http://svn.myrealbox.com/viewcvs/trunk/mcs/class/System/Microsoft.VisualBasic/VBCodeGenerator.cs?view=auto

namespace Boo.Lang.CodeDom

import System
import System.CodeDom
import System.CodeDom.Compiler
import System.IO
import System.Reflection
import System.Collections
import System.Text.RegularExpressions

class BooCodeGenerator(CodeGenerator):
	static primitives = { "System.Double" : "double",
						"System.Single" : "single",
						"System.Int32" : "int",
						"System.Int64" : "long",
						"System.Int16" : "short",
						"System.UInt16" : "ushort",
						"System.Boolean" : "bool",
						"System.Char" : "char",
						"System.String" : "string",
						"System.Object" : "object",
						"System.Void" : "void"}

	protected NullToken as string:
		get:
			return "null"

	protected override def OutputType(typeRef as CodeTypeReference):
		Output.Write(GetTypeOutput(typeRef))

	protected override def GenerateArrayCreateExpression(exp as CodeArrayCreateExpression) :
		if exp.Initializers:
			Output.Write("(")
			if exp.CreateType:
				Output.Write("of ")
				OutputType(exp.CreateType)
				Output.Write(": ")
			OutputExpressionList(exp.Initializers)
			//no trailing comma needed for single item with "of" type
			if exp.Initializers.Count==0 or (exp.Initializers.Count==1 and not exp.CreateType):
				Output.Write(",")
			Output.Write(")")
		else:
			Output.Write("array(")
			OutputType(exp.CreateType)
			Output.Write(",")
			if exp.SizeExpression:
				GenerateExpression(exp)
			else:
				Output.Write(exp.Size)
			Output.Write(")")

	protected override def GenerateBaseReferenceExpression(e as CodeBaseReferenceExpression) :
		Output.Write("super")

	protected override def GenerateCastExpression(e as CodeCastExpression) :
		Output.Write("cast(")
		OutputType(e.TargetType)
		Output.Write(",")
		GenerateExpression(e.Expression)
		Output.Write(")")

	protected override def GenerateDelegateCreateExpression(e as CodeDelegateCreateExpression) :
		GenerateExpression(e.TargetObject)
		Output.Write(".${e.MethodName} as ")
		OutputType(e.DelegateType)

	protected override def GenerateFieldReferenceExpression(e as CodeFieldReferenceExpression):
		MemberReference(e.TargetObject, e.FieldName)

	protected override def GenerateArgumentReferenceExpression(e as CodeArgumentReferenceExpression) :
		Output.Write(e.ParameterName)

	protected override def GenerateVariableReferenceExpression(e as CodeVariableReferenceExpression) :
		Output.Write(e.VariableName)

	protected override def GenerateIndexerExpression(e as CodeIndexerExpression) :
		Indexer(e.TargetObject, e.Indices)

	protected override def GenerateArrayIndexerExpression(e as CodeArrayIndexerExpression) :
		Indexer(e.TargetObject, e.Indices)

	protected override def GenerateMethodInvokeExpression(e as CodeMethodInvokeExpression) :
		Invocation(e.Method, e.Parameters)

	protected override def GenerateMethodReferenceExpression(e as CodeMethodReferenceExpression) :
		MemberReference(e.TargetObject, e.MethodName)
	protected override def GenerateEventReferenceExpression(e as CodeEventReferenceExpression) :
		MemberReference(e.TargetObject, e.EventName)

	protected override def GenerateDelegateInvokeExpression(e as CodeDelegateInvokeExpression) :
		Invocation(e.TargetObject, e.Parameters)

	protected override def GenerateObjectCreateExpression(e as CodeObjectCreateExpression) :
		OutputType(e.CreateType)
		Output.Write("(")
		OutputExpressionList(e.Parameters)
		Output.Write(")")

	protected override def GeneratePropertyReferenceExpression(e as CodePropertyReferenceExpression) :
		MemberReference(e.TargetObject, e.PropertyName)

	protected override def GeneratePropertySetValueReferenceExpression(e as CodePropertySetValueReferenceExpression) :
		Output.Write("value")

	protected override def GenerateThisReferenceExpression(e as CodeThisReferenceExpression) :
		Output.Write("self")

	protected override def GenerateExpressionStatement(e as CodeExpressionStatement) :
		GenerateExpression(e.Expression)
		Output.WriteLine()

	protected override def GenerateIterationStatement(e as CodeIterationStatement):
		if e.InitStatement:
			GenerateStatement(e.InitStatement)
		Output.Write("while ")
		GenerateExpression(e.TestExpression)
		BeginBlock()
		if e.Statements and e.Statements.IsValid():
			GenerateStatements(e.Statements)
		if e.IncrementStatement:
			GenerateStatement(e.IncrementStatement)
		else:
			passcheck(e.Statements)
		EndBlock()

	protected override def GenerateThrowExceptionStatement(e as CodeThrowExceptionStatement) :
		Output.Write("raise ")
		GenerateExpression(e.ToThrow)
		Output.WriteLine()

	protected override def GenerateComment(comment as CodeComment) :
		//TODO: check comment.DocComment when boo doc format is available
		commentSymbol = "#"
		txt = comment.Text

		Output.Write(commentSymbol + " ")

		i = 0
		while i<len(txt):
			rawArrayIndexing:
				Output.Write(txt[i])
				if txt[i] == char('\r'):
					if i<len(txt)-1 and txt[i+1]==char('\n'):
						continue
					Output.Write(commentSymbol)
				elif txt[i] == char('\n'):
					Output.Write(commentSymbol)
			++i
		Output.WriteLine()

	protected override def GenerateMethodReturnStatement(e as CodeMethodReturnStatement) :
		Output.Write("return ")
		GenerateExpression(e.Expression)
		Output.WriteLine()

	protected override def GenerateConditionStatement(e as CodeConditionStatement) :
		Output.Write("if ")
		GenerateExpression(e.Condition)
		BeginBlock()
		GenerateStatements(e.TrueStatements)
		passcheck(e.TrueStatements)
		EndBlock(false)
		if e.FalseStatements and e.FalseStatements.IsValid():
			Output.Write("else")
			BeginBlock()
			GenerateStatements(e.FalseStatements)
			passcheck(e.FalseStatements)
			EndBlock(false)
		End()

	protected override def GenerateTryCatchFinallyStatement(e as CodeTryCatchFinallyStatement) :
		Output.Write("try")
		BeginBlock()
		GenerateStatements(e.TryStatements)
		passcheck(e.TryStatements)
		EndBlock(false)
		for exp as CodeCatchClause in e.CatchClauses:
			Output.Write("except ")
			Output.Write("${exp.LocalName} as ")
			OutputType(exp.CatchExceptionType)
			BeginBlock()
			GenerateStatements(exp.Statements)
			passcheck(exp.Statements)
			EndBlock(false)

		if e.FinallyStatements and e.FinallyStatements.Count > 0:
			Output.Write("ensure")
			BeginBlock()
			GenerateStatements(e.FinallyStatements)
			passcheck(e.FinallyStatements)
			EndBlock(false)
		End()

	protected override def GenerateAssignStatement(e as CodeAssignStatement) :
		GenerateExpression(e.Left)
		Output.Write(" = ")
		GenerateExpression(e.Right)
		Output.WriteLine()
	protected override def GenerateAttachEventStatement(e as CodeAttachEventStatement) :
		GenerateExpression(e.Event)
		Output.Write(" += ")
		GenerateExpression(e.Listener)
		Output.WriteLine()
	protected override def GenerateRemoveEventStatement(e as CodeRemoveEventStatement) :
		GenerateExpression(e.Event)
		Output.Write(" -= ")
		GenerateExpression(e.Listener)
		Output.WriteLine()
	protected override def GenerateGotoStatement(e as CodeGotoStatement) :
		Output.WriteLine("goto ${e.Label}")
	protected override def GenerateLabeledStatement(e as CodeLabeledStatement) :
		Output.WriteLine(":${e.Label}")
		if e.Statement:
			GenerateStatement(e.Statement)
	protected override def GenerateVariableDeclarationStatement(e as CodeVariableDeclarationStatement) :
		Output.Write("${e.Name} as ")
		OutputType(e.Type)
		if e.InitExpression:
			Output.Write(" = ")
			GenerateExpression(e.InitExpression)
		Output.WriteLine()
	protected override def GenerateLinePragmaStart(e as CodeLinePragma) :
		pass

	protected override def GenerateLinePragmaEnd(e as CodeLinePragma) :
		pass

	protected override def GenerateEvent(e as CodeMemberEvent, c as CodeTypeDeclaration) :

		if e.PrivateImplementationType:
			raise "Does not support private implementation type."
		if e.ImplementationTypes.IsValid():
			raise "Does not implement these types: ${join(e.ImplementationTypes)}"
		e.Attributes |= MemberAttributes.Final

		ModifiersAndAttributes(e)

		Output.Write("event ${e.Name} as ")
		OutputType(e.Type)
		Output.WriteLine()
	protected override def GenerateField(e as CodeMemberField) :

		ModifiersAndAttributes(e)
		Output.Write("${e.Name} as ")
		OutputType(e.Type)
		if e.InitExpression:
			Output.Write(" = ")
			GenerateExpression(e.InitExpression)
		Output.WriteLine()

	protected override def GenerateSnippetMember(e as CodeSnippetTypeMember) :
		if e.CustomAttributes:
			OutputAttributes(e.CustomAttributes, null, false)
		Output.WriteLine(FixIndent(e.Text, Options.IndentString, Indent, false))

	protected override def GenerateSnippetExpression(e as CodeSnippetExpression) :
		//Output.Write("("+e.Value+")")
		Output.Write(e.Value)

	protected override def GenerateSnippetCompileUnit(e as CodeSnippetCompileUnit) :
		Output.WriteLine(FixIndent(e.Value, Options.IndentString, Indent, false))

	protected override def GenerateSnippetStatement(e as CodeSnippetStatement) :
		Output.WriteLine(FixIndent(e.Value, Options.IndentString, Indent, false))

	protected override def GenerateEntryPointMethod(e as CodeEntryPointMethod, c as CodeTypeDeclaration) :
		Method(e, "Main")

	protected override def GenerateMethod(e as CodeMemberMethod, c as CodeTypeDeclaration) :
		Method(e, e.Name)

	protected override def GenerateProperty(e as CodeMemberProperty, c as CodeTypeDeclaration) :
		ModifiersAndAttributes(e)

		Output.Write(" ${e.Name}")
		if len(e.Parameters) > 0:
			Output.Write("(")
			OutputParameters(e.Parameters)
			Output.Write(")")
		Output.Write(" as ")
		OutputType(e.Type)
		BeginBlock()
		if e.HasGet:
			Output.Write("get")
			BeginBlock()
			GenerateStatements(e.GetStatements)
			passcheck(e.GetStatements)
			EndBlock() //TODO: Change to EndBlock(false) when BOO-631 accepted
		if e.HasSet:
			Output.Write("set")
			BeginBlock()
			GenerateStatements(e.SetStatements)
			passcheck(e.SetStatements)
			EndBlock() //TODO: Change to EndBlock(false) when BOO-631 accepted
		EndBlock()

	protected override def GenerateConstructor(e as CodeConstructor, c as CodeTypeDeclaration) :
		Method(e, "constructor")

	protected override def GenerateTypeConstructor(e as CodeTypeConstructor) :
		e.Attributes |= MemberAttributes.Static
		e.Parameters.Clear()
		Method(e, "constructor")

	protected override def GenerateTypeStart(e as CodeTypeDeclaration):
		if e.CustomAttributes:
			OutputAttributes(e.CustomAttributes, null, false)
		if e isa CodeTypeDelegate:
			GenerateDelegate(e)
			return

		//TODO: move this to an override of OutputTypeAttributes

		//FIXME: When boo gets preprocessor directives, i.e. #if NET_2_0
		//Til then we have to partially rely on super.OutputTypeAttributes

		//output "final", since super.OutputTypeAttributes outputs "sealed"
		if e.TypeAttributes & TypeAttributes.Sealed == TypeAttributes.Sealed:
			Output.Write("final ")

			//remove sealed from attributes:

			//FIXME: when boo enum processing fixed
			//e.TypeAttributes &= ~TypeAttributes.Sealed
			e.TypeAttributes &= cast(TypeAttributes,~cast(int,TypeAttributes.Sealed))

		visibility = e.TypeAttributes & TypeAttributes.VisibilityMask
		if visibility == TypeAttributes.Public or visibility == TypeAttributes.NestedPublic:
			pass //Output.Write("public ") //public by default
		elif visibility == TypeAttributes.NestedPrivate:
			Output.Write("private ")
		elif (visibility == TypeAttributes.NotPublic or
			TypeAttributes.NestedFamANDAssem or
			TypeAttributes.NestedAssembly):
			Output.Write("internal ")
		elif visibility == TypeAttributes.NestedFamily:
			//FIXME: BOO-666
			Output.Write("protected ")
		elif visibility == TypeAttributes.NestedFamORAssem:
			//FIXME: BOO-666
			Output.Write("internal ")

		//super OutputTypeAttributes ignores TypeAttributes.NotPublic (internal)
		//FIXME: when boo enum processing fixed
		//e.TypeAttributes &= ~TypeAttributes.VisibilityMask
		e.TypeAttributes &= cast(TypeAttributes,~cast(int,TypeAttributes.VisibilityMask))
		e.TypeAttributes |= TypeAttributes.NotPublic //not really nec. since val is 0

		//TODO: Remove call to OutputTypeAttributes
		OutputTypeAttributes(e.TypeAttributes, e.IsStruct, e.IsEnum)
		/////////////////////////////////////

		Output.Write("${e.Name}(")
		for index in range(len(e.BaseTypes)):
			var as CodeTypeReference = e.BaseTypes[index]
			OutputType(var)
			continue unless index + 1 != e.BaseTypes.Count
			Output.Write(",")
		Output.Write(")")
		BeginBlock()
		#Ah hah hah, yeah, no. Empty class.
		passcheck(e.Members)

	private def GenerateDelegate(e as CodeTypeDelegate):
		if e.TypeAttributes & TypeAttributes.Sealed == TypeAttributes.Sealed:
			Output.Write("sealed ")
		if e.TypeAttributes & TypeAttributes.NestedFamORAssem == TypeAttributes.Public:
			Output.Write("public ")
		Output.Write("callable ${e.Name}(")
		OutputParameters(e.Parameters)
		Output.Write(") as ")
		OutputType(e.ReturnType)

	protected override def GenerateTypeEnd(e as CodeTypeDeclaration) :
		EndBlock()

	protected override def GenerateNamespaceStart(e as CodeNamespace) :
		if e and e.Name and e.Name != string.Empty:
			Output.WriteLine("namespace ${e.Name}")

	protected override def GenerateNamespaceEnd(e as CodeNamespace) :
		pass

	protected override def GenerateNamespaceImport(e as CodeNamespaceImport) :
		Output.WriteLine("import ${e.Namespace}")

	protected override def GenerateAttributeDeclarationsStart(attributes as CodeAttributeDeclarationCollection) :
		Output.Write("[")

	protected override def GenerateAttributeDeclarationsEnd(attributes as CodeAttributeDeclarationCollection) :
		Output.Write("]")

	protected override def Supports(support as GeneratorSupport) as bool:
		return true

	protected override def IsValidIdentifier(value as string) as bool:
		return true

	protected override def CreateEscapedIdentifier(value as string) as string:
		return value

	protected override def CreateValidIdentifier(value as string) as string:
		return value

	protected override def GetTypeOutput(typeRef as CodeTypeReference):
		if typeRef.ArrayElementType:
			out = GetTypeOutput(typeRef.ArrayElementType)
		elif typeRef.BaseType in primitives:
			out = primitives[typeRef.BaseType]
		else:
			out = typeRef.BaseType

		if typeRef.ArrayRank > 0:
			if typeRef.ArrayRank > 1:
				return "(${out}, ${typeRef.ArrayRank})"
			return "(${out})"
		return out

	protected override def QuoteSnippetString(snippet as string) as string:
		s = snippet.Replace("\\", "\\\\")
		s = s.Replace("\"", "\\\"")
		s = s.Replace("\t", "\\t")
		s = s.Replace("\r", "\\r")
		s = s.Replace("\n", "\\n")
		return "\"" + s + "\""

	def Method(method as duck, name as string):
		ModifiersAndAttributes(method)
		Output.Write("def ${name}(")
		OutputParameters(method.Parameters)
		Output.Write(")")
		unless GetTypeOutput(method.ReturnType) == "void":
			Output.Write(" as ")
			OutputType(method.ReturnType)
		if method.ReturnTypeCustomAttributes:
			OutputAttributes(method.ReturnTypeCustomAttributes, null, true)
		BeginBlock()
		ctor = method as CodeConstructor
		do_pass = true
		if ctor:
			if ctor.BaseConstructorArgs.IsValid():
				Output.Write("super(")
				OutputExpressionList(ctor.BaseConstructorArgs)
				Output.WriteLine(")")
				do_pass = false
			if ctor.ChainedConstructorArgs.IsValid():
				Output.Write("self(")
				OutputExpressionList(ctor.ChainedConstructorArgs)
				Output.WriteLine(")")
				do_pass = false
		GenerateStatements(method.Statements)
		if do_pass:
			passcheck(method.Statements)
		EndBlock()

	def MemberReference(target as CodeExpression, member as string):
		GenerateExpression(target)
		Output.Write(".${member}")

	def Indexer(target as CodeExpression, indices as CodeExpressionCollection):
		GenerateExpression(target)
		Output.Write("[")
		OutputExpressionList(indices)
		Output.Write("]")

	def Invocation(target as CodeExpression, parameters as CodeExpressionCollection):
		GenerateExpression(target)
		Output.Write("(")
		OutputExpressionList(parameters)
		Output.Write(")")

	def passcheck(stuff as ICollection):
		return if stuff.IsValid()
		if not AsBool(Options["WhiteSpaceAgnostic"]):
			Output.Write("pass")

	override def OutputTypeNamePair(type as CodeTypeReference, name as string):
		Output.Write("${name} as ")
		OutputType(type)

	protected override def GenerateParameterDeclarationExpression(e as CodeParameterDeclarationExpression):
		OutputAttributes(e.CustomAttributes, null, true)
		OutputDirection(e.Direction)
		OutputTypeNamePair(e.Type, e.Name)

	private def OutputAttributes(attributes as CodeAttributeDeclarationCollection, prefix as string, inline as bool):
		for att as CodeAttributeDeclaration in attributes:
			GenerateAttributeDeclarationsStart(attributes)
			Output.Write(prefix) if prefix
			OutputAttributeDeclaration(att)
			GenerateAttributeDeclarationsEnd(attributes)
			if inline:  //for parameter and return type attributes
				pass //Output.Write(" ")
			else:
				Output.WriteLine()

	private def OutputAttributeDeclaration(attribute as CodeAttributeDeclaration):
		Output.Write(attribute.Name.Replace ('+', '.'))
		Output.Write('(')
		first = true
		for argument as CodeAttributeArgument in attribute.Arguments:
			if first:
				OutputAttributeArgument(argument)
				first = false
			else:
				Output.Write(", ")
				OutputAttributeArgument(argument)
		Output.Write(')')

	def ModifiersAndAttributes(typeMember as CodeTypeMember):
		if typeMember.CustomAttributes:
			OutputAttributes(typeMember.CustomAttributes, null, false)

		//FIXME: We just ignore "new" methods, since they are unsupported in boo
		if typeMember.Attributes & MemberAttributes.VTableMask == MemberAttributes.New:
			typeMember.Attributes &= cast(MemberAttributes,~cast(int,MemberAttributes.New))

		//TODO: Override these.  Right now we are lucky C# and boo are the same
		//with respect to keywords and defaults (such as final/sealed by default).
		//We should override for example to not print out "public" since it is boo default.
		OutputMemberAccessModifier(typeMember.Attributes)
		OutputMemberScopeModifier(typeMember.Attributes)

	def OutputAttributeArgument(arg as CodeAttributeArgument):
		if arg.Name and arg.Name != "":
			Output.Write(arg.Name)
			Output.Write(":")
		GenerateExpression(arg.Value)

	protected override def OutputDirection(direction as FieldDirection):
		if direction == FieldDirection.Ref or direction == FieldDirection.Out:
			Output.Write("ref ")

	protected override def OutputFieldScopeModifier(attributes as MemberAttributes):
		scope = attributes & MemberAttributes.ScopeMask
		if scope == MemberAttributes.Static:
			Output.Write("static ")
		elif scope == MemberAttributes.Const:
			Output.Write("static final ")

	private def AsBool(data):
		return data is not null and data isa bool and cast(bool,data)

	protected override def GenerateCompileUnitStart(compileUnit as CodeCompileUnit):
		GenerateComment(CodeComment("------------------------------------------------------------------------------"))
		GenerateComment(CodeComment("This code was automatically generated by Boo.Lang.CodeDom v.${BooVersion}."))
		GenerateComment(CodeComment(""))
		GenerateComment(CodeComment("     Changes to this file may cause incorrect behavior and will be lost if "))
		GenerateComment(CodeComment("     the code is regenerated."))
		GenerateComment(CodeComment("------------------------------------------------------------------------------"))
		Output.WriteLine()

		//for future:
		if AsBool(Options["WhiteSpaceAgnostic"]):
			Output.WriteLine("//option WhiteSpaceAgnostic")
			Output.WriteLine()

		super.GenerateCompileUnitStart(compileUnit)

	protected override def GenerateCompileUnit(compileUnit as CodeCompileUnit):
		GenerateCompileUnitStart(compileUnit)

		for ns as CodeNamespace in compileUnit.Namespaces:
			GenerateNamespace(ns)

		if compileUnit.AssemblyCustomAttributes.Count > 0:
			OutputAttributes(compileUnit.AssemblyCustomAttributes,
					"assembly:", false)
			Output.WriteLine("")

		GenerateCompileUnitEnd(compileUnit)

	protected virtual def BeginBlock():
		Output.WriteLine(":")
		++Indent

	protected virtual def EndBlock():
		EndBlock(true)

	protected virtual def EndBlock(realend as bool):
		--Indent
		if realend:
			End()

	protected virtual def End():
		if AsBool(Options["WhiteSpaceAgnostic"]):
			Output.WriteLine("end")

#region FixIndent
	static newlinePattern = Regex("\\n",RegexOptions.Compiled)
	static whiteSpacePattern = Regex("(\\s*)",RegexOptions.Compiled)

	static public def FixIndent(code as string, indentstring as string,
				indentlevel as int, indentfirst as bool) as string:
		//how much the code should be indented:
		indentprefix = string.Empty
		indentprefix = indentstring * indentlevel if indentlevel > 0
		nonEmptyPrefix = (indentprefix != string.Empty)

		if code is null or code==string.Empty:
			if indentfirst:
				return indentprefix
			else:
				return string.Empty

		lines = newlinePattern.Split(code.Replace("\r\n","\n"))
		//or: lines = code.Split(System.Environment.NewLine.ToCharArray())

		foundFirstCodeLine = false //first line of real code
		insidecomment = 0 //inside /* */
		insidestring = false  //inside """ """
		firstline = true
		buffer = System.Text.StringBuilder()
		indent = ""
		indentSize = 0
		toTabs = indentprefix.StartsWith("\t") //otherwise, assume spaces
		for line in lines:
			//ignore comments before the first line of real code
			if not foundFirstCodeLine:
				trimmed = line.Trim()
				startmultiline = trimmed.StartsWith("/*")

				//if not whitespace and not a comment...
				if (insidecomment <= 0 and
						not startmultiline and
						not trimmed == string.Empty and
						not trimmed.StartsWith("#") and
						not trimmed.StartsWith("//")):
					foundFirstCodeLine = true
					indent = whiteSpacePattern.Match(line).Groups[0].Value
					indentSize = len(indent)
					insidecomment = 0

				//special stuff for handling multiline comments before code
				if startmultiline or insidecomment > 0:
					j = 0
					linelen = len(trimmed) - 1
					while j < linelen:
						rawArrayIndexing:
							c = trimmed[j]
						if c==char('/'):
							rawArrayIndexing:
								nextc = trimmed[j+1]
							if nextc==char('*'):
								++insidecomment
								++j
						elif c==char('*') and insidecomment > 0:
							rawArrayIndexing:
								nextc = trimmed[j+1]
							if nextc==char('/'):
								--insidecomment
								++j
						++j

			if not foundFirstCodeLine or not insidestring:
				if firstline:
					firstline = false
					if indentfirst and nonEmptyPrefix:
						buffer.Append(indentprefix)
				else:
					if nonEmptyPrefix:
						buffer.Append(indentprefix)

				if not foundFirstCodeLine:
					buffer.Append(line + System.Environment.NewLine)
					continue

				if line.StartsWith(indent):
					if indentSize > 0:
						rest = line[indentSize:]
					else:
						rest = line
					if len(rest) > 0:
						//convert any more leading whitespace to tabs or spaces
						//so we don't mix tabs and spaces on same line
						ws = whiteSpacePattern.Match(rest).Groups[0].Value
						sz = len(ws)
						if len(ws):
							rawArrayIndexing:
								c = rest[0]
							if toTabs:
								if c==char(' '):
									rest = rest[sz:]
									ws = ws.Replace("    ","\t")
									ws = ws.Replace("   ","\t")
									ws = ws.Replace("  ","\t")
									ws = ws.Replace(" ","\t")
									buffer.Append(ws)
							else:
								if c==char('\t'):
									rest = rest[sz:]
									ws = ws.Replace("\t","    ")
									buffer.Append(ws)
						buffer.Append(rest)
				else: //line is not indented as expected, pass it on as is:
					buffer.Append(line)
			else: //we are inside a triple quoted string:
				buffer.Append(line)

			buffer.Append(System.Environment.NewLine)

			//scan for triple quoted string literals:
			//   for speed reasons, I'm not also checking for insidecomment,
			//   so if you have an odd # of triple quotes inside a comment,
			//   you can break this.
			j = 0
			linelen = len(line) - 2
			while j < linelen:
				rawArrayIndexing:
					c1 = line[j]
				if c1==char('"'):
					rawArrayIndexing:
						c2 = line[j+1]
					if c2==char('"'):
						rawArrayIndexing:
							c3 = line[j+2]
						if c3==char('"'):
							insidestring = not insidestring
							j += 2
				++j

		return buffer.ToString()
#endregion

#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

// authors:
// Arron Washington
// Ian MacLean (original C# version)

//See also:
//http://svn.myrealbox.com/viewcvs/trunk/mcs/class/System/Microsoft.CSharp/CSharpCodeGenerator.cs?view=auto
//http://svn.myrealbox.com/viewcvs/trunk/mcs/class/System/Microsoft.VisualBasic/VBCodeGenerator.cs?view=auto

namespace Boo.Lang.CodeDom

import System
import System.CodeDom
import System.CodeDom.Compiler
import System.IO
import System.Reflection
import System.Collections
import System.Text.RegularExpressions

class BooCodeGenerator(CodeGenerator):
	static primitives = { "System.Double" : "double",
						"System.Single" : "single",
						"System.Int32" : "int",
						"System.Int64" : "long",
						"System.Int16" : "short",
						"System.UInt16" : "ushort",
						"System.Boolean" : "bool",
						"System.Char" : "char",
						"System.String" : "string",
						"System.Object" : "object",
						"System.Void" : "void"}

	protected NullToken as string:
		get:
			return "null"

	protected override def OutputType(typeRef as CodeTypeReference):
		Output.Write(GetTypeOutput(typeRef))

	protected override def GenerateArrayCreateExpression(exp as CodeArrayCreateExpression) :
		if exp.Initializers:
			Output.Write("(")
			if exp.CreateType:
				Output.Write("of ")
				OutputType(exp.CreateType)
				Output.Write(": ")
			OutputExpressionList(exp.Initializers)
			//no trailing comma needed for single item with "of" type
			if exp.Initializers.Count==0 or (exp.Initializers.Count==1 and not exp.CreateType):
				Output.Write(",")
			Output.Write(")")
		else:
			Output.Write("array(")
			OutputType(exp.CreateType)
			Output.Write(",")
			if exp.SizeExpression:
				GenerateExpression(exp)
			else:
				Output.Write(exp.Size)
			Output.Write(")")

	protected override def GenerateBaseReferenceExpression(e as CodeBaseReferenceExpression) :
		Output.Write("super")

	protected override def GenerateCastExpression(e as CodeCastExpression) :
		Output.Write("cast(")
		OutputType(e.TargetType)
		Output.Write(",")
		GenerateExpression(e.Expression)
		Output.Write(")")

	protected override def GenerateDelegateCreateExpression(e as CodeDelegateCreateExpression) :
		GenerateExpression(e.TargetObject)
		Output.Write(".${e.MethodName} as ")
		OutputType(e.DelegateType)

	protected override def GenerateFieldReferenceExpression(e as CodeFieldReferenceExpression):
		MemberReference(e.TargetObject, e.FieldName)

	protected override def GenerateArgumentReferenceExpression(e as CodeArgumentReferenceExpression) :
		Output.Write(e.ParameterName)

	protected override def GenerateVariableReferenceExpression(e as CodeVariableReferenceExpression) :
		Output.Write(e.VariableName)

	protected override def GenerateIndexerExpression(e as CodeIndexerExpression) :
		Indexer(e.TargetObject, e.Indices)

	protected override def GenerateArrayIndexerExpression(e as CodeArrayIndexerExpression) :
		Indexer(e.TargetObject, e.Indices)

	protected override def GenerateMethodInvokeExpression(e as CodeMethodInvokeExpression) :
		Invocation(e.Method, e.Parameters)

	protected override def GenerateMethodReferenceExpression(e as CodeMethodReferenceExpression) :
		MemberReference(e.TargetObject, e.MethodName)
	protected override def GenerateEventReferenceExpression(e as CodeEventReferenceExpression) :
		MemberReference(e.TargetObject, e.EventName)

	protected override def GenerateDelegateInvokeExpression(e as CodeDelegateInvokeExpression) :
		Invocation(e.TargetObject, e.Parameters)

	protected override def GenerateObjectCreateExpression(e as CodeObjectCreateExpression) :
		OutputType(e.CreateType)
		Output.Write("(")
		OutputExpressionList(e.Parameters)
		Output.Write(")")

	protected override def GeneratePropertyReferenceExpression(e as CodePropertyReferenceExpression) :
		MemberReference(e.TargetObject, e.PropertyName)

	protected override def GeneratePropertySetValueReferenceExpression(e as CodePropertySetValueReferenceExpression) :
		Output.Write("value")

	protected override def GenerateThisReferenceExpression(e as CodeThisReferenceExpression) :
		Output.Write("self")

	protected override def GenerateExpressionStatement(e as CodeExpressionStatement) :
		GenerateExpression(e.Expression)
		Output.WriteLine()

	protected override def GenerateIterationStatement(e as CodeIterationStatement):
		if e.InitStatement:
			GenerateStatement(e.InitStatement)
		Output.Write("while ")
		GenerateExpression(e.TestExpression)
		Output.WriteLine(":")
		Indent++
		if e.Statements and e.Statements.IsValid():
			GenerateStatements(e.Statements)
		if e.IncrementStatement:
			GenerateStatement(e.IncrementStatement)
		else:
			passcheck(e.Statements)
		Indent--
		EndBlock()

	protected override def GenerateThrowExceptionStatement(e as CodeThrowExceptionStatement) :
		Output.Write("raise ")
		GenerateExpression(e.ToThrow)
		Output.WriteLine()

	protected override def GenerateComment(comment as CodeComment) :
		//TODO: check comment.DocComment when boo doc format is available
		commentSymbol = "#"
		txt = comment.Text

		Output.Write(commentSymbol + " ")

		i = 0
		while i<len(txt):
			rawArrayIndexing:
				Output.Write(txt[i])
				if txt[i] == char('\r'):
					if i<len(txt)-1 and txt[i+1]==char('\n'):
						continue
					Output.Write(commentSymbol)
				elif txt[i] == char('\n'):
					Output.Write(commentSymbol)
			++i
		Output.WriteLine()

	protected override def GenerateMethodReturnStatement(e as CodeMethodReturnStatement) :
		Output.Write("return ")
		GenerateExpression(e.Expression)
		Output.WriteLine()

	protected override def GenerateConditionStatement(e as CodeConditionStatement) :
		Output.Write("if ")
		GenerateExpression(e.Condition)
		Output.WriteLine(":")
		Indent++
		GenerateStatements(e.TrueStatements)
		passcheck(e.TrueStatements)
		Indent--
		if e.FalseStatements and e.FalseStatements.IsValid():
			Output.WriteLine("else:")
			Indent++
			GenerateStatements(e.FalseStatements)
			passcheck(e.FalseStatements)
			Indent--
		EndBlock()

	protected override def GenerateTryCatchFinallyStatement(e as CodeTryCatchFinallyStatement) :
		Output.Write("try:")
		Indent++
		GenerateStatements(e.TryStatements)
		passcheck(e.TryStatements)
		Indent--
		for exp as CodeCatchClause in e.CatchClauses:
			Output.Write("except ")
			Output.Write("${exp.LocalName} as ")
			OutputType(exp.CatchExceptionType)
			Output.WriteLine(":")
			Indent++
			GenerateStatements(exp.Statements)
			passcheck(exp.Statements)
			Indent--

		if e.FinallyStatements and e.FinallyStatements.Count > 0:
			Output.WriteLine("ensure:")
			Indent++
			GenerateStatements(e.FinallyStatements)
			passcheck(e.FinallyStatements)
			Indent--
		EndBlock()

	protected override def GenerateAssignStatement(e as CodeAssignStatement) :
		GenerateExpression(e.Left)
		Output.Write(" = ")
		GenerateExpression(e.Right)
		Output.WriteLine()
	protected override def GenerateAttachEventStatement(e as CodeAttachEventStatement) :
		GenerateExpression(e.Event)
		Output.Write(" += ")
		GenerateExpression(e.Listener)
		Output.WriteLine()
	protected override def GenerateRemoveEventStatement(e as CodeRemoveEventStatement) :
		GenerateExpression(e.Event)
		Output.Write(" -= ")
		GenerateExpression(e.Listener)
		Output.WriteLine()
	protected override def GenerateGotoStatement(e as CodeGotoStatement) :
		Output.WriteLine("goto ${e.Label}")
	protected override def GenerateLabeledStatement(e as CodeLabeledStatement) :
		Output.WriteLine(":${e.Label}")
		if e.Statement:
			GenerateStatement(e.Statement)
	protected override def GenerateVariableDeclarationStatement(e as CodeVariableDeclarationStatement) :
		Output.Write("${e.Name} as ")
		OutputType(e.Type)
		if e.InitExpression:
			Output.Write(" = ")
			GenerateExpression(e.InitExpression)
		Output.WriteLine()
	protected override def GenerateLinePragmaStart(e as CodeLinePragma) :
		pass

	protected override def GenerateLinePragmaEnd(e as CodeLinePragma) :
		pass

	protected override def GenerateEvent(e as CodeMemberEvent, c as CodeTypeDeclaration) :

		if e.PrivateImplementationType:
			raise "Does not support private implementation type."
		if e.ImplementationTypes.IsValid():
			raise "Does not implement these types: ${join(e.ImplementationTypes)}"
		e.Attributes |= MemberAttributes.Final

		ModifiersAndAttributes(e)

		Output.Write("event ${e.Name} as ")
		OutputType(e.Type)
		Output.WriteLine()
	protected override def GenerateField(e as CodeMemberField) :

		ModifiersAndAttributes(e)
		Output.Write("${e.Name} as ")
		OutputType(e.Type)
		if e.InitExpression:
			Output.Write(" = ")
			GenerateExpression(e.InitExpression)
		Output.WriteLine()

	protected override def GenerateSnippetMember(e as CodeSnippetTypeMember) :
		OutputAttributeDeclarations(e.CustomAttributes) if e.CustomAttributes
		Output.WriteLine(FixIndent(e.Text, Options.IndentString, Indent, false))

	protected override def GenerateSnippetExpression(e as CodeSnippetExpression) :
		//Output.Write("("+e.Value+")")
		Output.Write(e.Value)

	protected override def GenerateSnippetCompileUnit(e as CodeSnippetCompileUnit) :
		Output.WriteLine(FixIndent(e.Value, Options.IndentString, Indent, false))

	protected override def GenerateSnippetStatement(e as CodeSnippetStatement) :
		Output.WriteLine(FixIndent(e.Value, Options.IndentString, Indent, false))

	protected override def GenerateEntryPointMethod(e as CodeEntryPointMethod, c as CodeTypeDeclaration) :
		Method(e, "Main")

	protected override def GenerateMethod(e as CodeMemberMethod, c as CodeTypeDeclaration) :
		Method(e, e.Name)

	protected override def GenerateProperty(e as CodeMemberProperty, c as CodeTypeDeclaration) :
		ModifiersAndAttributes(e)

		Output.Write(" ${e.Name}")
		if len(e.Parameters) > 0:
			Output.Write("(")
			OutputParameters(e.Parameters)
			Output.Write(")")
		Output.Write(" as ")
		OutputType(e.Type)
		Output.WriteLine(":")
		Indent++
		if e.HasGet:
			Output.WriteLine("get:")
			Indent++
			GenerateStatements(e.GetStatements)
			passcheck(e.GetStatements)
			Indent--
			EndBlock() //TODO: Remove when BOO-631 accepted
		if e.HasSet:
			Output.WriteLine("set:")
			Indent++
			GenerateStatements(e.SetStatements)
			passcheck(e.SetStatements)
			Indent--
			EndBlock() //TODO: Remove when BOO-631 accepted
		Indent--
		EndBlock()

	protected override def GenerateConstructor(e as CodeConstructor, c as CodeTypeDeclaration) :
		Method(e, "constructor")

	protected override def GenerateTypeConstructor(e as CodeTypeConstructor) :
		e.Attributes |= MemberAttributes.Static
		e.Parameters.Clear()
		Method(e, "constructor")

	protected override def GenerateTypeStart(e as CodeTypeDeclaration):
		Output.WriteLine()
		OutputAttributeDeclarations(e.CustomAttributes)
		Output.WriteLine()
		if e.TypeAttributes & TypeAttributes.Sealed:
			e.TypeAttributes = (e.TypeAttributes.ToInt() & ~TypeAttributes.Sealed.ToInt()).ToEnum(TypeAttributes)
			Output.Write("final ")
		if e isa CodeTypeDelegate:
			GenerateDelegate(e)
			return
		OutputTypeAttributes(e.TypeAttributes, e.IsStruct, e.IsEnum)
		Output.Write(" ${e.Name}(")
		for index in range(len(e.BaseTypes)):
			var as CodeTypeReference = e.BaseTypes[index]
			OutputType(var)
			continue unless index + 1 != e.BaseTypes.Count
			Output.Write(",")
		Output.Write("):")
		Indent++
		#Ah hah hah, yeah, no. Empty class.
		passcheck(e.Members)

	private def GenerateDelegate(e as CodeTypeDelegate):
		if e.TypeAttributes & TypeAttributes.NestedFamORAssem == TypeAttributes.Public:
			Output.Write("public ")
		Output.Write("callable ${e.Name}(")
		OutputParameters(e.Parameters)
		Output.Write(") as ")
		OutputType(e.ReturnType)

	protected override def GenerateTypeEnd(e as CodeTypeDeclaration) :
		Indent--
		EndBlock()

	protected override def GenerateNamespaceStart(e as CodeNamespace) :
		if e and e.Name and e.Name != string.Empty:
			Output.WriteLine("namespace ${e.Name}")

	protected override def GenerateNamespaceEnd(e as CodeNamespace) :
		pass

	protected override def GenerateNamespaceImport(e as CodeNamespaceImport) :
		Output.WriteLine("import ${e.Namespace}")

	protected override def GenerateAttributeDeclarationsStart(attributes as CodeAttributeDeclarationCollection) :
		Output.Write("[")

	protected override def GenerateAttributeDeclarationsEnd(attributes as CodeAttributeDeclarationCollection) :
		Output.Write("]")

	protected override def Supports(support as GeneratorSupport) as bool:
		return true

	protected override def IsValidIdentifier(value as string) as bool:
		return true

	protected override def CreateEscapedIdentifier(value as string) as string:
		return value

	protected override def CreateValidIdentifier(value as string) as string:
		return value

	protected override def GetTypeOutput(typeRef as CodeTypeReference):
		if typeRef.ArrayElementType:
			out = GetTypeOutput(typeRef.ArrayElementType)
		elif typeRef.BaseType in primitives:
			out = primitives[typeRef.BaseType]
		else:
			out = typeRef.BaseType

		if typeRef.ArrayRank > 0:
			if typeRef.ArrayRank > 1:
				return "(${out}, ${typeRef.ArrayRank})"
			return "(${out})"
		return out

	protected override def QuoteSnippetString(snippet as string) as string:
		s = snippet.Replace("\\", "\\\\")
		s = s.Replace("\"", "\\\"")
		s = s.Replace("\t", "\\t")
		s = s.Replace("\r", "\\r")
		s = s.Replace("\n", "\\n")
		return "\"" + s + "\""

	def Method(method as duck, name as string):
		ModifiersAndAttributes(method)
		Output.Write("def ${name}(")
		OutputParameters(method.Parameters)
		Output.Write(")")
		unless GetTypeOutput(method.ReturnType) == "void":
			Output.Write(" as ")
			OutputType(method.ReturnType)
			OutputAttributeDeclarations(method.ReturnTypeCustomAttributes) if method.ReturnTypeCustomAttributes
		Output.WriteLine(":")
		Indent++
		ctor = method as CodeConstructor
		do_pass = true
		if ctor:
			if ctor.BaseConstructorArgs.IsValid():
				Output.Write("super(")
				OutputExpressionList(ctor.BaseConstructorArgs)
				Output.WriteLine(")")
				do_pass = false
			if ctor.ChainedConstructorArgs.IsValid():
				Output.Write("self(")
				OutputExpressionList(ctor.ChainedConstructorArgs)
				Output.WriteLine(")")
				do_pass = false
		GenerateStatements(method.Statements)
		if do_pass:
			passcheck(method.Statements)
		Indent--
		EndBlock()

	def MemberReference(target as CodeExpression, member as string):
		GenerateExpression(target)
		Output.Write(".${member}")

	def Indexer(target as CodeExpression, indices as CodeExpressionCollection):
		GenerateExpression(target)
		Output.Write("[")
		OutputExpressionList(indices)
		Output.Write("]")

	def Invocation(target as CodeExpression, parameters as CodeExpressionCollection):
		GenerateExpression(target)
		Output.Write("(")
		OutputExpressionList(parameters)
		Output.Write(")")

	def passcheck(stuff as ICollection):
		return if stuff.IsValid()
		if not AsBool(Options["WhiteSpaceAgnostic"]):
			Output.Write("pass")

	override def OutputTypeNamePair(type as CodeTypeReference, name as string):
		Output.Write("${name} as ")
		OutputType(type)

	protected override def GenerateParameterDeclarationExpression(e as CodeParameterDeclarationExpression):
		OutputAttributes(e.CustomAttributes, null, true);
		OutputDirection(e.Direction)
		OutputTypeNamePair(e.Type, e.Name)

	private def OutputAttributes(attributes as CodeAttributeDeclarationCollection, prefix as string, inline as bool):
		for att as CodeAttributeDeclaration in attributes:
			GenerateAttributeDeclarationsStart(attributes)
			Output.Write(prefix) if prefix
			OutputAttributeDeclaration(att)
			GenerateAttributeDeclarationsEnd(attributes)
			if inline:
				Output.Write(" ")
			else:
				Output.WriteLine()

	private def OutputAttributeDeclaration(attribute as CodeAttributeDeclaration):
		Output.Write(attribute.Name.Replace ('+', '.'))
		Output.Write('(')
		first = true
		for argument as CodeAttributeArgument in attribute.Arguments:
			if first:
				OutputAttributeArgument(argument)
				first = false
			else:
				Output.Write(", ")
				OutputAttributeArgument(argument)
		Output.Write(')')

	def ModifiersAndAttributes(typeMember as CodeTypeMember):
		OutputAttributeDeclarations(typeMember.CustomAttributes) if typeMember.CustomAttributes
		Output.WriteLine()
		to = { ziggy| return cast(int, ziggy) }
		fro = { ziggy| return cast(MemberAttributes, ziggy) }
		if typeMember.Attributes & MemberAttributes.VTableMask == MemberAttributes.New:
			typeMember.Attributes = fro(to(typeMember.Attributes) & ~ to(MemberAttributes.New))
		OutputMemberAccessModifier(typeMember.Attributes)
		OutputMemberScopeModifier(typeMember.Attributes)

	def OutputAttributeArgument(arg as CodeAttributeArgument):
		if arg.Name and arg.Name != "":
			Output.Write(arg.Name)
			Output.Write(" : ")
		GenerateExpression(arg.Value)

	protected override def OutputDirection(direction as FieldDirection):
		if direction == FieldDirection.Ref or direction == FieldDirection.Out:
			Output.Write("ref ")

	protected override def OutputFieldScopeModifier(attributes as MemberAttributes):
		scope = attributes & MemberAttributes.ScopeMask
		if scope == MemberAttributes.Static:
			Output.Write("static ")
		elif scope == MemberAttributes.Const:
			Output.Write("static final ")

	private def AsBool(data):
		return data is not null and data isa bool and cast(bool,data)

	protected override def GenerateCompileUnitStart(compileUnit as CodeCompileUnit):
		GenerateComment(CodeComment("------------------------------------------------------------------------------"))
		GenerateComment(CodeComment("This code was automatically generated by Boo.Lang.CodeDom v.${BooVersion}."))
		GenerateComment(CodeComment(""))
		GenerateComment(CodeComment("     Changes to this file may cause incorrect behavior and will be lost if "))
		GenerateComment(CodeComment("     the code is regenerated."))
		GenerateComment(CodeComment("------------------------------------------------------------------------------"))
		Output.WriteLine()

		//for future:
		if AsBool(Options["WhiteSpaceAgnostic"]):
			Output.WriteLine("//option WhiteSpaceAgnostic")
			Output.WriteLine()

		super.GenerateCompileUnitStart(compileUnit)

	protected override def GenerateCompileUnit(compileUnit as CodeCompileUnit):
		GenerateCompileUnitStart(compileUnit)

		for ns as CodeNamespace in compileUnit.Namespaces:
			GenerateNamespace(ns)

		if compileUnit.AssemblyCustomAttributes.Count > 0:
			OutputAttributes(compileUnit.AssemblyCustomAttributes,
					"assembly:", false)
			Output.WriteLine("")

		GenerateCompileUnitEnd(compileUnit)

	private def EndBlock():
		if AsBool(Options["WhiteSpaceAgnostic"]):
			Output.WriteLine("end")

#region FixIndent
	static newlinePattern = Regex("\\n",RegexOptions.Compiled)
	static whiteSpacePattern = Regex("(\\s*)",RegexOptions.Compiled)

	static public def FixIndent(code as string, indentstring as string,
				indentlevel as int, indentfirst as bool) as string:
		//how much the code should be indented:
		indentprefix = string.Empty
		indentprefix = indentstring * indentlevel if indentlevel > 0
		nonEmptyPrefix = (indentprefix != string.Empty)

		if code is null or code==string.Empty:
			if indentfirst:
				return indentprefix
			else:
				return string.Empty

		lines = newlinePattern.Split(code.Replace("\r\n","\n"))
		//or: lines = code.Split(System.Environment.NewLine.ToCharArray())

		foundFirstCodeLine = false //first line of real code
		insidecomment = 0 //inside /* */
		insidestring = false  //inside """ """
		firstline = true
		buffer = System.Text.StringBuilder()
		indent = ""
		indentSize = 0
		toTabs = indentprefix.StartsWith("\t") //otherwise, assume spaces
		for line in lines:
			//ignore comments before the first line of real code
			if not foundFirstCodeLine:
				trimmed = line.Trim()
				startmultiline = trimmed.StartsWith("/*")

				//if not whitespace and not a comment...
				if (insidecomment <= 0 and
						not startmultiline and
						not trimmed == string.Empty and
						not trimmed.StartsWith("#") and
						not trimmed.StartsWith("//")):
					foundFirstCodeLine = true
					indent = whiteSpacePattern.Match(line).Groups[0].Value
					indentSize = len(indent)
					insidecomment = 0

				//special stuff for handling multiline comments before code
				if startmultiline or insidecomment > 0:
					j = 0
					linelen = len(trimmed) - 1
					while j < linelen:
						rawArrayIndexing:
							c = trimmed[j]
						if c==char('/'):
							rawArrayIndexing:
								nextc = trimmed[j+1]
							if nextc==char('*'):
								++insidecomment
								++j
						elif c==char('*') and insidecomment > 0:
							rawArrayIndexing:
								nextc = trimmed[j+1]
							if nextc==char('/'):
								--insidecomment
								++j
						++j

			if not foundFirstCodeLine or not insidestring:
				if firstline:
					firstline = false
					if indentfirst and nonEmptyPrefix:
						buffer.Append(indentprefix)
				else:
					if nonEmptyPrefix:
						buffer.Append(indentprefix)

				if not foundFirstCodeLine:
					buffer.Append(line + System.Environment.NewLine)
					continue

				if line.StartsWith(indent):
					if indentSize > 0:
						rest = line[indentSize:]
					else:
						rest = line
					if len(rest) > 0:
						//convert any more leading whitespace to tabs or spaces
						//so we don't mix tabs and spaces on same line
						ws = whiteSpacePattern.Match(rest).Groups[0].Value
						sz = len(ws)
						if len(ws):
							rawArrayIndexing:
								c = rest[0]
							if toTabs:
								if c==char(' '):
									rest = rest[sz:]
									ws = ws.Replace("    ","\t")
									ws = ws.Replace("   ","\t")
									ws = ws.Replace("  ","\t")
									ws = ws.Replace(" ","\t")
									buffer.Append(ws)
							else:
								if c==char('\t'):
									rest = rest[sz:]
									ws = ws.Replace("\t","    ")
									buffer.Append(ws)
						buffer.Append(rest)
				else: //line is not indented as expected, pass it on as is:
					buffer.Append(line)
			else: //we are inside a triple quoted string:
				buffer.Append(line)

			buffer.Append(System.Environment.NewLine)

			//scan for triple quoted string literals:
			//   for speed reasons, I'm not also checking for insidecomment,
			//   so if you have an odd # of triple quotes inside a comment,
			//   you can break this.
			j = 0
			linelen = len(line) - 2
			while j < linelen:
				rawArrayIndexing:
					c1 = line[j]
				if c1==char('"'):
					rawArrayIndexing:
						c2 = line[j+1]
					if c2==char('"'):
						rawArrayIndexing:
							c3 = line[j+2]
						if c3==char('"'):
							insidestring = not insidestring
							j += 2
				++j

		return buffer.ToString()
#endregion


BOO-52 Boo Explorer Output Window - patch sent by Mike Douglas

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

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

class BooEditor(ScrolledWindow):

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

	[getter(Buffer)]
	_buffer = SourceBuffer(_booSourceLanguage,
							Highlight: true)

	_view = SourceView(_buffer,
						ShowLineNumbers: true,
						AutoIndent: true,
						TabsWidth: 4)

	[getter(FileName)]
	_fname as string

	Label:
		get:
			return System.IO.Path.GetFileName(_fname) if _fname
			return "unnamed.boo"

	def constructor():
		self.SetPolicy(PolicyType.Automatic, PolicyType.Automatic)
		self.Add(_view)

	def Open([required] fname as string):
		_buffer.Text = TextFile.ReadFile(fname)
		_buffer.Modified = false
		_fname = fname

	def SaveAs([required] fname as string):
		TextFile.WriteFile(fname, _buffer.Text)
		_fname = fname
		_buffer.Modified = false

	def Redo():
		_buffer.Redo()

	def Undo():
		_buffer.Undo()

class MainWindow(Window):

	_status = Statusbar(HasResizeGrip: false)
	_notebookEditors = Notebook(TabPos: PositionType.Top, Scrollable: true)
	_notebookHelpers = Notebook(TabPos: PositionType.Bottom, Scrollable: true)
	_notebookOutline = Notebook(TabPos: PositionType.Bottom, Scrollable: true)

	_documentOutline = TreeView()

	_output = TextView()
	_outputBuffer = _output.Buffer

	_accelGroup = AccelGroup()
	_editors = [] # workaround for gtk# bug #61703

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

		self.AddAccelGroup(_accelGroup)
		self.Maximize()
		self.DeleteEvent += OnDelete

		_documentOutline.AppendColumn("Name", CellRendererText (), ("text", 0))
		_notebookOutline.AppendPage(CreateScrolled(_documentOutline), Label("Document Outline"))
		_notebookHelpers.AppendPage(CreateScrolled(_output), Label("Output"))

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

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

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

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

		self.Add(vbox)

		self.NewDocument()

	private def CreateScrolled(widget):
		sw = ScrolledWindow()
		sw.Add(widget)
		return sw

	private def AppendEditor(editor as BooEditor):
		_notebookEditors.AppendPage(editor, Label(editor.Label))
		_editors.Add(editor)
		editor.ShowAll()
		_notebookEditors.CurrentPage = _notebookEditors.NPages-1

	def NewDocument():
		self.AppendEditor(BooEditor())

	def OpenDocument(fname as string):
		editor = BooEditor()
		editor.Open(fname)
		self.AppendEditor(editor)

	private def CreateMenuBar():
		mb = MenuBar()

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

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

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

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

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

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

	def AppendOutput(text as string):
		_outputBuffer.Insert(_outputBuffer.EndIter, text)

	private def _menuItemExecute_Activated(sender, args as EventArgs):

		_outputBuffer.Clear()
		self.AppendOutput("${_outputBuffer.Text}****** Compiling ${CurrentEditor.FileName} *******\n")
		compiler = Boo.Lang.Compiler.BooCompiler()
		compiler.Parameters.Input.Add(
						StringInput(CurrentEditor.Label, 								CurrentEditor.Buffer.Text))
		compiler.Parameters.Pipeline = Boo.Lang.Compiler.Pipelines.Run()

		start = date.Now
		result = compiler.Run()
		self.AppendOutput(result.Errors.ToString(true)) if (len(result.Errors))
		self.AppendOutput("Complete in ${date.Now-start}.")

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

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

		self.UpdateDocumentOutline()

	private def UpdateDocumentOutline():
		DocumentOutlineProcessor(_documentOutline, CurrentEditor).Update()

	private def _menuItemSave_Activated(sender, args as EventArgs):
		editor = CurrentEditor
		fname = editor.FileName
		if fname is null:
			fs = FileSelection("Save As", SelectMultiple: false)
			if cast(int, ResponseType.Ok) != fs.Run():
				return
			fs.Hide()
			fname = fs.Selections[0]
		editor.SaveAs(fname)
		_notebookEditors.SetTabLabelText(editor, editor.Label)

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

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

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

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

class DocumentOutlineProcessor:

	_store = TreeStore((string,))
	_documentOutline as TreeView
	_module as Boo.Lang.Compiler.Ast.Module

	def constructor(documentOutline, editor as BooEditor):
		_module = Parse(editor.FileName, editor.Buffer.Text)
		_documentOutline = documentOutline

	def Parse(fname, text):
		compiler = BooCompiler()
		compiler.Parameters.Input.Add(StringInput(fname, text))
		compiler.Parameters.Pipeline = Pipelines.Parse()
		return compiler.Run().CompileUnit.Modules[0]

	def Update():
		for type in _module.Members:
			iter = _store.AppendValues((type.Name,))
			if type isa TypeDefinition:
				UpdateType(iter, type)
		_documentOutline.Model = _store
		_documentOutline.ExpandAll()

	def UpdateType(parent, type as TypeDefinition):
		for member in type.Members:
			iter = _store.AppendValues(parent, (member.Name,))

Application.Init()

MainWindow().ShowAll()

Application.Run()

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

class BooEditor(ScrolledWindow):

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

	[getter(Buffer)]
	_buffer = SourceBuffer(_booSourceLanguage,
							Highlight: true)

	_view = SourceView(_buffer,
						ShowLineNumbers: true,
						AutoIndent: true,
						TabsWidth: 4)

	[getter(FileName)]
	_fname as string

	Label:
		get:
			return System.IO.Path.GetFileName(_fname) if _fname
			return "unnamed.boo"

	def constructor():
		self.SetPolicy(PolicyType.Automatic, PolicyType.Automatic)
		self.Add(_view)

	def Open([required] fname as string):
		_buffer.Text = TextFile.ReadFile(fname)
		_buffer.Modified = false
		_fname = fname

	def SaveAs([required] fname as string):
		TextFile.WriteFile(fname, _buffer.Text)
		_fname = fname
		_buffer.Modified = false

	def Redo():
		_buffer.Redo()

	def Undo():
		_buffer.Undo()

class MainWindow(Window):

	_status = Statusbar(HasResizeGrip: false)
	_notebookEditors = Notebook(TabPos: PositionType.Top, Scrollable: true)
	_notebookHelpers = Notebook(TabPos: PositionType.Top, Scrollable: true)
	_notebookOutline = Notebook(TabPos: PositionType.Bottom, Scrollable: true)

	_documentOutline = TreeView()

	_accelGroup = AccelGroup()
	_editors = [] # workaround for gtk# bug #61703

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

		self.AddAccelGroup(_accelGroup)
		self.Maximize()
		self.DeleteEvent += OnDelete

		_documentOutline.AppendColumn("Name", CellRendererText (), ("text", 0))
		sw = ScrolledWindow()
		sw.Add(_documentOutline)
		_notebookOutline.AppendPage(sw, Label("Document Outline"))

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

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

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

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

		self.Add(vbox)

		self.NewDocument()

	private def AppendEditor(editor as BooEditor):
		_notebookEditors.AppendPage(editor, Label(editor.Label))
		_editors.Add(editor)
		editor.ShowAll()
		_notebookEditors.CurrentPage = _notebookEditors.NPages-1

	def NewDocument():
		self.AppendEditor(BooEditor())

	def OpenDocument(fname as string):
		editor = BooEditor()
		editor.Open(fname)
		self.AppendEditor(editor)

	private def CreateMenuBar():
		mb = MenuBar()

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

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

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

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

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

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

	private def _menuItemExecute_Activated(sender, args as EventArgs):

		compiler = Boo.Lang.Compiler.BooCompiler()
		compiler.Parameters.Input.Add(
						StringInput(CurrentEditor.Label, 								CurrentEditor.Buffer.Text))
		compiler.Parameters.Pipeline = Boo.Lang.Compiler.Pipelines.Run()

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

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

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

		self.UpdateDocumentOutline()

	private def UpdateDocumentOutline():
		DocumentOutlineProcessor(_documentOutline, CurrentEditor).Update()

	private def _menuItemSave_Activated(sender, args as EventArgs):
		editor = CurrentEditor
		fname = editor.FileName
		if fname is null:
			fs = FileSelection("Save As", SelectMultiple: false)
			if cast(int, ResponseType.Ok) != fs.Run():
				return
			fs.Hide()
			fname = fs.Selections[0]
		editor.SaveAs(fname)
		_notebookEditors.SetTabLabelText(editor, editor.Label)

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

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

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

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

class DocumentOutlineProcessor:

	_store = TreeStore((string,))
	_documentOutline as TreeView
	_module as Boo.Lang.Compiler.Ast.Module

	def constructor(documentOutline, editor as BooEditor):
		_module = Parse(editor.FileName, editor.Buffer.Text)
		_documentOutline = documentOutline

	def Parse(fname, text):
		compiler = BooCompiler()
		compiler.Parameters.Input.Add(StringInput(fname, text))
		compiler.Parameters.Pipeline = Pipelines.Parse()
		return compiler.Run().CompileUnit.Modules[0]

	def Update():
		for type in _module.Members:
			iter = _store.AppendValues((type.Name,))
			if type isa TypeDefinition:
				UpdateType(iter, type)
		_documentOutline.Model = _store
		_documentOutline.ExpandAll()

	def UpdateType(parent, type as TypeDefinition):
		for member in type.Members:
			iter = _store.AppendValues(parent, (member.Name,))

Application.Init()

MainWindow().ShowAll()

Application.Run()


don't collect sequence parse trees when they are not needed

namespace Boo.OMeta

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

macro ometa:

	def enclosingTypeDefinition():
		type as TypeDefinition = ometa.GetAncestor(NodeType.ClassDefinition) or ometa.GetAncestor(NodeType.Module)
		return type

	def expressions():
		for stmt in ometa.Block.Statements:
			match stmt:
				case ExpressionStatement(Expression: e):
					yield e

	def expandGrammarSetup():
		block = Block()
		for e in expressions():
			match e:
				case [| $(ReferenceExpression(Name: name)) = $pattern |]:
					code = [|
						block:
							InstallRule($name) do(grammar as OMetaGrammar, input as OMetaInput):
								//print "> ${$name}"
								//try:
									$(RuleExpander(name).expand(pattern))
								//ensure:
								//	print "< ${$name}"
					|].Block
					block.Add(code)
		return block

	declaration = ometa.Arguments[0]

	type = [|
		class $(grammarName(declaration))(OMetaGrammar):

			_grammar as OMetaGrammar

			def constructor():
				_grammar = $(prototypeFor(declaration))
				$(expandGrammarSetup())

			def InstallRule(ruleName as string, rule as OMetaRule):
				_grammar.InstallRule(ruleName, rule)

			def OMetaGrammar.Apply(context as OMetaGrammar, rule as string, input as OMetaInput):
				return _grammar.Apply(context, rule, input)

			def OMetaGrammar.SuperApply(context as OMetaGrammar, rule as string, input as OMetaInput):
				return _grammar.SuperApply(context, rule, input)

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

	|]
	for e in expressions():
		match e:
			case [| $(ReferenceExpression(Name: name)) = $_ |]:
				m = [|
					def $name(input as OMetaInput):
						return Apply($name, input)
				|]
				type.Members.Add(m)

	type.LexicalInfo = ometa.LexicalInfo
	enclosingTypeDefinition().Members.Add(type)

def prototypeFor(e as Expression) as MethodInvocationExpression:
	match e:
		case [| $_ < $prototype |]:
			return [| OMetaDelegatingGrammar($prototype()) |]
		case ReferenceExpression():
			return [| OMetaGrammarPrototype() |]

def grammarName(e as Expression) as string:
	match e:
		case ReferenceExpression(Name: name):
			return name
		case [| $l < $_ |]:
			return grammarName(l)

class RuleExpander:

	_ruleName as string
	_collectingParseTree = DynamicVariable[of bool](true)

	def constructor(ruleName as string):
		_ruleName = ruleName

	def expand(e as Expression) as Block:
		block = expand(e, [| input |], [| lastMatch |])
		block.Add([| return lastMatch |])
		block.LexicalInfo = e.LexicalInfo
		return block

	def expand(e as Expression, input as Expression, lastMatch as ReferenceExpression) as Block:
		block = Block()
		expand block, e, input, lastMatch
		return block

	def expandChoices(block as Block, choices as List, input as Expression, lastMatch as ReferenceExpression):
		temp = uniqueName()

		currentBlock = block
		for choice in choices:
			expand currentBlock, choice, input, temp
			code = [|
				if $temp isa SuccessfulMatch:
					$lastMatch = $temp
				else:
					pass
			|]
			currentBlock.Add(code)
			currentBlock = code.FalseBlock
		currentBlock.Add([| $lastMatch = FailedMatch($input) |])

	def expandRepetition(block as Block, e as Expression, input as Expression, lastMatch as ReferenceExpression):

		temp = lastMatch #uniqueName()
		result = uniqueName()
		code = [|
			block:
				$(expand(e, input, temp))
				smatch = $temp as SuccessfulMatch
				if smatch is not null:
					$result = [smatch.Value]
					while true:
						$(expand(e, [| $temp.Input |], temp))
						smatch = $temp as SuccessfulMatch
						break if smatch is null
						$result.Add(smatch.Value)

					$lastMatch = SuccessfulMatch($temp.Input, $result )
		|].Block
		block.Add(code)

	def collectChoices(choices as List, e as Expression):
		match e:
			case [| $l | $r |]:
				collectChoices choices, l
				collectChoices choices, r
			otherwise:
				choices.Add(e)

	collectingParseTree:
		get: return _collectingParseTree.Value

	def expandSequence(block as Block, sequence as ExpressionCollection, input as Expression, lastMatch as ReferenceExpression):

		if collectingParseTree:
			expandSequenceWithParseTree block, sequence, input, lastMatch
		else:
			expandSequenceWithoutParseTree block, sequence, input, lastMatch

	def expandSequenceWithoutParseTree(block as Block, sequence as ExpressionCollection, input as Expression, lastMatch as ReferenceExpression):

		currentBlock = block
		for item in sequence:
			expand currentBlock, item, input, lastMatch
			input = [| $lastMatch.Input |]
			code = [|
				if $lastMatch isa SuccessfulMatch:
					pass
			|]
			currentBlock.Add(code)
			currentBlock = code.TrueBlock

	def expandSequenceWithParseTree(block as Block, sequence as ExpressionCollection, input as Expression, lastMatch as ReferenceExpression):

		result = uniqueName()
		currentBlock = block
		currentBlock.Add([| $result = [] |])

		for item in sequence:
			expand currentBlock, item, input, lastMatch
			input = [| $lastMatch.Input |]
			currentBlock.Add([| smatch = $lastMatch as SuccessfulMatch |])
			code = [|
				if smatch is not null:
					$result.Add(smatch.Value)
			|]
			currentBlock.Add(code)
			currentBlock = code.TrueBlock
		currentBlock.Add([| $lastMatch = SuccessfulMatch(smatch.Input, $result) |])

	def expand(block as Block, e as Expression, input as Expression, lastMatch as ReferenceExpression):

		match e:
			case [| $pattern ^ $value |]:
				_collectingParseTree.With(false):
					expand block, pattern, input, lastMatch
					code = [|
						block:
							smatch = $lastMatch as SuccessfulMatch
							if smatch is not null:
								$lastMatch = SuccessfulMatch(smatch.Input, $value)
					|].Block
					block.Add(code)

			case [| $pattern >> $variable |]:
				_collectingParseTree.With(true):
					expand block, pattern, input, lastMatch
					code = [|
						block:
							smatch = $lastMatch as SuccessfulMatch
							if smatch is not null:
								$variable = smatch.Value
					|].Block
					block.Add(code)

			case [| $_ | $_ |]:
				choices = []
				collectChoices choices, e
				expandChoices block, choices, input, lastMatch

			case StringLiteralExpression():
				block.Add([| $lastMatch = string_($input, $e) |])

			case [| ++$rule |]:
				expandRepetition block, rule, input, lastMatch

			case ReferenceExpression(Name: name):
				block.Add([| $lastMatch = grammar.Apply(grammar, $name, $input) |])

			case [| super |]:
				block.Add([| $lastMatch = grammar.SuperApply(grammar, $_ruleName, $input) |])

			case ArrayLiteralExpression(Items: items):
				expandSequence block, items, input, lastMatch

def uniqueName():
	return ReferenceExpression(Name: "temp${CompilerContext.Current.AllocIndex()}")

namespace Boo.OMeta

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

let currentRuleName = DynamicVariable[of string]()

macro ometa:

	def enclosingTypeDefinition():
		type as TypeDefinition = ometa.GetAncestor(NodeType.ClassDefinition) or ometa.GetAncestor(NodeType.Module)
		return type

	def expressions():
		for stmt in ometa.Block.Statements:
			match stmt:
				case ExpressionStatement(Expression: e):
					yield e

	def expandGrammarSetup():
		block = Block()
		for e in expressions():
			match e:
				case [| $(ReferenceExpression(Name: name)) = $pattern |]:
					currentRuleName.With(name):
						code = [|
							block:
								InstallRule($name) do(grammar as OMetaGrammar, input as OMetaInput):
									//print "> ${$name}"
									//try:
										$(expand(pattern))
									//ensure:
									//	print "< ${$name}"
						|].Block
						block.Add(code)
		return block

	declaration = ometa.Arguments[0]

	type = [|
		class $(grammarName(declaration))(OMetaGrammar):

			_grammar as OMetaGrammar

			def constructor():
				_grammar = $(prototypeFor(declaration))
				$(expandGrammarSetup())

			def InstallRule(ruleName as string, rule as OMetaRule):
				_grammar.InstallRule(ruleName, rule)

			def OMetaGrammar.Apply(context as OMetaGrammar, rule as string, input as OMetaInput):
				return _grammar.Apply(context, rule, input)

			def OMetaGrammar.SuperApply(context as OMetaGrammar, rule as string, input as OMetaInput):
				return _grammar.SuperApply(context, rule, input)

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

	|]
	for e in expressions():
		match e:
			case [| $(ReferenceExpression(Name: name)) = $_ |]:
				m = [|
					def $name(input as OMetaInput):
						return Apply($name, input)
				|]
				type.Members.Add(m)

	type.LexicalInfo = ometa.LexicalInfo
	enclosingTypeDefinition().Members.Add(type)

def prototypeFor(e as Expression) as MethodInvocationExpression:
	match e:
		case [| $_ < $prototype |]:
			return [| OMetaDelegatingGrammar($prototype()) |]
		case ReferenceExpression():
			return [| OMetaGrammarPrototype() |]

def grammarName(e as Expression) as string:
	match e:
		case ReferenceExpression(Name: name):
			return name
		case [| $l < $_ |]:
			return grammarName(l)

def expand(e as Expression) as Block:
	block = expand(e, [| input |], [| lastMatch |])
	block.Add([| return lastMatch |])
	block.LexicalInfo = e.LexicalInfo
	return block

def expand(e as Expression, input as Expression, lastMatch as ReferenceExpression) as Block:
	block = Block()
	expand block, e, input, lastMatch
	return block

def expandChoices(block as Block, choices as List, input as Expression, lastMatch as ReferenceExpression):
	temp = uniqueName()

	currentBlock = block
	for choice in choices:
		expand currentBlock, choice, input, temp
		code = [|
			if $temp isa SuccessfulMatch:
				$lastMatch = $temp
			else:
				pass
		|]
		currentBlock.Add(code)
		currentBlock = code.FalseBlock
	currentBlock.Add([| $lastMatch = FailedMatch($input) |])

def expandRepetition(block as Block, e as Expression, input as Expression, lastMatch as ReferenceExpression):

	temp = lastMatch #uniqueName()
	result = uniqueName()
	code = [|
		block:
			$(expand(e, input, temp))
			smatch = $temp as SuccessfulMatch
			if smatch is not null:
				$result = [smatch.Value]
				while true:
					$(expand(e, [| $temp.Input |], temp))
					smatch = $temp as SuccessfulMatch
					break if smatch is null
					$result.Add(smatch.Value)

				$lastMatch = SuccessfulMatch($temp.Input, $result )
	|].Block
	block.Add(code)

def uniqueName():
	return ReferenceExpression(Name: "temp${CompilerContext.Current.AllocIndex()}")

def collectChoices(choices as List, e as Expression):
	match e:
		case [| $l | $r |]:
			collectChoices choices, l
			collectChoices choices, r
		otherwise:
			choices.Add(e)

def expandSequence(block as Block, sequence as ExpressionCollection, input as Expression, lastMatch as ReferenceExpression):
	currentBlock = block

	values = uniqueName()
	currentBlock.Add([| $values = [] |])
	for item in sequence:
		expand currentBlock, item, input, lastMatch
		input = [| $lastMatch.Input |]
		currentBlock.Add([| smatch = $lastMatch as SuccessfulMatch |])
		code = [|
			if smatch is not null:
				$values.Add(smatch.Value)
		|]
		currentBlock.Add(code)
		currentBlock = code.TrueBlock
	currentBlock.Add([| $lastMatch = SuccessfulMatch(smatch.Input, $values) |])

def expand(block as Block, e as Expression, input as Expression, lastMatch as ReferenceExpression):

	match e:
		case [| $pattern ^ $value |]:
			expand block, pattern, input, lastMatch
			code = [|
				block:
					smatch = $lastMatch as SuccessfulMatch
					if smatch is not null:
						$lastMatch = SuccessfulMatch(smatch.Input, $value)
			|].Block
			block.Add(code)

		case [| $pattern >> $variable |]:
			expand block, pattern, input, lastMatch
			code = [|
				block:
					smatch = $lastMatch as SuccessfulMatch
					if smatch is not null:
						$variable = smatch.Value
			|].Block
			block.Add(code)

		case [| $_ | $_ |]:
			choices = []
			collectChoices choices, e
			expandChoices block, choices, input, lastMatch

		case StringLiteralExpression():
			block.Add([| $lastMatch = string_($input, $e) |])

		case [| ++$rule |]:
			expandRepetition block, rule, input, lastMatch

		case ReferenceExpression(Name: name):
			block.Add([| $lastMatch = grammar.Apply(grammar, $name, $input) |])

		case [| super |]:
			block.Add([| $lastMatch = grammar.SuperApply(grammar, $(currentRuleName.Value), $input) |])

		case ArrayLiteralExpression(Items: items):
			expandSequence block, items, input, lastMatch


code completion patch by Arron Washington

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

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

"""
Interactive Forms-based Console
"""

namespace booish.gui

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

class KeyChar:

	public static Enter = chr(Keys.Enter)

	public static Dot = chr('.')

	public static Back = chr(Keys.Back)

	public static Esc = chr(Keys.Escape)

	public static Del = chr(Keys.Delete)

class CompletionBox(ListBox):

	_interpreter as InteractiveInterpreter

	_currentCodeCompletion = ""

	_codeCompletionArchive = []

	_upsideDown = false

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

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

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

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

		self.Visible = true
		self.Focus()

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

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

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

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

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

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

class PromptBox(TextBox):

	enum InputState:
		SingleLine = 0
		Block = 1

	_state = InputState.SingleLine

	_block = System.IO.StringWriter()

	_console = StringWriter()

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

	_completionBox = CompletionBox(_interpreter)

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

		Controls.Add(_completionBox)

		prompt()

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

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

	def SingleLineInputState():
		code = GetCurrentLine()

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

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

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

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

		super(args)

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

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

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

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

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

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

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

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

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

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

"""
Interactive Forms-based Console
"""

namespace booish.gui

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

class KeyChar:

	public static Enter = chr(Keys.Enter)

	public static Dot = chr('.')

	public static Back = chr(Keys.Back)

	public static Esc = chr(Keys.Escape)

class CompletionBox(ListBox):

	_interpreter as InteractiveInterpreter

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

	def Fill(type as System.Type):

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

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

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

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

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

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

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

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

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

class PromptBox(TextBox):

	enum InputState:
		SingleLine = 0
		Block = 1

	_state = InputState.SingleLine

	_block = System.IO.StringWriter()

	_console = StringWriter()

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

	_completionBox = CompletionBox(_interpreter)

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

		Controls.Add(_completionBox)

		prompt()

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

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

	def SingleLineInputState():
		code = GetCurrentLine()

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

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

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

		super(args)

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

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

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

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

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

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

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

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

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


Fixed buggy test logic

Fixed buggy test logic

"""
lower, end
medium, end
higher, end
unless, end
end
"""

def gen_if(x as int):
	if x < 10:
		yield "lower"
	elif x < 20:
		yield "medium"
	else:
		yield "higher"

	yield 'end'

def gen_unless(x as int):
	unless x > 10:
		yield "unless"
	yield 'end'

print join(gen_if(5), ', ')
print join(gen_if(15), ', ')
print join(gen_if(25), ', ')

print join(gen_unless(5), ', ')
print join(gen_unless(25), ', ')

"""
lower, end
medium, end
higher, end
unless, end
end
"""

def gen_if(x as int):
	if x < 10:
		yield "lower"
	elif x < 20:
		yield "medium"
	else:
		yield "higher"

	yield 'end'

def gen_unless(x as int):
	unless x < 10:
		yield "unless"
	yield 'end'

print join(gen_if(5), ', ')
print join(gen_if(15), ', ')
print join(gen_if(25), ', ')

print join(gen_unless(5), ', ')
print join(gen_unless(25), ', ')