gtkshell, set only the font (not the size)

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

import System
import Gtk from "gtk-sharp"
import Gdk from "gdk-sharp" as Gdk
import Pango from "pango-sharp" as Pango
import booish from booish

class PromptView(TextView):

	_interpreter = InteractiveInterpreter(RememberLastValue: true)

	def constructor():

		self.ModifyFont(
			Pango.FontDescription(Family: "Courier New"))

		_interpreter.References.Add(typeof(TextView).Assembly)
		_interpreter.References.Add(typeof(Gdk.Key).Assembly)
		_interpreter.SetValue("print", print)
		_interpreter.SetValue("dir", dir)
		_interpreter.SetValue("help", help)
		_interpreter.SetValue("cls", { Buffer.Text = "" })

		prompt()

	override def OnKeyPressEvent(ev as Gdk.EventKey):
		if Gdk.Key.Return == ev.Key:
			try:
				EvalCurrentLine()
			except x:
				print(x)
			prompt()
			return true
		elif ev.Key in Gdk.Key.BackSpace, Gdk.Key.Left:
			if Buffer.GetIterAtMark(Buffer.InsertMark).LineOffset < 5:
				return true

		return super(ev)

	def print(obj):
		Buffer.InsertAtCursor("${obj}\n")

	def prompt():
		Buffer.MoveMark(Buffer.InsertMark, Buffer.EndIter)
		Buffer.InsertAtCursor(">>> ")

	def dir([required] obj):
		type = (obj as Type) or obj.GetType()
		for member in type.GetMembers():
			method = member as System.Reflection.MethodInfo
			if method is not null:
				yield method if method.IsPublic and not method.IsSpecialName
			else:
				yield member

	def help(obj):
		print(join(dir(obj), "\n"))

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

	def EvalCurrentLine():
		start = Buffer.GetIterAtLine(Buffer.LineCount)
		line = Buffer.GetText(start, Buffer.EndIter, false)

		print("")
		result = _interpreter.Eval(line[4:])
		if len(result.Errors):

			for error in result.Errors:
				pos = error.LexicalInfo.StartColumn
				print("---" + "-"*pos + "^") if pos > 0
				print("ERROR: ${error.Message}")
		else:
			_ = _interpreter.LastValue
			if _ is not null:
				print(repr(_))
				_interpreter.SetValue("_", _)

class MainWindow(Window):

	def constructor():
		super("booish")

		window = ScrolledWindow()
		window.Add(PromptView())

		self.Add(window)

		self.DeleteEvent += Application.Quit

Application.Init()

window = MainWindow(DefaultWidth:  400,
				DefaultHeight: 250)

window.ShowAll()

Application.Run()

import System
import Gtk from "gtk-sharp"
import Gdk from "gdk-sharp" as Gdk
import Pango from "pango-sharp" as Pango
import booish from booish

class PromptView(TextView):

	_interpreter = InteractiveInterpreter(RememberLastValue: true)

	def constructor():

		self.ModifyFont(
			Pango.FontDescription(Family: "Lucida Console", Size: 12))

		_interpreter.References.Add(typeof(TextView).Assembly)
		_interpreter.References.Add(typeof(Gdk.Key).Assembly)
		_interpreter.SetValue("print", print)
		_interpreter.SetValue("dir", dir)
		_interpreter.SetValue("help", help)
		_interpreter.SetValue("cls", { Buffer.Text = "" })

		prompt()

	override def OnKeyPressEvent(ev as Gdk.EventKey):
		if Gdk.Key.Return == ev.Key:
			try:
				EvalCurrentLine()
			except x:
				print(x)
			prompt()
			return true
		elif ev.Key in Gdk.Key.BackSpace, Gdk.Key.Left:
			if Buffer.GetIterAtMark(Buffer.InsertMark).LineOffset < 5:
				return true

		return super(ev)

	def print(obj):
		Buffer.InsertAtCursor("${obj}\n")

	def prompt():
		Buffer.MoveMark(Buffer.InsertMark, Buffer.EndIter)
		Buffer.InsertAtCursor(">>> ")

	def dir([required] obj):
		type = (obj as Type) or obj.GetType()
		for member in type.GetMembers():
			method = member as System.Reflection.MethodInfo
			if method is not null:
				yield method if method.IsPublic and not method.IsSpecialName
			else:
				yield member

	def help(obj):
		print(join(dir(obj), "\n"))

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

	def EvalCurrentLine():
		start = Buffer.GetIterAtLine(Buffer.LineCount)
		line = Buffer.GetText(start, Buffer.EndIter, false)

		print("")
		result = _interpreter.Eval(line[4:])
		if len(result.Errors):

			for error in result.Errors:
				pos = error.LexicalInfo.StartColumn
				print("---" + "-"*pos + "^") if pos > 0
				print("ERROR: ${error.Message}")
		else:
			_ = _interpreter.LastValue
			if _ is not null:
				print(repr(_))
				_interpreter.SetValue("_", _)

class MainWindow(Window):

	def constructor():
		super("booish")

		window = ScrolledWindow()
		window.Add(PromptView())

		self.Add(window)

		self.DeleteEvent += Application.Quit

Application.Init()

window = MainWindow(DefaultWidth:  400,
				DefaultHeight: 250)

window.ShowAll()

Application.Run()


fixed compiler options handling (library, winexe, exe)

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

#region license
// Copyright (c) 2004, Daniel Grunwald (daniel@danielgrunwald.de)
// All rights reserved.
//
// BooBinding is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// BooBinding is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with BooBinding; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#endregion

namespace BooBinding

import System
import System.Collections
import System.Diagnostics
import System.IO
import System.Globalization
import System.Threading
import System.Reflection.Assembly as Assembly
import Boo.Lang.Compiler
import Boo.Lang.Compiler.IO
import Boo.Lang.Compiler.Pipelines
import Boo.Lang.Compiler.Resources

class BooCompilerWrapper(MarshalByRefObject):
	_compiler as BooCompiler
	_options as CompilerParameters
	_defaultReferences = ("System.Data",
	                      "System.Drawing",
	                      "System.Management",
	                      "System.Messaging",
	                      "System.Runtime.Remoting",
	                      "System.Runtime.Serialization.Formatters.Soap",
	                      "System.Security",
	                      "System.ServiceProcess",
	                      "System.Web",
	                      "System.Web.Services",
	                      "System.Windows.Forms",
	                      "System.Xml")

	def constructor():
		_compiler = BooCompiler()
		_options = _compiler.Parameters
		for reference in _defaultReferences:
			AddReference(reference)

	def SetOptions(o as BooBinding.CompilerOptions):
		_options.Debug = o.IncludeDebugInformation

		if o.CompileTarget == CompileTarget.WinExe:
			_options.OutputType = CompilerOutputType.WindowsApplication
		elif o.CompileTarget == CompileTarget.Library:
			_options.OutputType = CompilerOutputType.Library
		else:
			_options.OutputType = CompilerOutputType.ConsoleApplication

		if _options.Pipeline == null:
			pipeline = CompileToFile()
			parsingStep as Boo.Lang.Parser.BooParsingStep = pipeline[0]
			parsingStep.TabSize = 1
			_options.Pipeline = pipeline

	OutputFile as string:
		get:
			return _options.OutputAssembly
		set:
			_options.OutputAssembly = value

	def AddInputFile(fileName as string):
		_options.Input.Add(FileInput(fileName))

	def AddReference(assemblyName as string):
		reference as Assembly = Assembly.LoadWithPartialName(assemblyName)
		if reference == null:
			reference = Assembly.LoadFrom(Path.GetFullPath(assemblyName))
			if reference == null:
				raise ApplicationException('Unable to load reference ' + assemblyName)

		_options.References.Add(reference)

	def AddResource(fileName as string):
		_options.Resources.Add(FileResource(fileName))

	def Run():
		context = _compiler.Run()
		result = ArrayList()
		for error as CompilerError in context.Errors:
			result.Add(error)
		for warning as CompilerWarning in context.Warnings:
			result.Add(warning)
		return result

#region license
// Copyright (c) 2004, Daniel Grunwald (daniel@danielgrunwald.de)
// All rights reserved.
//
// BooBinding is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// BooBinding is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with BooBinding; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#endregion

namespace BooBinding

import System
import System.Collections
import System.Diagnostics
import System.IO
import System.Globalization
import System.Threading
import System.Reflection.Assembly as Assembly
import Boo.Lang.Compiler
import Boo.Lang.Compiler.IO
import Boo.Lang.Compiler.Pipelines
import Boo.Lang.Compiler.Resources

class BooCompilerWrapper(MarshalByRefObject):
	_compiler as BooCompiler
	_options as CompilerParameters
	_defaultReferences = ("System.Data",
	                      "System.Drawing",
	                      "System.Management",
	                      "System.Messaging",
	                      "System.Runtime.Remoting",
	                      "System.Runtime.Serialization.Formatters.Soap",
	                      "System.Security",
	                      "System.ServiceProcess",
	                      "System.Web",
	                      "System.Web.Services",
	                      "System.Windows.Forms",
	                      "System.Xml")

	def constructor():
		_compiler = BooCompiler()
		_options = _compiler.Parameters
		for reference in _defaultReferences:
			AddReference(reference)

	def SetOptions(o as BooBinding.CompilerOptions):
		/* This code creates an internal compiler error when compiling with the NAnt build file
		_options.Debug = o.IncludeDebugInformation

		if o.CompileTarget == CompileTarget.WinExe:
			_options.OutputType = CompilerOutputType.WindowsApplication
		elif o.CompileTarget == CompileTarget.Library:
			_options.OutputType = CompilerOutputType.Library
		else:
			_options.OutputType = CompilerOutputType.ConsoleApplication
		*/
		if _options.Pipeline == null:
			pipeline = CompileToFile()
			parsingStep as Boo.Lang.Parser.BooParsingStep = pipeline[0]
			parsingStep.TabSize = 1
			_options.Pipeline = pipeline

	OutputFile as string:
		get:
			return _options.OutputAssembly
		set:
			_options.OutputAssembly = value

	def AddInputFile(fileName as string):
		_options.Input.Add(FileInput(fileName))

	def AddReference(assemblyName as string):
		reference as Assembly = Assembly.LoadWithPartialName(assemblyName)
		if reference == null:
			reference = Assembly.LoadFrom(Path.GetFullPath(assemblyName))
			if reference == null:
				raise ApplicationException('Unable to load reference ' + assemblyName)

		_options.References.Add(reference)

	def AddResource(fileName as string):
		_options.Resources.Add(FileResource(fileName))

	def Run():
		context = _compiler.Run()
		result = ArrayList()
		for error as CompilerError in context.Errors:
			result.Add(error)
		for warning as CompilerWarning in context.Warnings:
			result.Add(warning)
		return result


report version to teamcity

report version to teamcity

import System.Environment
import System.IO

import GitSharp

repo as Repository
version as string
out = ".\\out"
slnFile = ".\\src\\ntemplate.sln"
configuration = 'Release'

def getRepo:
	if repo == null:
		repo = Repository(Directory.GetCurrentDirectory())
	return repo

def setVersion:
	majorVersion = File.ReadAllText('version').Trim()
	buildNumber = env('BUILD_NUMBER')
	buildNumber = "0" if (string.IsNullOrEmpty(buildNumber))
	version = majorVersion + "." + buildNumber
	print 'version: ' + version

def reportVersionToTeamCity:
	print "##teamcity[buildNumber '${version}']"

def writeBuildInfo:
	sourceOutFilename = Path.Combine(out, 'BuildInfo.txt')
	lines = ["Build info:","===================="]
	lines.Add(" at: " + System.DateTime.UtcNow)
	lines.Add(" branch: " + repo.CurrentBranch.Name)
	lines.Add(" commit: " + repo.Head.CurrentCommit.Hash)
	lines.Add(" source: " + env("BUILD_VCS_URL"))

	File.WriteAllLines(sourceOutFilename, array(string, lines))

def revertAssemblyInfo:
	index = Index(repo)
	fullpath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "src/SolutionAssemblyInfo.cs"))
	index.Checkout(fullpath)

setVersion()
reportVersionToTeamCity()
getRepo()

desc "set build configuration to Debug"
target debug:
	configuration = 'Debug'

desc "initialization"
target init, (generate_assembly_info):
	rmdir(out)
	mkdir(out)
	writeBuildInfo()
	print 'initialized'

desc "Generating the shared assemblyInfo with version info"
target generate_assembly_info:
	with generate_assembly_info():
		.file = 'src\\SolutionAssemblyInfo.cs'
		.version = version

desc "entry point for the teamcity build"
target default, (init, build, test):
	print 'default target completed'

desc "Build the sources"
target build:
	msbuild(file: slnFile, configuration: configuration)

desc "run unit tests"
target test:
	test_assemblies = (".\\src\\NTemplate.Tests\\bin\\${configuration}\\NTemplate.Tests.dll",)
	nunit(assemblies: test_assemblies, toolPath: ".\\src\\packages\\NUnit.2.5.7.10213\\Tools\\nunit-console.exe" )

import System.Environment
import System.IO

import GitSharp

repo as Repository
version as string
out = ".\\out"
slnFile = ".\\src\\ntemplate.sln"
configuration = 'Release'

def getRepo:
	if repo == null:
		repo = Repository(Directory.GetCurrentDirectory())
	return repo

def setVersion:
	majorVersion = File.ReadAllText('version').Trim()
	buildNumber = env('BUILD_NUMBER')
	buildNumber = "0" if (string.IsNullOrEmpty(buildNumber))
	version = majorVersion + "." + buildNumber
	print 'version: ' + version

def writeBuildInfo:
	sourceOutFilename = Path.Combine(out, 'BuildInfo.txt')
	lines = ["Build info:","===================="]
	lines.Add(" at: " + System.DateTime.UtcNow)
	lines.Add(" branch: " + repo.CurrentBranch.Name)
	lines.Add(" commit: " + repo.Head.CurrentCommit.Hash)
	lines.Add(" source: " + env("BUILD_VCS_URL"))

	File.WriteAllLines(sourceOutFilename, array(string, lines))

def revertAssemblyInfo:
	index = Index(repo)
	fullpath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "src/SolutionAssemblyInfo.cs"))
	index.Checkout(fullpath)

setVersion()
getRepo()

desc "set build configuration to Debug"
target debug:
	configuration = 'Debug'

desc "initialization"
target init, (generate_assembly_info):
	rmdir(out)
	mkdir(out)
	writeBuildInfo()
	print 'initialized'

desc "Generating the shared assemblyInfo with version info"
target generate_assembly_info:
	with generate_assembly_info():
		.file = 'src\\SolutionAssemblyInfo.cs'
		.version = version

desc "entry point for the teamcity build"
target default, (init, build, test):
	print 'default target completed'

desc "Build the sources"
target build:
	msbuild(file: slnFile, configuration: configuration)

desc "run unit tests"
target test:
	test_assemblies = (".\\src\\NTemplate.Tests\\bin\\${configuration}\\NTemplate.Tests.dll",)
	nunit(assemblies: test_assemblies, toolPath: ".\\src\\packages\\NUnit.2.5.7.10213\\Tools\\nunit-console.exe" )


one to go!

one to go!

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

"""
Eric
Terry
John
"""
class Person:

	[getter(Name)]
	_name as string

	def constructor(name as string):
		_name = name

	static def create(name):
		return Person(name)

for p as Person in map(Person.create, ["Eric", "Terry", "John"]):
	print(p.Name)

"""
Eric
Terry
John
"""
class Person:

	[getter(Name)]
	_name as string

	def constructor(name as string):
		_name = name

for p as Person in map(Person, ["Eric", "Terry", "John"]):
	print(p.Name)


default booish to darker colors, looks better on both dark or bright backgrounds.

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

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

namespace Boo.Lang.Interpreter

import System
import System.Collections
import System.Collections.Generic
import System.IO
import System.Text
import System.Text.RegularExpressions
import Boo.Lang
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.TypeSystem
import Boo.Lang.Compiler.IO

class InteractiveInterpreter2(AbstractInterpreter):

	_values = {}

	_declarations = {}

	_representers = []

	[property(ShowWarnings)]
	_showWarnings = false

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

	[getter(LastValue)]
	_lastValue

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

	_entityNameComparer = EntityNameComparer()

	def constructor():
		super()
		Initialize()

	def constructor(parser as ICompilerStep):
		super(parser)
		Initialize()

	def Initialize():
		_disableColors = true if Environment.GetEnvironmentVariable("BOOISH_DISABLE_COLORS") is not null
		_disableColors = true if Environment.GetEnvironmentVariable("INSIDE_EMACS") is not null
		_disableAutocompletion = true if Environment.GetEnvironmentVariable("BOOISH_DISABLE_AUTOCOMPLETION") is not null
		InitializeStandardReferences()
		LoadHistory()

	def Reset():
		_values.Clear()
		_declarations.Clear()
		_lastValue = null
		_indent = 0
		InitializeStandardReferences()

	public final static HISTORY_FILENAME = "booish_history"
	public final static HISTORY_CAPACITY = 100
	protected _history = System.Collections.Generic.Queue of string(HISTORY_CAPACITY)
	protected _historyFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), HISTORY_FILENAME)
	protected _historyIndex = 0
	protected _session = System.Collections.Generic.List of string()

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

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

	[property(DefaultPrompt)]
	_defaultPrompt = ">>>"

	[property(BlockPrompt)]
	_blockPrompt = "..."

	[property(DisableColors)]
	_disableColors = false

	[property(DisableAutocompletion)]
	_disableAutocompletion = false

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

	[property(PromptColor)]
	_promptColor = ConsoleColor.DarkGreen

	[property(ExceptionColor)]
	_exceptionColor = ConsoleColor.DarkRed

	[property(SuggestionsColor)]
	_suggestionsColor = ConsoleColor.DarkYellow

	[property(SelectedSuggestionColor)]
	_selectedSuggestionColor = ConsoleColor.DarkMagenta

	[property(SelectedSuggestionIndex)]
	_selectedSuggIdx as int?

	[property(Suggestions)]
	_suggestions as (object)

	CanAutoComplete as bool:
		get:
			return _selectedSuggIdx is not null

	private _builtins as (IEntity)
	private _filter as string

	private def ConsolePrintPrompt():
		Console.ForegroundColor = _promptColor if not _disableColors
		Console.Write(CurrentPrompt)
		#TODO: automatic indentation option ?
		#for i in range(_indent):
		#	_line.Append("\t")
		#	Console.Write("\t")
		Console.ResetColor() if not _disableColors

	private def ConsolePrintMessage(msg as string):
		Console.ForegroundColor = _interpreterColor if not _disableColors
		print msg
		Console.ResetColor() if not _disableColors

	private def ConsolePrintException(e as Exception):
		Console.ForegroundColor = _exceptionColor if not _disableColors
		print e
		Console.ResetColor() if not _disableColors

	private def ConsolePrintError(msg as string):
		Console.ForegroundColor = _exceptionColor if not _disableColors
		print msg
		Console.ResetColor() if not _disableColors

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

		i = 0

		Array.Sort(_suggestions) if _suggestions isa (string)
		Array.Sort[of IEntity](_suggestions, _entityNameComparer) if _suggestions isa (IEntity)

		for s in _suggestions as (object):
			Console.ForegroundColor = _suggestionsColor if not _disableColors
			Console.Write(", ") if i > 0
			if i > 20: #TODO: maxcandidates pref + paging?
				Console.Write("... (too much candidates)")
				break
			if i == _selectedSuggIdx:
				Console.ForegroundColor = _selectedSuggestionColor if not _disableColors
			if s isa IEntity:
				Console.Write(DescribeEntity(s as IEntity))
			else:
				Console.Write(s)
			i++

		Console.ResetColor() if not _disableColors
		#Console.CursorTop = cursorTop
		Console.Write(Environment.NewLine)
		ConsolePrintPrompt()
		Console.Write(_line.ToString())
		Console.CursorLeft = cursorLeft

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

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

	def DisplaySuggestions(query as string):
		return if DisableAutocompletion

		#TODO: FIXME: refactor to one regex?
		p_open = re_open.Matches(query).Count
		p_close = re_close.Matches(query).Count
		if p_open > p_close:
			query = query.Split(" ,(\t".ToCharArray(), 100)[-1]
		else:
			query = query.Split(" ,\t".ToCharArray(), 100)[-1]
		if query.LastIndexOf('.') > 0:
			codeToComplete = query[0:query.LastIndexOf('.')+1]
			_suggestions = SuggestCodeCompletion(codeToComplete+"__codecomplete__")
			_filter = query[query.LastIndexOf('.')+1:]
			_suggestions = array(e for e in _suggestions as (IEntity)
								unless not e.Name.StartsWith(_filter)) as (IEntity)

		if not _suggestions or 0 == len(_suggestions): #suggest a  var
			_filter = query
			_suggestions = array(var.ToString() for var in _values.Keys
									unless not var.ToString().StartsWith(_filter))

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

	def AutoComplete():
		raise InvalidOperationException("no suggestions") if _suggestions is null or _selectedSuggIdx is null

		s = _suggestions[_selectedSuggIdx.Value] as IEntity
		completion = (s.Name[len(_filter):] if s is not null else (_suggestions[_selectedSuggIdx.Value] as string)[len(_filter):])
		Write(completion)
		if s and s.EntityType == EntityType.Method:
			Write("(")
			m = s as IMethod
			Write(')') if m.GetParameters().Length == 0

		_selectedSuggIdx = null
		_suggestions = null

	private _beforeHistory = string.Empty

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

	def ConsoleLoopEval():
		Console.CursorVisible = true
		ConsolePrintPrompt()

		lastChar = char('0')
		key = ConsoleKey.LeftArrow
		while not _quit:
			cki = Console.ReadKey(true)
			key = cki.Key
			keyChar = cki.KeyChar
			control = false

			newLine = keyChar in Environment.NewLine

			if char.IsControl(keyChar):
				control = true
				if keyChar == char('\t'):
					test = char(' ')
					test = _line.ToString()[_line.Length-1] if _line.Length > 0
					if char.IsLetterOrDigit(test) or test == char('.'):
						_selectedSuggIdx = 0
						DisplaySuggestions(_line.ToString())
					else:
						Write("    ")

				#line-editing support
				if not _multiline:
					if key == ConsoleKey.Backspace:
						if Console.CursorLeft > len(CurrentPrompt) and _line.Length > 0:
							cx = Console.CursorLeft-len(CurrentPrompt)-1
							_line.Remove(cx, 1)
							cx2 = --Console.CursorLeft
							Console.Write("${_line.ToString(cx, _line.Length-cx)} ")
							Console.CursorLeft = cx2
					elif key == ConsoleKey.Delete:
						if Console.CursorLeft >= len(CurrentPrompt) and _line.Length > 0:
							cx = Console.CursorLeft-len(CurrentPrompt)
							if cx < _line.Length:
								_line.Remove(cx, 1)
								cx2 = Console.CursorLeft
								Console.Write("${_line.ToString(cx, _line.Length-cx)} ")
								Console.CursorLeft = cx2
					elif key == ConsoleKey.LeftArrow:
						if Console.CursorLeft > len(CurrentPrompt) and _line.Length > 0:
							Console.CursorLeft--
					elif key == ConsoleKey.RightArrow:
						if Console.CursorLeft < (len(CurrentPrompt)+_line.Length):
							Console.CursorLeft++
					elif key == ConsoleKey.Home:
						Console.CursorLeft = len(CurrentPrompt)
					elif key == ConsoleKey.End:
						Console.CursorLeft = len(CurrentPrompt) + _line.Length

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

				#auto-completion support
				if CanAutoComplete:
					if key == ConsoleKey.LeftArrow:
						if _selectedSuggIdx > 0:
							_selectedSuggIdx--
						else:
							_selectedSuggIdx = len(_suggestions)
						DisplaySuggestions(_line.ToString())
					elif key == ConsoleKey.RightArrow:
						if _selectedSuggIdx <= len(_suggestions):
							_selectedSuggIdx++
						else:
							_selectedSuggIdx = 0
						DisplaySuggestions(_line.ToString())
					if newLine:
						AutoComplete()
						continue
				if not newLine:
					continue

			_selectedSuggIdx = null

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

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

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

				line = _line.ToString()

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

					try:
						if len(line) > 0:
							_indent++ if line[len(line)-1] in _blockStarters
							_indent-- if line.EndsWith(keyChar.ToString()) and _indent > 0
						elif _indent > 0:
							_indent--
						if _indent == 0:
							InternalLoopEval(_buffer.ToString())
							_buffer.Length = 0 #truncate buffer
					except x as System.Reflection.TargetInvocationException:
						ConsolePrintException(x.InnerException)
					except x:
						ConsolePrintException(x)

					_line.Length = 0 #truncate line
					ConsolePrintPrompt()

			lastChar = keyChar

		SaveHistory()
		DisplayGoodbye()

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

		cmd = line.Split()

		if len(cmd) == 1:
			if cmd[0] == "/q" or cmd[0] == "/quit":
				quit()
			elif cmd[0] == "/?" or cmd[0] == "/h" or cmd[0] == "/help":
				DisplayHelp()
				_selectedSuggIdx = null
				_buffer.Length = 0
				ConsolePrintPrompt()
			elif cmd[0] == "/g" or cmd[0] == "/globals":
				globals()
			else:
				return false

		elif len(cmd) == 2:
			if cmd[0] == "/l" or cmd[0] == "/load":
				load(cmd[1])
				_line.Length = 0
				ConsolePrintPrompt()
			elif cmd[0] == "/s" or cmd[0] == "/save":
				save(cmd[1])
				_line.Length = 0
				ConsolePrintPrompt()
			else:
				return false

		else:
			return false
		return true

	private _indent as int = 0

	def LoopEval(code as string):
		using console = ConsoleCapture():
			result = InternalLoopEval(code)
			for line in System.IO.StringReader(console.ToString()):
				_print(line)
		return result

	private def InternalLoopEval(code as string):
		result = self.Eval(code)
		if ShowWarnings:
			self.DisplayProblems(result.Warnings)
		if not self.DisplayProblems(result.Errors):
			ProcessLastValue()
			_session.Add(code)
		return result

	private def ProcessLastValue():
		_ = self.LastValue
		if _ is not null:
			_print(repr(_))
			SetValue("_", _)

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

	override def SetLastValue(value):
		_lastValue = value

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

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

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

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

	def DisplayProblems(problems as ICollection):
		return if problems is null or problems.Count == 0
		Console.ForegroundColor = _exceptionColor if not _disableColors
		for problem as duck in problems:
			markLocation(problem.LexicalInfo)
			type = ("WARNING", "ERROR")[problem isa CompilerError]
			_print("${type}: ${problem.Message}")
		Console.ResetColor() if not _disableColors
		if problems.Count > 0:
			return true
		return false

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

	private def InitializeStandardReferences():
		SetValue("interpreter", self)
		SetValue("dir", dir)
		SetValue("help", help)
		SetValue("print", { value | _print(value) })
		SetValue("load", load)
		SetValue("globals", globals)
		SetValue("getRootNamespace", Namespace.GetRootNamespace)

	def DisplayHelp():
		Console.ForegroundColor = _interpreterColor	if not _disableColors
		print """Welcome to booish, an interpreter for the boo programming language.
Running boo ${BooVersion} in CLR v${Environment.Version}.

The following builtin functions are available:
    /d[ir] type : lists the members of a type
    /h[elp] type : prints detailed information about a type
    /l[oad] file : evals an external boo file
    /s[ave] file : writes your current booish session into file
    /g[lobals] : returns names of all variables known to interpreter
    /n[ames] : namespace navigation
    /q[uit] : exits the interpreter

Enter boo code in the prompt below."""
		Console.ResetColor() if not _disableColors

	def DisplayGoodbye():	// booish is friendly
		Console.ForegroundColor = _interpreterColor if not _disableColors
		print ""
		print "All your boo are belong to us!"
		Console.ResetColor() if not _disableColors

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

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

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

	def globals():
		return array(string, _values.Keys)

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

	def load([required] path as string):
		if path.EndsWith(".boo"):
			ConsolePrintMessage("Evaluating '${path}' ...")
			result = EvalCompilerInput(FileInput(path))
			if ShowWarnings:
				DisplayProblems(result.Warnings)
			if not DisplayProblems(result.Errors):
				ProcessLastValue()
		else:
			try:
				ConsolePrintMessage("Adding reference to '${path}'")
				References.Add(System.Reflection.Assembly.LoadFrom(path))
			except e:
				ConsolePrintError(e.Message)

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

	def help(obj):
		type = (obj as Type) or obj.GetType()
		DescribeType("    ", type)

	private _quit = false
	def quit():
		_quit = true

	def DescribeType(indent as string, type as Type):
		if type.IsInterface:
			typeDef = "interface"
			baseTypes = array(GetBooTypeName(t) for t in type.GetInterfaces())
		else:
			typeDef = "class"
			baseTypes = (GetBooTypeName(type.BaseType),) + array(GetBooTypeName(t) for t in type.GetInterfaces())

		_print("${typeDef} ${type.Name}(${join(baseTypes, ', ')}):")
		_print("")

		for ctor in type.GetConstructors():
			_print("${indent}def constructor(${DescribeParameters(ctor.GetParameters())})")
			_print("")

		sortByName = def (lhs as Reflection.MemberInfo, rhs as Reflection.MemberInfo):
			return lhs.Name.CompareTo(rhs.Name)

		for f as Reflection.FieldInfo in List(type.GetFields()).Sort(sortByName):
			_print("${indent}public ${DescribeField(f)}")
			_print("")

		for p as Reflection.PropertyInfo in List(type.GetProperties()).Sort(sortByName):
			_print("${indent}${DescribeProperty(p)}:")
			_print("${indent}${indent}get") if p.GetGetMethod() is not null
			_print("${indent}${indent}set") if p.GetSetMethod() is not null
			_print("")

		for m as Reflection.MethodInfo in List(type.GetMethods()).Sort(sortByName):
			continue if m.IsSpecialName
			_print("${indent}${DescribeMethod(m)}")
			_print("")

		for e as Reflection.EventInfo in List(type.GetEvents()).Sort(sortByName):
			_print("${indent}${DescribeEvent(e)}")
			_print("")

	static def DescribeEntity(entity as IEntity):
		method = entity as ExternalMethod
		if method is not null:
			return InteractiveInterpreter.DescribeMethod(method.MethodInfo)
		field = entity as ExternalField
		if field is not null:
			return InteractiveInterpreter.DescribeField(field.FieldInfo)
		property = entity as ExternalProperty
		if property is not null:
			return InteractiveInterpreter.DescribeProperty(property.PropertyInfo)
		e = entity as ExternalEvent
		if e is not null:
			return InteractiveInterpreter.DescribeEvent(e.EventInfo)
		return entity.ToString()

	static def DescribeEvent(e as Reflection.EventInfo):
		return "${DescribeModifiers(e)}event ${e.Name} as ${e.EventHandlerType}"

	static def DescribeProperty(p as Reflection.PropertyInfo):
		modifiers = DescribeModifiers(p)
		params = DescribePropertyParameters(p.GetIndexParameters())
		return "${modifiers}${p.Name}${params} as ${GetBooTypeName(p.PropertyType)}"

	static def DescribeField(f as Reflection.FieldInfo):
		return "${DescribeModifiers(f)}${f.Name} as ${GetBooTypeName(f.FieldType)}"

	static def DescribeMethod(m as Reflection.MethodInfo):
		returnType = GetBooTypeName(m.ReturnType)
		modifiers = DescribeModifiers(m)
		return "${modifiers}def ${m.Name}(${DescribeParameters(m.GetParameters())}) as ${returnType}"

	static def DescribeModifiers(f as Reflection.FieldInfo):
		return "static " if f.IsStatic
		return ""

	static def DescribeModifiers(m as Reflection.MethodBase):
		return "static " if m.IsStatic
		return ""

	static def DescribeModifiers(e as Reflection.EventInfo):
		return DescribeModifiers(e.GetAddMethod(true) or e.GetRemoveMethod(true))

	static def DescribeModifiers(p as Reflection.PropertyInfo):
		accessor = p.GetGetMethod(true) or p.GetSetMethod(true)
		return DescribeModifiers(accessor)

	static def DescribePropertyParameters(parameters as (Reflection.ParameterInfo)):
		return "" if 0 == len(parameters)
		return "(${DescribeParameters(parameters)})"

	static def DescribeParameters(parameters as (Reflection.ParameterInfo)):
		return join(DescribeParameter(p) for p in parameters, ", ")

	static def DescribeParameter(p as Reflection.ParameterInfo):
		return "${p.Name} as ${GetBooTypeName(p.ParameterType)}"

	static def GetBooTypeName(type as System.Type) as string:
		return "(${GetBooTypeName(type.GetElementType())})" if type.IsArray
		return "object" if object is type
		return "string" if string is type
		return "void" if void is type
		return "bool" if bool is type
		return "byte" if byte is type
		return "char" if char is type
		return "sbyte" if sbyte is type
		return "short" if short is type
		return "ushort" if ushort is type
		return "int" if int is type
		return "uint" if uint is type
		return "long" if long is type
		return "ulong" if ulong is type
		return "single" if single is type
		return "double" if double is type
		return "date" if date is type
		return "timespan" if timespan is type
		return "regex" if regex is type
		return type.FullName

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

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

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

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

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

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

		AddRepresenter(IDictionary) do (value as IDictionary, writer as TextWriter):
			writer.Write("{")
			i = 0
			for key in value.Keys:
				writer.Write(", ") if i
				repr(key, writer)
				writer.Write(": ")
				repr(value[key], writer)
				++i
			writer.Write("}")

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

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

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

	callable Representer(value, writer as TextWriter)

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

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

class EntityNameComparer(IComparer of IEntity):
	def Compare(a as IEntity, b as IEntity) as int:
		return string.Compare(a.Name, b.Name)

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

namespace Boo.Lang.Interpreter

import System
import System.Collections
import System.Collections.Generic
import System.IO
import System.Text
import System.Text.RegularExpressions
import Boo.Lang
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.TypeSystem
import Boo.Lang.Compiler.IO

class InteractiveInterpreter2(AbstractInterpreter):

	_values = {}

	_declarations = {}

	_representers = []

	[property(ShowWarnings)]
	_showWarnings = false

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

	[getter(LastValue)]
	_lastValue

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

	_entityNameComparer = EntityNameComparer()

	def constructor():
		super()
		Initialize()

	def constructor(parser as ICompilerStep):
		super(parser)
		Initialize()

	def Initialize():
		_disableColors = true if Environment.GetEnvironmentVariable("BOOISH_DISABLE_COLORS") is not null
		_disableColors = true if Environment.GetEnvironmentVariable("INSIDE_EMACS") is not null
		_disableAutocompletion = true if Environment.GetEnvironmentVariable("BOOISH_DISABLE_AUTOCOMPLETION") is not null
		InitializeStandardReferences()
		LoadHistory()

	def Reset():
		_values.Clear()
		_declarations.Clear()
		_lastValue = null
		_indent = 0
		InitializeStandardReferences()

	public final static HISTORY_FILENAME = "booish_history"
	public final static HISTORY_CAPACITY = 100
	protected _history = System.Collections.Generic.Queue of string(HISTORY_CAPACITY)
	protected _historyFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), HISTORY_FILENAME)
	protected _historyIndex = 0
	protected _session = System.Collections.Generic.List of string()

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

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

	[property(DefaultPrompt)]
	_defaultPrompt = ">>>"

	[property(BlockPrompt)]
	_blockPrompt = "..."

	[property(DisableColors)]
	_disableColors = false

	[property(DisableAutocompletion)]
	_disableAutocompletion = false

	[property(InterpreterColor)] # messages from the interpreter (not from user code)
	_interpreterColor = ConsoleColor.Gray

	[property(PromptColor)]
	_promptColor = ConsoleColor.Green

	[property(ExceptionColor)]
	_exceptionColor = ConsoleColor.Red

	[property(SuggestionsColor)]
	_suggestionsColor = ConsoleColor.Yellow

	[property(SelectedSuggestionColor)]
	_selectedSuggestionColor = ConsoleColor.Magenta

	[property(SelectedSuggestionIndex)]
	_selectedSuggIdx as int?

	[property(Suggestions)]
	_suggestions as (object)

	CanAutoComplete as bool:
		get:
			return _selectedSuggIdx is not null

	private _builtins as (IEntity)
	private _filter as string

	private def ConsolePrintPrompt():
		Console.ForegroundColor = _promptColor if not _disableColors
		Console.Write(CurrentPrompt)
		#TODO: automatic indentation option ?
		#for i in range(_indent):
		#	_line.Append("\t")
		#	Console.Write("\t")
		Console.ResetColor() if not _disableColors

	private def ConsolePrintMessage(msg as string):
		Console.ForegroundColor = _interpreterColor if not _disableColors
		print msg
		Console.ResetColor() if not _disableColors

	private def ConsolePrintException(e as Exception):
		Console.ForegroundColor = _exceptionColor if not _disableColors
		print e
		Console.ResetColor() if not _disableColors

	private def ConsolePrintError(msg as string):
		Console.ForegroundColor = _exceptionColor if not _disableColors
		print msg
		Console.ResetColor() if not _disableColors

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

		i = 0

		Array.Sort(_suggestions) if _suggestions isa (string)
		Array.Sort[of IEntity](_suggestions, _entityNameComparer) if _suggestions isa (IEntity)

		for s in _suggestions as (object):
			Console.ForegroundColor = _suggestionsColor if not _disableColors
			Console.Write(", ") if i > 0
			if i > 20: #TODO: maxcandidates pref + paging?
				Console.Write("... (too much candidates)")
				break
			if i == _selectedSuggIdx:
				Console.ForegroundColor = _selectedSuggestionColor if not _disableColors
			if s isa IEntity:
				Console.Write(DescribeEntity(s as IEntity))
			else:
				Console.Write(s)
			i++

		Console.ResetColor() if not _disableColors
		#Console.CursorTop = cursorTop
		Console.Write(Environment.NewLine)
		ConsolePrintPrompt()
		Console.Write(_line.ToString())
		Console.CursorLeft = cursorLeft

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

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

	def DisplaySuggestions(query as string):
		return if DisableAutocompletion

		#TODO: FIXME: refactor to one regex?
		p_open = re_open.Matches(query).Count
		p_close = re_close.Matches(query).Count
		if p_open > p_close:
			query = query.Split(" ,(\t".ToCharArray(), 100)[-1]
		else:
			query = query.Split(" ,\t".ToCharArray(), 100)[-1]
		if query.LastIndexOf('.') > 0:
			codeToComplete = query[0:query.LastIndexOf('.')+1]
			_suggestions = SuggestCodeCompletion(codeToComplete+"__codecomplete__")
			_filter = query[query.LastIndexOf('.')+1:]
			_suggestions = array(e for e in _suggestions as (IEntity)
								unless not e.Name.StartsWith(_filter)) as (IEntity)

		if not _suggestions or 0 == len(_suggestions): #suggest a  var
			_filter = query
			_suggestions = array(var.ToString() for var in _values.Keys
									unless not var.ToString().StartsWith(_filter))

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

	def AutoComplete():
		raise InvalidOperationException("no suggestions") if _suggestions is null or _selectedSuggIdx is null

		s = _suggestions[_selectedSuggIdx.Value] as IEntity
		completion = (s.Name[len(_filter):] if s is not null else (_suggestions[_selectedSuggIdx.Value] as string)[len(_filter):])
		Write(completion)
		if s and s.EntityType == EntityType.Method:
			Write("(")
			m = s as IMethod
			Write(')') if m.GetParameters().Length == 0

		_selectedSuggIdx = null
		_suggestions = null

	private _beforeHistory = string.Empty

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

	def ConsoleLoopEval():
		Console.CursorVisible = true
		ConsolePrintPrompt()

		lastChar = char('0')
		key = ConsoleKey.LeftArrow
		while not _quit:
			cki = Console.ReadKey(true)
			key = cki.Key
			keyChar = cki.KeyChar
			control = false

			newLine = keyChar in Environment.NewLine

			if char.IsControl(keyChar):
				control = true
				if keyChar == char('\t'):
					test = char(' ')
					test = _line.ToString()[_line.Length-1] if _line.Length > 0
					if char.IsLetterOrDigit(test) or test == char('.'):
						_selectedSuggIdx = 0
						DisplaySuggestions(_line.ToString())
					else:
						Write("    ")

				#line-editing support
				if not _multiline:
					if key == ConsoleKey.Backspace:
						if Console.CursorLeft > len(CurrentPrompt) and _line.Length > 0:
							cx = Console.CursorLeft-len(CurrentPrompt)-1
							_line.Remove(cx, 1)
							cx2 = --Console.CursorLeft
							Console.Write("${_line.ToString(cx, _line.Length-cx)} ")
							Console.CursorLeft = cx2
					elif key == ConsoleKey.Delete:
						if Console.CursorLeft >= len(CurrentPrompt) and _line.Length > 0:
							cx = Console.CursorLeft-len(CurrentPrompt)
							if cx < _line.Length:
								_line.Remove(cx, 1)
								cx2 = Console.CursorLeft
								Console.Write("${_line.ToString(cx, _line.Length-cx)} ")
								Console.CursorLeft = cx2
					elif key == ConsoleKey.LeftArrow:
						if Console.CursorLeft > len(CurrentPrompt) and _line.Length > 0:
							Console.CursorLeft--
					elif key == ConsoleKey.RightArrow:
						if Console.CursorLeft < (len(CurrentPrompt)+_line.Length):
							Console.CursorLeft++
					elif key == ConsoleKey.Home:
						Console.CursorLeft = len(CurrentPrompt)
					elif key == ConsoleKey.End:
						Console.CursorLeft = len(CurrentPrompt) + _line.Length

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

				#auto-completion support
				if CanAutoComplete:
					if key == ConsoleKey.LeftArrow:
						if _selectedSuggIdx > 0:
							_selectedSuggIdx--
						else:
							_selectedSuggIdx = len(_suggestions)
						DisplaySuggestions(_line.ToString())
					elif key == ConsoleKey.RightArrow:
						if _selectedSuggIdx <= len(_suggestions):
							_selectedSuggIdx++
						else:
							_selectedSuggIdx = 0
						DisplaySuggestions(_line.ToString())
					if newLine:
						AutoComplete()
						continue
				if not newLine:
					continue

			_selectedSuggIdx = null

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

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

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

				line = _line.ToString()

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

					try:
						if len(line) > 0:
							_indent++ if line[len(line)-1] in _blockStarters
							_indent-- if line.EndsWith(keyChar.ToString()) and _indent > 0
						elif _indent > 0:
							_indent--
						if _indent == 0:
							InternalLoopEval(_buffer.ToString())
							_buffer.Length = 0 #truncate buffer
					except x as System.Reflection.TargetInvocationException:
						ConsolePrintException(x.InnerException)
					except x:
						ConsolePrintException(x)

					_line.Length = 0 #truncate line
					ConsolePrintPrompt()

			lastChar = keyChar

		SaveHistory()
		DisplayGoodbye()

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

		cmd = line.Split()

		if len(cmd) == 1:
			if cmd[0] == "/q" or cmd[0] == "/quit":
				quit()
			elif cmd[0] == "/?" or cmd[0] == "/h" or cmd[0] == "/help":
				DisplayHelp()
				_selectedSuggIdx = null
				_buffer.Length = 0
				ConsolePrintPrompt()
			elif cmd[0] == "/g" or cmd[0] == "/globals":
				globals()
			else:
				return false

		elif len(cmd) == 2:
			if cmd[0] == "/l" or cmd[0] == "/load":
				load(cmd[1])
				_line.Length = 0
				ConsolePrintPrompt()
			elif cmd[0] == "/s" or cmd[0] == "/save":
				save(cmd[1])
				_line.Length = 0
				ConsolePrintPrompt()
			else:
				return false

		else:
			return false
		return true

	private _indent as int = 0

	def LoopEval(code as string):
		using console = ConsoleCapture():
			result = InternalLoopEval(code)
			for line in System.IO.StringReader(console.ToString()):
				_print(line)
		return result

	private def InternalLoopEval(code as string):
		result = self.Eval(code)
		if ShowWarnings:
			self.DisplayProblems(result.Warnings)
		if not self.DisplayProblems(result.Errors):
			ProcessLastValue()
			_session.Add(code)
		return result

	private def ProcessLastValue():
		_ = self.LastValue
		if _ is not null:
			_print(repr(_))
			SetValue("_", _)

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

	override def SetLastValue(value):
		_lastValue = value

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

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

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

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

	def DisplayProblems(problems as ICollection):
		return if problems is null or problems.Count == 0
		Console.ForegroundColor = _exceptionColor if not _disableColors
		for problem as duck in problems:
			markLocation(problem.LexicalInfo)
			type = ("WARNING", "ERROR")[problem isa CompilerError]
			_print("${type}: ${problem.Message}")
		Console.ResetColor() if not _disableColors
		if problems.Count > 0:
			return true
		return false

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

	private def InitializeStandardReferences():
		SetValue("interpreter", self)
		SetValue("dir", dir)
		SetValue("help", help)
		SetValue("print", { value | _print(value) })
		SetValue("load", load)
		SetValue("globals", globals)
		SetValue("getRootNamespace", Namespace.GetRootNamespace)

	def DisplayHelp():
		Console.ForegroundColor = _interpreterColor	if not _disableColors
		print """Welcome to booish, an interpreter for the boo programming language.
Running boo ${BooVersion} in CLR v${Environment.Version}.

The following builtin functions are available:
    /d[ir] type : lists the members of a type
    /h[elp] type : prints detailed information about a type
    /l[oad] file : evals an external boo file
    /s[ave] file : writes your current booish session into file
    /g[lobals] : returns names of all variables known to interpreter
    /n[ames] : namespace navigation
    /q[uit] : exits the interpreter

Enter boo code in the prompt below."""
		Console.ResetColor() if not _disableColors

	def DisplayGoodbye():	// booish is friendly
		Console.ForegroundColor = _interpreterColor if not _disableColors
		print ""
		print "All your boo are belong to us!"
		Console.ResetColor() if not _disableColors

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

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

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

	def globals():
		return array(string, _values.Keys)

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

	def load([required] path as string):
		if path.EndsWith(".boo"):
			ConsolePrintMessage("Evaluating '${path}' ...")
			result = EvalCompilerInput(FileInput(path))
			if ShowWarnings:
				DisplayProblems(result.Warnings)
			if not DisplayProblems(result.Errors):
				ProcessLastValue()
		else:
			try:
				ConsolePrintMessage("Adding reference to '${path}'")
				References.Add(System.Reflection.Assembly.LoadFrom(path))
			except e:
				ConsolePrintError(e.Message)

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

	def help(obj):
		type = (obj as Type) or obj.GetType()
		DescribeType("    ", type)

	private _quit = false
	def quit():
		_quit = true

	def DescribeType(indent as string, type as Type):
		if type.IsInterface:
			typeDef = "interface"
			baseTypes = array(GetBooTypeName(t) for t in type.GetInterfaces())
		else:
			typeDef = "class"
			baseTypes = (GetBooTypeName(type.BaseType),) + array(GetBooTypeName(t) for t in type.GetInterfaces())

		_print("${typeDef} ${type.Name}(${join(baseTypes, ', ')}):")
		_print("")

		for ctor in type.GetConstructors():
			_print("${indent}def constructor(${DescribeParameters(ctor.GetParameters())})")
			_print("")

		sortByName = def (lhs as Reflection.MemberInfo, rhs as Reflection.MemberInfo):
			return lhs.Name.CompareTo(rhs.Name)

		for f as Reflection.FieldInfo in List(type.GetFields()).Sort(sortByName):
			_print("${indent}public ${DescribeField(f)}")
			_print("")

		for p as Reflection.PropertyInfo in List(type.GetProperties()).Sort(sortByName):
			_print("${indent}${DescribeProperty(p)}:")
			_print("${indent}${indent}get") if p.GetGetMethod() is not null
			_print("${indent}${indent}set") if p.GetSetMethod() is not null
			_print("")

		for m as Reflection.MethodInfo in List(type.GetMethods()).Sort(sortByName):
			continue if m.IsSpecialName
			_print("${indent}${DescribeMethod(m)}")
			_print("")

		for e as Reflection.EventInfo in List(type.GetEvents()).Sort(sortByName):
			_print("${indent}${DescribeEvent(e)}")
			_print("")

	static def DescribeEntity(entity as IEntity):
		method = entity as ExternalMethod
		if method is not null:
			return InteractiveInterpreter.DescribeMethod(method.MethodInfo)
		field = entity as ExternalField
		if field is not null:
			return InteractiveInterpreter.DescribeField(field.FieldInfo)
		property = entity as ExternalProperty
		if property is not null:
			return InteractiveInterpreter.DescribeProperty(property.PropertyInfo)
		e = entity as ExternalEvent
		if e is not null:
			return InteractiveInterpreter.DescribeEvent(e.EventInfo)
		return entity.ToString()

	static def DescribeEvent(e as Reflection.EventInfo):
		return "${DescribeModifiers(e)}event ${e.Name} as ${e.EventHandlerType}"

	static def DescribeProperty(p as Reflection.PropertyInfo):
		modifiers = DescribeModifiers(p)
		params = DescribePropertyParameters(p.GetIndexParameters())
		return "${modifiers}${p.Name}${params} as ${GetBooTypeName(p.PropertyType)}"

	static def DescribeField(f as Reflection.FieldInfo):
		return "${DescribeModifiers(f)}${f.Name} as ${GetBooTypeName(f.FieldType)}"

	static def DescribeMethod(m as Reflection.MethodInfo):
		returnType = GetBooTypeName(m.ReturnType)
		modifiers = DescribeModifiers(m)
		return "${modifiers}def ${m.Name}(${DescribeParameters(m.GetParameters())}) as ${returnType}"

	static def DescribeModifiers(f as Reflection.FieldInfo):
		return "static " if f.IsStatic
		return ""

	static def DescribeModifiers(m as Reflection.MethodBase):
		return "static " if m.IsStatic
		return ""

	static def DescribeModifiers(e as Reflection.EventInfo):
		return DescribeModifiers(e.GetAddMethod(true) or e.GetRemoveMethod(true))

	static def DescribeModifiers(p as Reflection.PropertyInfo):
		accessor = p.GetGetMethod(true) or p.GetSetMethod(true)
		return DescribeModifiers(accessor)

	static def DescribePropertyParameters(parameters as (Reflection.ParameterInfo)):
		return "" if 0 == len(parameters)
		return "(${DescribeParameters(parameters)})"

	static def DescribeParameters(parameters as (Reflection.ParameterInfo)):
		return join(DescribeParameter(p) for p in parameters, ", ")

	static def DescribeParameter(p as Reflection.ParameterInfo):
		return "${p.Name} as ${GetBooTypeName(p.ParameterType)}"

	static def GetBooTypeName(type as System.Type) as string:
		return "(${GetBooTypeName(type.GetElementType())})" if type.IsArray
		return "object" if object is type
		return "string" if string is type
		return "void" if void is type
		return "bool" if bool is type
		return "byte" if byte is type
		return "char" if char is type
		return "sbyte" if sbyte is type
		return "short" if short is type
		return "ushort" if ushort is type
		return "int" if int is type
		return "uint" if uint is type
		return "long" if long is type
		return "ulong" if ulong is type
		return "single" if single is type
		return "double" if double is type
		return "date" if date is type
		return "timespan" if timespan is type
		return "regex" if regex is type
		return type.FullName

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

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

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

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

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

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

		AddRepresenter(IDictionary) do (value as IDictionary, writer as TextWriter):
			writer.Write("{")
			i = 0
			for key in value.Keys:
				writer.Write(", ") if i
				repr(key, writer)
				writer.Write(": ")
				repr(value[key], writer)
				++i
			writer.Write("}")

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

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

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

	callable Representer(value, writer as TextWriter)

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

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

class EntityNameComparer(IComparer of IEntity):
	def Compare(a as IEntity, b as IEntity) as int:
		return string.Compare(a.Name, b.Name)


Fix BeginInvoke() problem

Fix BeginInvoke() problem

git-svn-id: bab0c2de90ee11a20376049c2cdee62584491f34@1075 73e77b4c-caa6-f847-a29a-24ab75ae54b6

// Copyright 2004-2005 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
namespace Castle.MonoRail.Views.Brail

import System
import System.IO
import System.Collections
import Castle.MonoRail.Framework
import Boo.Lang.Compiler
import Boo.Lang.Compiler.IO
import Boo.Lang.Compiler.Steps
import Boo.Lang.Compiler.Pipelines

# This is the class that is responsible to take a template name and turn it into an
# executable code that then is piped to the user.
public class BooViewEngine (ViewEngineBase):
	compilations = Hashtable.Synchronized(Hashtable(CaseInsensitiveHashCodeProvider.Default,
			CaseInsensitiveComparer.Default))

	# This is used to add a reference to the common scripts for each compiled scripts
	common as System.Reflection.Assembly
	# Watch the common script files directory
	commonScriptWatcher as FileSystemWatcher
	# Watch the Views directory for changes
	viewsScriptWatcher as FileSystemWatcher

	static options as BooViewEngineOptions
	baseSavePath as string
	commonScriptPath as string
	fullPathToViewDir as string

	static def InitializeConfig():
		InitializeConfig("brail")
		InitializeConfig("Brail") if options is null
		options = BooViewEngineOptions() if options is null

	static def InitializeConfig(name as string):
		options = System.Configuration.ConfigurationSettings.GetConfig(name)

	# Get configuration options if they exists, if they do not exist, load the default ones
	# Create directory to save the compiled assemblies if required.
	# pre-compile the common scripts
	override def Init():
		InitializeConfig() if options is null
		baseDir = Path.GetDirectoryName(typeof(BooViewEngine).Assembly.Location)
		self.baseSavePath = Path.Combine(baseDir,options.SaveDirectory)
		self.commonScriptPath = Path.Combine(ViewRootDir, options.CommonScriptsDirectory)
		self.fullPathToViewDir = Path.GetFullPath(ViewRootDir)
		if options.SaveToDisk and not Directory.Exists(baseSavePath):
			Directory.CreateDirectory(baseSavePath)

		viewsScriptWatcher = FileSystemWatcher(ViewRootDir,
			IncludeSubdirectories: true, Filter: "*.boo")
		viewsScriptWatcher.Changed += def (source, e as FileSystemEventArgs):
			file = Path.GetFullPath(e.FullPath)
			# Will cause a recompilation
			compilations[file] = null

		viewsScriptWatcher.EnableRaisingEvents = true

		if not CompileCommonScripts():
			return

		commonScriptWatcher = FileSystemWatcher(commonScriptPath,
			IncludeSubdirectories: false, Filter:"*.boo")
		commonScriptWatcher.Changed += def (source, e as FileSystemEventArgs):
			CompileCommonScripts.BeginInvoke( null,
				{ ar as IAsyncResult | CompileCommonScripts.EndInvoke(ar) } )

		commonScriptWatcher.EnableRaisingEvents = true

	# Just check if the filename exists, I'm not sure when it's called
	override def HasTemplate(templateName as string):
		file = GetFileName(templateName)
		exists = File.Exists(file)
		return exists

	# Process a template name and output the results to the user
	# This may throw if an error occured and the user is not local (which would
	# cause the yellow screen of death) if the user is local, then a detailed stack
	# is shown
	override def Process(context as IRailsEngineContext, controller as Controller, templateName as string):
		try:
			file = GetFileName(templateName)
			view as BrailBase
			# Output may be the layout's child output if a layout exists
			# or the context.Response.Output if the layout is null
			output as TextWriter, layout as BrailBase = GetOutput(context, controller)
			# Will compile on first time, then save the assembly on the cache.
			view = GetCompiledScriptInstance(file, output, context, controller)
			controller.PreSendView(view)
			view.Run()
			layout.Run() if layout is not null
			controller.PostSendView(view)
		except e:
			HandleError(context, controller, e, "Cannot load view ${templateName}")

	# Send the contents text directly to the user, only adding the layout if neccecary
	override def ProcessContents(context as IRailsEngineContext, controller as Controller, contents as string):
		try:
			output as TextWriter, layout as BrailBase = GetOutput(context, controller)
			output.Write(contents)
			layout.Run() if layout is not null
		except e:
			HandleError(context, controller, e, "")

	# Throw if the user is not local, otherwise send detailed error message
	def HandleError(context as IRailsEngineContext, controller as Controller, e as Exception, msg as string):
		if not context.Request.IsLocal:
				raise RailsException(msg)
		error = OutputError(self, context.Response.Output,context, controller, e)
		error.Run()

	# Check if a layout has been defined. If it was, then the layout would be created
	# and will take over the output, otherwise, the context.Reposne.Output is used,
	# and layout is null
	def GetOutput(context as IRailsEngineContext, controller as Controller):
		layout as BrailBase
		output as TextWriter = context.Response.Output
		if controller.LayoutName is not null:
			layoutTemplate = "layouts/${controller.LayoutName}"
			layoutFilename = GetFileName(layoutTemplate)
			layout = GetCompiledScriptInstance(layoutFilename,output,
				context, controller)
			output = layout.ChildOutput = StringWriter()
		return output, layout

	def GetFileName(templateName as string):
		filename = Path.Combine(self.ViewRootDir,templateName)+".boo"
		return Path.GetFullPath(filename)

	# This takes a filename and return an instance of the view ready to be used.
	# If the file does not exist, an exception is raised
	# The cache is checked to see if the file has already been compiled, and it had been
	# a check is made to see that the compiled instance is newer then the file's modification date.
	# If the file has not been compiled, or the version on disk is newer than the one in memory, a new
	# version is compiled.
	# Finally, an instance is created and returned
	def GetCompiledScriptInstance(file as string,
		output as TextWriter,
		context as IRailsEngineContext,
		controller as Controller) as BrailBase:

		batch as bool = options.BatchCompile
		if  compilations.ContainsKey(file):
			type as Type = compilations[file]
			if type is not null:
				return type(self, output, context, controller)
			# if file is in compilations and the type is null,
			# this means that we need to recompile. Since this usually means that
			# the file was changed, we'll set batch to false and procceed to compile just
			# this file.
			batch = false

		type = CompileScript(file, batch)
		return type(self, output,context, controller)

	# Compile a script (or all scripts in a directory), save the compiled result
	# to the cache and return the compiled type.
	# If an error occurs in batch compilation, then an attempt is made to compile just the single
	# request file.
	def CompileScript(filename as string, batch as bool) as Type:
		inputs = GetInput(filename, batch)
		name = NormalizeName(filename, batch)
		result = DoCompile(inputs,name)
		if result.Errors.Count:
			if not batch:
				#This can be very useful to figure out things, but it's also pretty bad
				#security wise.
				#code = BrailPreProcessor.Booify(System.IO.File.OpenText(filename).ReadToEnd())
				#code = System.Web.HttpUtility.HtmlEncode(code)
				raise RailsException("Error during compile:\r\n${result.Errors.ToString()}\r\nCode:\r\n")
			#error compiling a batch, let's try a single file
			return CompileScript(filename,false)

		type as Type
		for input in inputs:
			type = result.GeneratedAssembly.GetType(Path.GetFileNameWithoutExtension(input.Name))
			compilations[input.Name] = type

		type = compilations[filename]
		return type

	# If batch compilation is set to true, this would return all the view scripts
	# in the director (not recursive!)
	# Otherwise, it would return just the single file
	def GetInput(filename as string, batch as bool) as (ICompilerInput):
		return (FileInput(filename),) if not batch
		inputs = []
		directory = Path.GetDirectoryName(filename)
		for file in Directory.GetFiles(directory,"*.boo"):
			inputs.Add(FileInput(file))
		return inputs.ToArray(ICompilerInput)

	# Perform the actual compilation of the scripts
	# Things to note here:
	# * The generated assembly reference the Castle.MonoRail.MonoRailBrail and Castle.MonoRail.Framework assemblies
	# * If a common scripts assembly exist, it is also referenced
	# * The AddBrailBaseClassStep compiler step is added - to create a class from the view's code
	# * The ProcessMethodBodiesWithDuckTyping is replaced with ReplaceUknownWithParameters
	#   this allows to use naked parameters such as (output context.IsLocal) without using
	#   any special syntax
	# * The IntroduceGlobalNamespaces step is removed, to allow to use common variables such as
	#   date & list without accidently using the Boo.Lang.BuiltIn versions
	def DoCompile(files as (ICompilerInput), name as string):
		compiler = SetupCompiler(files)
		filename = Path.Combine(self.baseSavePath,name)
		compiler.Parameters.OutputAssembly = filename
		# this is here and not in SetupCompiler since CompileCommon is also
		# using SetupCompiler, and we don't want reference to the old common from the new one
		compiler.Parameters.References.Add(common) if common is not null
		# pre procsssor needs to run before the parser
		compiler.Parameters.Pipeline.Insert(0, BrailPreProcessor())
		# inserting the add class step after the parser
		compiler.Parameters.Pipeline.Insert(2, TransformToBrailStep())
		compiler.Parameters.Pipeline.Replace(Steps.ProcessMethodBodiesWithDuckTyping,ReplaceUknownWithParameters())
		compiler.Parameters.Pipeline.Replace(InitializeTypeSystemServices, InitializeCustomTypeSystem())
		compiler.Parameters.Pipeline.RemoveAt(compiler.Parameters.Pipeline.Find(IntroduceGlobalNamespaces))

		return compiler.Run()

	# Return the output filename for the generated assembly
	# The filename is dependant on whatever we are doing a batch
	# compile or not, if it's a batch compile, then the directory name
	# is used, if it's just a single file, we're using the file's name.
	# '/' and '\' are replaced with '_', I'm not handling ':' since the path
	# should never include it since I'm converting this to a relative path
	def NormalizeName(filename as string, batch as bool):
		#Get relative filename,
		name = Path.GetFullPath(filename)[fullPathToViewDir.Length+1:]
		name = Path.GetDirectoryName(name) if batch
		for ch as char in (Path.AltDirectorySeparatorChar,
			Path.DirectorySeparatorChar):
			name = name.Replace(ch.ToString(),"_")
		return name + ".dll"

	# Compile all the common scripts to a common assemblies
	# an error in the common scripts would raise an exception.
	def CompileCommonScripts():
		if options.CommonScriptsDirectory is null:
			return false
		if not Directory.Exists(commonScriptPath):
			return false

		# the demi.boo is stripped, but GetInput require it.
		demiFile = Path.Combine(commonScriptPath,"demi.boo")
		inputs = GetInput(demiFile,true)
		compiler = SetupCompiler(inputs)
		outputFile = Path.Combine(self.baseSavePath, "CommonScripts.dll")
		compiler.Parameters.OutputAssembly = outputFile
		result = compiler.Run()
		if result.Errors.Count:
			raise RailsException(result.Errors.ToString(true))
		common = result.GeneratedAssembly
		compilations.Clear()
		return true

	# common setup for the compiler
	def SetupCompiler(files as (ICompilerInput)):

		compiler = BooCompiler()
		compiler.Parameters.Ducky = true
		compiler.Parameters.Debug = options.Debug
		if options.SaveToDisk:
			compiler.Parameters.Pipeline = CompileToFile()
		else:
			compiler.Parameters.Pipeline = CompileToMemory()
		# replace the normal parser with white space agnostic one.
		compiler.Parameters.Pipeline.RemoveAt(0)
		compiler.Parameters.Pipeline.Insert(0, WSABoo.Parser.WSABooParsingStep() )
		for file in files:
			compiler.Parameters.Input.Add(file)
		for assembly as System.Reflection.Assembly in options.AssembliesToReference:
			compiler.Parameters.References.Add(assembly)
		compiler.Parameters.OutputType = CompilerOutputType.Library
		return compiler

// Copyright 2004-2005 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
namespace Castle.MonoRail.Views.Brail

import System
import System.IO
import System.Collections
import Castle.MonoRail.Framework
import Boo.Lang.Compiler
import Boo.Lang.Compiler.IO
import Boo.Lang.Compiler.Steps
import Boo.Lang.Compiler.Pipelines

# This is the class that is responsible to take a template name and turn it into an
# executable code that then is piped to the user.
public class BooViewEngine (ViewEngineBase):
	compilations = Hashtable.Synchronized(Hashtable(CaseInsensitiveHashCodeProvider.Default,
			CaseInsensitiveComparer.Default))

	# This is used to add a reference to the common scripts for each compiled scripts
	common as System.Reflection.Assembly
	# Watch the common script files directory
	commonScriptWatcher as FileSystemWatcher
	# Watch the Views directory for changes
	viewsScriptWatcher as FileSystemWatcher

	static options as BooViewEngineOptions
	baseSavePath as string
	commonScriptPath as string
	fullPathToViewDir as string

	static def InitializeConfig():
		InitializeConfig("brail")
		InitializeConfig("Brail") if options is null
		options = BooViewEngineOptions() if options is null

	static def InitializeConfig(name as string):
		options = System.Configuration.ConfigurationSettings.GetConfig(name)

	# Get configuration options if they exists, if they do not exist, load the default ones
	# Create directory to save the compiled assemblies if required.
	# pre-compile the common scripts
	override def Init():
		InitializeConfig() if options is null
		baseDir = Path.GetDirectoryName(typeof(BooViewEngine).Assembly.Location)
		self.baseSavePath = Path.Combine(baseDir,options.SaveDirectory)
		self.commonScriptPath = Path.Combine(ViewRootDir, options.CommonScriptsDirectory)
		self.fullPathToViewDir = Path.GetFullPath(ViewRootDir)
		if options.SaveToDisk and not Directory.Exists(baseSavePath):
			Directory.CreateDirectory(baseSavePath)

		viewsScriptWatcher = FileSystemWatcher(ViewRootDir,
			IncludeSubdirectories: true, Filter: "*.boo")
		viewsScriptWatcher.Changed += def (source, e as FileSystemEventArgs):
			file = Path.GetFullPath(e.FullPath)
			# Will cause a recompilation
			compilations[file] = null

		viewsScriptWatcher.EnableRaisingEvents = true

		if not CompileCommonScripts():
			return

		commonScriptWatcher = FileSystemWatcher(commonScriptPath,
			IncludeSubdirectories: false, Filter:"*.boo")
		commonScriptWatcher.Changed += def (source, e as FileSystemEventArgs):
			CompileCommonScripts.BeginInvoke(
				{ ar as IAsyncResult | CompileCommonScripts.EndInvoke(ar) } )

		commonScriptWatcher.EnableRaisingEvents = true

	# Just check if the filename exists, I'm not sure when it's called
	override def HasTemplate(templateName as string):
		file = GetFileName(templateName)
		exists = File.Exists(file)
		return exists

	# Process a template name and output the results to the user
	# This may throw if an error occured and the user is not local (which would
	# cause the yellow screen of death) if the user is local, then a detailed stack
	# is shown
	override def Process(context as IRailsEngineContext, controller as Controller, templateName as string):
		try:
			file = GetFileName(templateName)
			view as BrailBase
			# Output may be the layout's child output if a layout exists
			# or the context.Response.Output if the layout is null
			output as TextWriter, layout as BrailBase = GetOutput(context, controller)
			# Will compile on first time, then save the assembly on the cache.
			view = GetCompiledScriptInstance(file, output, context, controller)
			controller.PreSendView(view)
			view.Run()
			layout.Run() if layout is not null
			controller.PostSendView(view)
		except e:
			HandleError(context, controller, e, "Cannot load view ${templateName}")

	# Send the contents text directly to the user, only adding the layout if neccecary
	override def ProcessContents(context as IRailsEngineContext, controller as Controller, contents as string):
		try:
			output as TextWriter, layout as BrailBase = GetOutput(context, controller)
			output.Write(contents)
			layout.Run() if layout is not null
		except e:
			HandleError(context, controller, e, "")

	# Throw if the user is not local, otherwise send detailed error message
	def HandleError(context as IRailsEngineContext, controller as Controller, e as Exception, msg as string):
		if not context.Request.IsLocal:
				raise RailsException(msg)
		error = OutputError(self, context.Response.Output,context, controller, e)
		error.Run()

	# Check if a layout has been defined. If it was, then the layout would be created
	# and will take over the output, otherwise, the context.Reposne.Output is used,
	# and layout is null
	def GetOutput(context as IRailsEngineContext, controller as Controller):
		layout as BrailBase
		output as TextWriter = context.Response.Output
		if controller.LayoutName is not null:
			layoutTemplate = "layouts/${controller.LayoutName}"
			layoutFilename = GetFileName(layoutTemplate)
			layout = GetCompiledScriptInstance(layoutFilename,output,
				context, controller)
			output = layout.ChildOutput = StringWriter()
		return output, layout

	def GetFileName(templateName as string):
		filename = Path.Combine(self.ViewRootDir,templateName)+".boo"
		return Path.GetFullPath(filename)

	# This takes a filename and return an instance of the view ready to be used.
	# If the file does not exist, an exception is raised
	# The cache is checked to see if the file has already been compiled, and it had been
	# a check is made to see that the compiled instance is newer then the file's modification date.
	# If the file has not been compiled, or the version on disk is newer than the one in memory, a new
	# version is compiled.
	# Finally, an instance is created and returned
	def GetCompiledScriptInstance(file as string,
		output as TextWriter,
		context as IRailsEngineContext,
		controller as Controller) as BrailBase:

		batch as bool = options.BatchCompile
		if  compilations.ContainsKey(file):
			type as Type = compilations[file]
			if type is not null:
				return type(self, output, context, controller)
			# if file is in compilations and the type is null,
			# this means that we need to recompile. Since this usually means that
			# the file was changed, we'll set batch to false and procceed to compile just
			# this file.
			batch = false

		type = CompileScript(file, batch)
		return type(self, output,context, controller)

	# Compile a script (or all scripts in a directory), save the compiled result
	# to the cache and return the compiled type.
	# If an error occurs in batch compilation, then an attempt is made to compile just the single
	# request file.
	def CompileScript(filename as string, batch as bool) as Type:
		inputs = GetInput(filename, batch)
		name = NormalizeName(filename, batch)
		result = DoCompile(inputs,name)
		if result.Errors.Count:
			if not batch:
				#This can be very useful to figure out things, but it's also pretty bad
				#security wise.
				#code = BrailPreProcessor.Booify(System.IO.File.OpenText(filename).ReadToEnd())
				#code = System.Web.HttpUtility.HtmlEncode(code)
				raise RailsException("Error during compile:\r\n${result.Errors.ToString()}\r\nCode:\r\n")
			#error compiling a batch, let's try a single file
			return CompileScript(filename,false)

		type as Type
		for input in inputs:
			type = result.GeneratedAssembly.GetType(Path.GetFileNameWithoutExtension(input.Name))
			compilations[input.Name] = type

		type = compilations[filename]
		return type

	# If batch compilation is set to true, this would return all the view scripts
	# in the director (not recursive!)
	# Otherwise, it would return just the single file
	def GetInput(filename as string, batch as bool) as (ICompilerInput):
		return (FileInput(filename),) if not batch
		inputs = []
		directory = Path.GetDirectoryName(filename)
		for file in Directory.GetFiles(directory,"*.boo"):
			inputs.Add(FileInput(file))
		return inputs.ToArray(ICompilerInput)

	# Perform the actual compilation of the scripts
	# Things to note here:
	# * The generated assembly reference the Castle.MonoRail.MonoRailBrail and Castle.MonoRail.Framework assemblies
	# * If a common scripts assembly exist, it is also referenced
	# * The AddBrailBaseClassStep compiler step is added - to create a class from the view's code
	# * The ProcessMethodBodiesWithDuckTyping is replaced with ReplaceUknownWithParameters
	#   this allows to use naked parameters such as (output context.IsLocal) without using
	#   any special syntax
	# * The IntroduceGlobalNamespaces step is removed, to allow to use common variables such as
	#   date & list without accidently using the Boo.Lang.BuiltIn versions
	def DoCompile(files as (ICompilerInput), name as string):
		compiler = SetupCompiler(files)
		filename = Path.Combine(self.baseSavePath,name)
		compiler.Parameters.OutputAssembly = filename
		# this is here and not in SetupCompiler since CompileCommon is also
		# using SetupCompiler, and we don't want reference to the old common from the new one
		compiler.Parameters.References.Add(common) if common is not null
		# pre procsssor needs to run before the parser
		compiler.Parameters.Pipeline.Insert(0, BrailPreProcessor())
		# inserting the add class step after the parser
		compiler.Parameters.Pipeline.Insert(2, TransformToBrailStep())
		compiler.Parameters.Pipeline.Replace(Steps.ProcessMethodBodiesWithDuckTyping,ReplaceUknownWithParameters())
		compiler.Parameters.Pipeline.Replace(InitializeTypeSystemServices, InitializeCustomTypeSystem())
		compiler.Parameters.Pipeline.RemoveAt(compiler.Parameters.Pipeline.Find(IntroduceGlobalNamespaces))

		return compiler.Run()

	# Return the output filename for the generated assembly
	# The filename is dependant on whatever we are doing a batch
	# compile or not, if it's a batch compile, then the directory name
	# is used, if it's just a single file, we're using the file's name.
	# '/' and '\' are replaced with '_', I'm not handling ':' since the path
	# should never include it since I'm converting this to a relative path
	def NormalizeName(filename as string, batch as bool):
		#Get relative filename,
		name = Path.GetFullPath(filename)[fullPathToViewDir.Length+1:]
		name = Path.GetDirectoryName(name) if batch
		for ch as char in (Path.AltDirectorySeparatorChar,
			Path.DirectorySeparatorChar):
			name = name.Replace(ch.ToString(),"_")
		return name + ".dll"

	# Compile all the common scripts to a common assemblies
	# an error in the common scripts would raise an exception.
	def CompileCommonScripts():
		if options.CommonScriptsDirectory is null:
			return false
		if not Directory.Exists(commonScriptPath):
			return false

		# the demi.boo is stripped, but GetInput require it.
		demiFile = Path.Combine(commonScriptPath,"demi.boo")
		inputs = GetInput(demiFile,true)
		compiler = SetupCompiler(inputs)
		outputFile = Path.Combine(self.baseSavePath, "CommonScripts.dll")
		compiler.Parameters.OutputAssembly = outputFile
		result = compiler.Run()
		if result.Errors.Count:
			raise RailsException(result.Errors.ToString(true))
		common = result.GeneratedAssembly
		compilations.Clear()
		return true

	# common setup for the compiler
	def SetupCompiler(files as (ICompilerInput)):

		compiler = BooCompiler()
		compiler.Parameters.Ducky = true
		compiler.Parameters.Debug = options.Debug
		if options.SaveToDisk:
			compiler.Parameters.Pipeline = CompileToFile()
		else:
			compiler.Parameters.Pipeline = CompileToMemory()
		# replace the normal parser with white space agnostic one.
		compiler.Parameters.Pipeline.RemoveAt(0)
		compiler.Parameters.Pipeline.Insert(0, WSABoo.Parser.WSABooParsingStep() )
		for file in files:
			compiler.Parameters.Input.Add(file)
		for assembly as System.Reflection.Assembly in options.AssembliesToReference:
			compiler.Parameters.References.Add(assembly)
		compiler.Parameters.OutputType = CompilerOutputType.Library
		return compiler