component container to properly dispose of internal controls

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

#region license
// Copyright (c) 2004, Rodrigo B. de Oliveira (rbo@acm.org)
// All rights reserved.
//
// This file is part of Boo Explorer.
//
// Boo Explorer 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.
//
// Boo Explorer 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 Foobar; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#endregion

namespace BooExplorer

import System
import System.Environment
import System.IO
import System.ComponentModel
import System.Windows.Forms
import System.Drawing
import WeifenLuo.WinFormsUI
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Pipelines
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.IO

class MainForm(Form):

	_dockPanel as DockPanel
	_status as StatusBar
	_statusPanel1 as StatusBarPanel
	_timer as Timer

	[getter(Settings)]
	_settings = LoadSettings()

	[getter(DocumentOutline)]
	_documentOutline = BooExplorer.DocumentOutline()

	[getter(TaskList)]
	_taskList = BooExplorer.TaskList(self)

	[getter(OutputPane)]
	_outputPane = BooExplorer.OutputPane()

	[getter(InteractiveConsole)]
	_interactiveConsole = BooExplorer.InteractiveConsole(self)

	[getter(TextInterceptors)]
	_textInterceptors as (ITextInterceptor)

	_argv as (string)

	_menuItemClose as MenuItem
	_menuItemSave as MenuItem
	_menuItemSaveAs as MenuItem

	_parser = BooCompiler()

	_resourceManager = System.Resources.ResourceManager(MainForm)

	_container = System.ComponentModel.Container()

	def constructor(argv as (string)):
		_argv = argv
		_dockPanel = DockPanel(Dock: DockStyle.Fill,
						ActiveAutoHideContent: null,
						TabIndex: 1,
						ActiveDocumentChanged: _dockPanel_ActiveDocumentChanged)

		_statusPanel1 = StatusBarPanel(AutoSize: StatusBarPanelAutoSize.Contents)

		_status = StatusBar(ShowPanels: true, TabIndex: 2)
		_status.Panels.Add(_statusPanel1)

		_parser.Parameters.Pipeline = Boo.Lang.Compiler.Pipelines.Parse()
		(_parser.Parameters.Pipeline[0] as duck).TabSize = 1

		SuspendLayout()

		self.Icon = _resourceManager.GetObject("_icon")
		self.Size = System.Drawing.Size(800, 600)
		self.Menu = CreateMainMenu()
		self.Text = "Boo Explorer"
		self.IsMdiContainer = true

		_container.Add(_dockPanel)
		_container.Add(_status)

		Controls.AddRange((
					_dockPanel,
					_status))
		ResumeLayout(false)

		LoadInterceptors()

		_timer = Timer(Tick: _timer_Tick, Interval: 50ms.TotalMilliseconds)
		_timer.Enabled = true

	override def Dispose(flag as bool):
		_container.Dispose()
		super(flag)

	private def GetSettingsFileName():
		return Path.Combine(GetApplicationDataFolder(), "settings.xml")

	private def SaveSettings():
		_settings.Save(GetSettingsFileName())

	private def LoadSettings():
		fname = GetSettingsFileName()
		if File.Exists(fname):
			return BooxSettings.Load(fname)
		return BooxSettings()

	private def CreateMainMenu():

		menu = MainMenu()
		file = MenuItem(Text: "&File", MergeOrder: 0)
		file.MenuItems.Add(MenuItem(Text: "&Open...",
									Click: _menuItemOpen_Click,
									Shortcut: Shortcut.CtrlO))
		file.MenuItems.Add(MenuItem(Text: "&New",
									Click: _menuItemNew_Click,
									Shortcut: Shortcut.CtrlN))

		file.MenuItems.Add(MenuItem("-"))

		file.MenuItems.Add(_menuItemSave = MenuItem(Text: "&Save",
									Enabled: false,
									Click: _menuItemSave_Click,
									Shortcut: Shortcut.CtrlS))
		file.MenuItems.Add(_menuItemSaveAs = MenuItem(Text: "S&ave as...",
									Enabled: false,
									Click: _menuItemSaveAs_Click))

		file.MenuItems.Add(_menuItemClose = MenuItem(Text: "&Close",
									Click: _menuItemClose_Click,
									Shortcut: Shortcut.CtrlW,
									Enabled: false))

		file.MenuItems.Add(MenuItem("-"))
		file.MenuItems.Add(MenuItem(Text: "E&xit",
									Shortcut: Shortcut.CtrlQ,
									Click: _menuItemExit_Click))

		tools = MenuItem(Text: "&Tools", MergeOrder: 2, MergeType: MenuMerge.MergeItems)
		tools.MenuItems.Add(MenuItem(Text: "&Options",
								Shortcut: Shortcut.CtrlO,
								Click: _menuItemOptions_Click,
								MergeOrder: int.MaxValue))

		view = MenuItem(Text: "&View", MergeOrder: 4)
		view.MenuItems.AddRange(
			(
				MenuItem(Text: "Document Outline",
						Click: _menuItemDocumentOutline_Click,
						Shortcut: Shortcut.CtrlShiftD),
				MenuItem(Text: "Task List",
						Click: _menuItemTaskList_Click,
						Shortcut: Shortcut.CtrlShiftT),
				MenuItem(Text: "Output",
						Click: _menuItemOutputPane_Click,
						Shortcut: Shortcut.CtrlShiftO),
				MenuItem(Text: "Prompt",
						Click: ShowPrompt,
						Shortcut: Shortcut.CtrlShiftP)
			))

		menu.MenuItems.AddRange((file, tools, view))
		return menu

	def _timer_Tick(sender, args as EventArgs):
		_timer.Enabled = false

		if File.Exists(GetDockStateXmlFileName()):
			LoadDockState()
			OpenDocuments(_argv)
		else:
			if len(_argv):
				OpenDocuments(_argv)
			else:
				NewDocument()
			ShowDocumentOutline()

	StatusText as string:
		set:
			_statusPanel1.Text = value

	def ParseString(fname as string, code as string):
		try:
			_parser.Parameters.Input.Add(StringInput(fname, code))
			return _parser.Run().CompileUnit
		ensure:
			_parser.Parameters.Input.Clear()

	private def LoadInterceptors():
		tempInterceptors = []

		if _settings.LoadPlugins:
			for file in Directory.GetFiles(MapPath("scripts"), "*.int"):
				interceptors = LoadInterceptorsFromFile(file)
				tempInterceptors = tempInterceptors + interceptors if interceptors

		_textInterceptors = array(ITextInterceptor, tempInterceptors)
		StatusText = "Loaded ${len(_textInterceptors)} TextInterceptor(s)"

	def MapPath(path as string):
		return Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), path)

	def LoadInterceptorsFromFile(fileName as string):
		script = ScriptCompiler.CompileFile(fileName)
		if len(script.Errors):
			for error in script.Errors:
				print("Compiler error: ${error}")
			return null

		retTypes = script.GetTypes()
		return null unless retTypes
		return [cast(ITextInterceptor, retType()) for retType in retTypes if retType() isa ITextInterceptor]

	def Expand(fname as string, code as string):
		compiler = BooCompiler()
		compiler.Parameters.OutputWriter = StringWriter()
		compiler.Parameters.Pipeline = CompileToBoo()
		compiler.Parameters.Input.Add(StringInput(fname, code))

		result = compiler.Run()
		self.UpdateTaskList(result.Errors)

		NewDocument().TextContent = compiler.Parameters.OutputWriter.ToString()

	def ShowDocumentOutline():
		ShowContent(_documentOutline)

	private def ShowContent(content as DockContent):
		content.Show(_dockPanel)
		if DockState.Unknown == content.DockState:
			content.Pane.DockState = content.ShowHint

	def NewDocument():
		editor = BooEditor(self)
		editor.Show(_dockPanel)
		editor.TextArea.Focus()
		return editor

	def OpenDocuments([required] fnames):
		for fname in fnames:
			try:
				OpenDocument(fname)
			except x:
				print(x)

	def OpenDocument([required] filename as string):
		filename = Path.GetFullPath(filename)
		content = FindEditor(filename)
		if content is null:
			editor = CreateEditor(filename)
			editor.Show(_dockPanel)
			editor.TextArea.Focus()
			return editor
		else:
			content.Show(_dockPanel)
			content.TextArea.Focus()
			return content

	private def CreateEditor([required] fname as string):
		editor = BooEditor(self)
		editor.Open(fname)
		return editor

	def _dockPanel_ActiveDocumentChanged(sender, args as EventArgs):
		document = _dockPanel.ActiveDocument
		editor = document as BooEditor
		_documentOutline.ActiveDocument = editor
		_menuItemClose.Enabled = document is not null
		_menuItemSaveAs.Enabled = _menuItemSave.Enabled = document is not null

	def _menuItemSaveAs_Click(sender, args as EventArgs):
		cast(BooEditor, _dockPanel.ActiveDocument).SaveAs()

	def _menuItemSave_Click(sender, args as EventArgs):
		cast(BooEditor, _dockPanel.ActiveDocument).Save()

	def _menuItemExit_Click(sender, args as EventArgs):
		self.Close()

	def _menuItemClose_Click(sender, args as EventArgs):
		_dockPanel.ActiveDocument.Close()

	def _menuItemDocumentOutline_Click(sender, args as EventArgs):
		ShowDocumentOutline()

	def _menuItemTaskList_Click(sender, args as EventArgs):
		ShowTaskList()

	def UpdateTaskList(errors as CompilerErrorCollection):
		_taskList.Update(errors)
		ShowTaskList() if len(errors)

	def ShowTaskList():
		ShowContent(_taskList)

	def ShowOutputPane():
		ShowContent(_outputPane)

	def ShowPrompt():
		ShowContent(_interactiveConsole)

	def _menuItemOutputPane_Click(sender, args as EventArgs):
		ShowOutputPane()

	def _menuItemOptions_Click():
		dlg = Form(Text: "Options")
		dlg.Controls.Add(PropertyGrid(
							Dock: DockStyle.Fill,
							SelectedObject: _settings,
							Font: Font,
							PropertySort: PropertySort.Alphabetical))
		dlg.ShowDialog()
		SaveSettings()

	def _menuItemOpen_Click(sender, args as EventArgs):
		dlg = OpenFileDialog(
					Filter: "boo files (*.boo)|*.boo|All files (*.*)|*.*",
					Multiselect: true)
		if DialogResult.OK == dlg.ShowDialog(self):
			for fname in dlg.FileNames:
				OpenDocument(fname)

	def _menuItemNew_Click(sender, args as EventArgs):
		NewDocument()

	def GetApplicationDataFolder():
		folder = Application.UserAppDataPath
		Directory.CreateDirectory(folder) unless Directory.Exists(folder)
		return folder

	def GetDockStateXmlFileName():
		return Path.Combine(GetApplicationDataFolder(), "dockstate.xml")

	def SaveDockState():
		_dockPanel.SaveAsXml(GetDockStateXmlFileName())

	def LoadDockState():
		_dockPanel.LoadFromXml(GetDockStateXmlFileName(), OnDeserializeDockContent)

	def OnDeserializeDockContent(persistString as string) as DockContent:
		type, content = /\|/.Split(persistString)
		print("type: ${type}, content: ${content}")
		if "DocumentOutline" == type:
			return _documentOutline
		if "InteractiveConsole" == type:
			return _interactiveConsole
		if "TaskList" == type:
			return _taskList
		if "OutputPane" == type:
			return _outputPane
		if "BooEditor" == type:
			editor = BooEditor(self)
			editor.Open(content) if File.Exists(content)
			return editor
		raise ArgumentException("Invalid persistence string: ${persistString}")

	override protected def OnClosed(args as EventArgs):
		SaveDockState()
		super(args)

	override protected def OnClosing(args as CancelEventArgs):
		super(args)
		if not args.Cancel:
			dirtyDocuments = [
							editor.GetSafeFileName()
							for document in _dockPanel.Documents
							if (editor=(document as BooEditor)) and editor.IsDirty
							]
			return unless len(dirtyDocuments)

			args.Cancel = (
							DialogResult.Yes !=
							MessageBox.Show("The following files were modified:\n\n\t" +
											join(dirtyDocuments, "\n\t") +
											"\n\nAre you sure you want to leave and lose all your changes?",
											"Boo Explorer",
											MessageBoxButtons.YesNo))

	def FindEditor(fname as string):
		for document in _dockPanel.Documents:
			editor = document as BooEditor
			if editor and editor.FileName == fname:
				return editor
		return null

#region license
// Copyright (c) 2004, Rodrigo B. de Oliveira (rbo@acm.org)
// All rights reserved.
//
// This file is part of Boo Explorer.
//
// Boo Explorer 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.
//
// Boo Explorer 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 Foobar; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#endregion

namespace BooExplorer

import System
import System.Environment
import System.IO
import System.ComponentModel
import System.Windows.Forms
import System.Drawing
import WeifenLuo.WinFormsUI
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Pipelines
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.IO

class MainForm(Form):

	_dockPanel as DockPanel
	_status as StatusBar
	_statusPanel1 as StatusBarPanel
	_timer as Timer

	[getter(Settings)]
	_settings = LoadSettings()

	[getter(DocumentOutline)]
	_documentOutline = BooExplorer.DocumentOutline()

	[getter(TaskList)]
	_taskList = BooExplorer.TaskList(self)

	[getter(OutputPane)]
	_outputPane = BooExplorer.OutputPane()

	[getter(InteractiveConsole)]
	_interactiveConsole = BooExplorer.InteractiveConsole(self)

	[getter(TextInterceptors)]
	_textInterceptors as (ITextInterceptor)

	_argv as (string)

	_menuItemClose as MenuItem
	_menuItemSave as MenuItem
	_menuItemSaveAs as MenuItem

	_parser = BooCompiler()

	_resourceManager = System.Resources.ResourceManager(MainForm)

	def constructor(argv as (string)):
		_argv = argv
		_dockPanel = DockPanel(Dock: DockStyle.Fill,
						ActiveAutoHideContent: null,
						TabIndex: 1,
						ActiveDocumentChanged: _dockPanel_ActiveDocumentChanged)

		_statusPanel1 = StatusBarPanel(AutoSize: StatusBarPanelAutoSize.Contents)

		_status = StatusBar(ShowPanels: true, TabIndex: 2)
		_status.Panels.Add(_statusPanel1)

		_parser.Parameters.Pipeline = Boo.Lang.Compiler.Pipelines.Parse()
		(_parser.Parameters.Pipeline[0] as duck).TabSize = 1

		SuspendLayout()

		self.Icon = _resourceManager.GetObject("_icon")
		self.Size = System.Drawing.Size(800, 600)
		self.Menu = CreateMainMenu()
		self.Text = "Boo Explorer"
		self.IsMdiContainer = true

		Controls.AddRange((
					_dockPanel,
					_status))
		ResumeLayout(false)

		LoadInterceptors()

		_timer = Timer(Tick: _timer_Tick, Interval: 50ms.TotalMilliseconds)
		_timer.Enabled = true

	private def GetSettingsFileName():
		return Path.Combine(GetApplicationDataFolder(), "settings.xml")

	private def SaveSettings():
		_settings.Save(GetSettingsFileName())

	private def LoadSettings():
		fname = GetSettingsFileName()
		if File.Exists(fname):
			return BooxSettings.Load(fname)
		return BooxSettings()

	private def CreateMainMenu():

		menu = MainMenu()
		file = MenuItem(Text: "&File", MergeOrder: 0)
		file.MenuItems.Add(MenuItem(Text: "&Open...",
									Click: _menuItemOpen_Click,
									Shortcut: Shortcut.CtrlO))
		file.MenuItems.Add(MenuItem(Text: "&New",
									Click: _menuItemNew_Click,
									Shortcut: Shortcut.CtrlN))

		file.MenuItems.Add(MenuItem("-"))

		file.MenuItems.Add(_menuItemSave = MenuItem(Text: "&Save",
									Enabled: false,
									Click: _menuItemSave_Click,
									Shortcut: Shortcut.CtrlS))
		file.MenuItems.Add(_menuItemSaveAs = MenuItem(Text: "S&ave as...",
									Enabled: false,
									Click: _menuItemSaveAs_Click))

		file.MenuItems.Add(_menuItemClose = MenuItem(Text: "&Close",
									Click: _menuItemClose_Click,
									Shortcut: Shortcut.CtrlW,
									Enabled: false))

		file.MenuItems.Add(MenuItem("-"))
		file.MenuItems.Add(MenuItem(Text: "E&xit",
									Shortcut: Shortcut.CtrlQ,
									Click: _menuItemExit_Click))

		tools = MenuItem(Text: "&Tools", MergeOrder: 2, MergeType: MenuMerge.MergeItems)
		tools.MenuItems.Add(MenuItem(Text: "&Options",
								Shortcut: Shortcut.CtrlO,
								Click: _menuItemOptions_Click,
								MergeOrder: int.MaxValue))

		view = MenuItem(Text: "&View", MergeOrder: 4)
		view.MenuItems.AddRange(
			(
				MenuItem(Text: "Document Outline",
						Click: _menuItemDocumentOutline_Click,
						Shortcut: Shortcut.CtrlShiftD),
				MenuItem(Text: "Task List",
						Click: _menuItemTaskList_Click,
						Shortcut: Shortcut.CtrlShiftT),
				MenuItem(Text: "Output",
						Click: _menuItemOutputPane_Click,
						Shortcut: Shortcut.CtrlShiftO),
				MenuItem(Text: "Prompt",
						Click: ShowPrompt,
						Shortcut: Shortcut.CtrlShiftP)
			))

		menu.MenuItems.AddRange((file, tools, view))
		return menu

	def _timer_Tick(sender, args as EventArgs):
		_timer.Enabled = false

		if File.Exists(GetDockStateXmlFileName()):
			LoadDockState()
			OpenDocuments(_argv)
		else:
			if len(_argv):
				OpenDocuments(_argv)
			else:
				NewDocument()
			ShowDocumentOutline()

	StatusText as string:
		set:
			_statusPanel1.Text = value

	def ParseString(fname as string, code as string):
		try:
			_parser.Parameters.Input.Add(StringInput(fname, code))
			return _parser.Run().CompileUnit
		ensure:
			_parser.Parameters.Input.Clear()

	private def LoadInterceptors():
		tempInterceptors = []

		if _settings.LoadPlugins:
			for file in Directory.GetFiles(MapPath("scripts"), "*.int"):
				interceptors = LoadInterceptorsFromFile(file)
				tempInterceptors = tempInterceptors + interceptors if interceptors

		_textInterceptors = array(ITextInterceptor, tempInterceptors)
		StatusText = "Loaded ${len(_textInterceptors)} TextInterceptor(s)"

	def MapPath(path as string):
		return Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), path)

	def LoadInterceptorsFromFile(fileName as string):
		script = ScriptCompiler.CompileFile(fileName)
		if len(script.Errors):
			for error in script.Errors:
				print("Compiler error: ${error}")
			return null

		retTypes = script.GetTypes()
		return null unless retTypes
		return [cast(ITextInterceptor, retType()) for retType in retTypes if retType() isa ITextInterceptor]

	def Expand(fname as string, code as string):
		compiler = BooCompiler()
		compiler.Parameters.OutputWriter = StringWriter()
		compiler.Parameters.Pipeline = CompileToBoo()
		compiler.Parameters.Input.Add(StringInput(fname, code))

		result = compiler.Run()
		self.UpdateTaskList(result.Errors)

		NewDocument().TextContent = compiler.Parameters.OutputWriter.ToString()

	def ShowDocumentOutline():
		ShowContent(_documentOutline)

	private def ShowContent(content as DockContent):
		content.Show(_dockPanel)
		if DockState.Unknown == content.DockState:
			content.Pane.DockState = content.ShowHint

	def NewDocument():
		editor = BooEditor(self)
		editor.Show(_dockPanel)
		editor.TextArea.Focus()
		return editor

	def OpenDocuments([required] fnames):
		for fname in fnames:
			try:
				OpenDocument(fname)
			except x:
				print(x)

	def OpenDocument([required] filename as string):
		filename = Path.GetFullPath(filename)
		content = FindEditor(filename)
		if content is null:
			editor = CreateEditor(filename)
			editor.Show(_dockPanel)
			editor.TextArea.Focus()
			return editor
		else:
			content.Show(_dockPanel)
			content.TextArea.Focus()
			return content

	private def CreateEditor([required] fname as string):
		editor = BooEditor(self)
		editor.Open(fname)
		return editor

	def _dockPanel_ActiveDocumentChanged(sender, args as EventArgs):
		document = _dockPanel.ActiveDocument
		editor = document as BooEditor
		_documentOutline.ActiveDocument = editor
		_menuItemClose.Enabled = document is not null
		_menuItemSaveAs.Enabled = _menuItemSave.Enabled = document is not null

	def _menuItemSaveAs_Click(sender, args as EventArgs):
		cast(BooEditor, _dockPanel.ActiveDocument).SaveAs()

	def _menuItemSave_Click(sender, args as EventArgs):
		cast(BooEditor, _dockPanel.ActiveDocument).Save()

	def _menuItemExit_Click(sender, args as EventArgs):
		self.Close()

	def _menuItemClose_Click(sender, args as EventArgs):
		_dockPanel.ActiveDocument.Close()

	def _menuItemDocumentOutline_Click(sender, args as EventArgs):
		ShowDocumentOutline()

	def _menuItemTaskList_Click(sender, args as EventArgs):
		ShowTaskList()

	def UpdateTaskList(errors as CompilerErrorCollection):
		_taskList.Update(errors)
		ShowTaskList() if len(errors)

	def ShowTaskList():
		ShowContent(_taskList)

	def ShowOutputPane():
		ShowContent(_outputPane)

	def ShowPrompt():
		ShowContent(_interactiveConsole)

	def _menuItemOutputPane_Click(sender, args as EventArgs):
		ShowOutputPane()

	def _menuItemOptions_Click():
		dlg = Form(Text: "Options")
		dlg.Controls.Add(PropertyGrid(
							Dock: DockStyle.Fill,
							SelectedObject: _settings,
							Font: Font,
							PropertySort: PropertySort.Alphabetical))
		dlg.ShowDialog()
		SaveSettings()

	def _menuItemOpen_Click(sender, args as EventArgs):
		dlg = OpenFileDialog(
					Filter: "boo files (*.boo)|*.boo|All files (*.*)|*.*",
					Multiselect: true)
		if DialogResult.OK == dlg.ShowDialog(self):
			for fname in dlg.FileNames:
				OpenDocument(fname)

	def _menuItemNew_Click(sender, args as EventArgs):
		NewDocument()

	def GetApplicationDataFolder():
		folder = Application.UserAppDataPath
		Directory.CreateDirectory(folder) unless Directory.Exists(folder)
		return folder

	def GetDockStateXmlFileName():
		return Path.Combine(GetApplicationDataFolder(), "dockstate.xml")

	def SaveDockState():
		_dockPanel.SaveAsXml(GetDockStateXmlFileName())

	def LoadDockState():
		_dockPanel.LoadFromXml(GetDockStateXmlFileName(), OnDeserializeDockContent)

	def OnDeserializeDockContent(persistString as string) as DockContent:
		type, content = /\|/.Split(persistString)
		print("type: ${type}, content: ${content}")
		if "DocumentOutline" == type:
			return _documentOutline
		if "InteractiveConsole" == type:
			return _interactiveConsole
		if "TaskList" == type:
			return _taskList
		if "OutputPane" == type:
			return _outputPane
		if "BooEditor" == type:
			editor = BooEditor(self)
			editor.Open(content) if File.Exists(content)
			return editor
		raise ArgumentException("Invalid persistence string: ${persistString}")

	override protected def OnClosed(args as EventArgs):
		SaveDockState()
		super(args)

	override protected def OnClosing(args as CancelEventArgs):
		super(args)
		if not args.Cancel:
			dirtyDocuments = [
							editor.GetSafeFileName()
							for document in _dockPanel.Documents
							if (editor=(document as BooEditor)) and editor.IsDirty
							]
			return unless len(dirtyDocuments)

			args.Cancel = (
							DialogResult.Yes !=
							MessageBox.Show("The following files were modified:\n\n\t" +
											join(dirtyDocuments, "\n\t") +
											"\n\nAre you sure you want to leave and lose all your changes?",
											"Boo Explorer",
											MessageBoxButtons.YesNo))

	def FindEditor(fname as string):
		for document in _dockPanel.Documents:
			editor = document as BooEditor
			if editor and editor.FileName == fname:
				return editor
		return null


Improving upload code

Improving upload code

""" Simple script to upload a new release asset to GitHub.

    Authentication is done using oAuth2 tokens. The token to use should
    be available as an environment variable named GITHUB_TOKEN.

    usage: booi github-release.boo -- <release> <path/to/build.zip>

    Note that It will remove any prior assets with the same name before
    uploading the new one.

"""

from System import Environment
from System.IO import Path, File
from System.Net import WebClient, WebException, HttpWebResponse
from System.Collections.Generic import List
import System.Web.Script.Serialization from System.Web.Extensions

class Release:
""" Models the Json responses from GitHub http://developer.github.com/v3/repos/releases/
    Using dynamic types is buggy under some Mono versions, so this is the
    only solution to make it work.
"""
    class Asset:
        property url as string
        property id as int
        property name as string
        property label as string
        property state as string
        property content_type as string
        property size as int
        property download_count as int
        property created_at as string
        property updated_at as string

    property assets as List[of Asset]
    property url as string
    property html_url as string
    property assets_url as string
    property upload_url as string
    property id as int
    property tag_name as string
    property target_commitish as string
    property name as string
    property body as string
    property draft as bool
    property prerelease as bool
    property created_at as string
    property published_at as string

def client():
""" WebClient doesn't keep the headers between requests, so we instantiate
    a new one every time we want to use it.
"""
    wc = WebClient()
    wc.Headers['Authorization'] = 'token ' + Environment.GetEnvironmentVariable('GITHUB_TOKEN')
    # The API requires this custom Accept header while in beta
    wc.Headers.Add('Accept', 'application/vnd.github.manifold-preview')
    wc.Headers.Add('user-agent', 'boo-lang')
    return wc

if len(argv) < 2:
    print 'Usage: github-release <release> <path/to/build.zip>'
    return

if not Environment.GetEnvironmentVariable('GITHUB_TOKEN'):
    raise 'GITHUB_TOKEN environment variable not set'

MAX_UPLOAD_RETRIES = 5
USERNAME = 'boo-lang'
REPO = 'boo'
API_URL = "https://api.github.com/repos/$USERNAME/$REPO/releases"
RELEASE_NAME = argv[0]
ASSET_FILE = argv[1]
ASSET_NAME = Path.GetFileName(ASSET_FILE)

json = JavaScriptSerializer()

print "Looking for release $RELEASE_NAME"
try:
    result = client().DownloadString(API_URL)
except wx as WebException:
    var e = wx.Response cast HttpWebResponse
    using sr = System.IO.StreamReader(e.GetResponseStream()):
        print sr.ReadToEnd()
    raise
releases = json.Deserialize[of List[of Release]](result)
release = releases.Find({rel | rel.tag_name == RELEASE_NAME})

if not release:
    raise "Release $RELEASE_NAME not found"

for asset in release.assets:
    if asset.name == ASSET_NAME:
        print "Removing asset $(asset.id)"
        client().UploadData(asset.url as string, "DELETE", array(byte, 0))
        break

upload_url = (release.upload_url as string).Replace('{?name,label}', "?name=$ASSET_NAME&label=$RELEASE_NAME")
print "Uploading $ASSET_FILE to $upload_url"
retries = 0
:retry
try:
    using uploadClient = client():
        uploadClient.Headers.Add("Content-Type", "application/zip")
        using fileStream = File.OpenRead(filePath), requestStream = client.OpenWrite(Uri(upload_url), "POST"):
            fileStream.CopyTo(requestStream)
except ex as WebException:
    var e2 = ex.Response cast HttpWebResponse
    using sr = System.IO.StreamReader(e2.GetResponseStream()):
        print sr.ReadToEnd()
    retries += 1
    if retries >= MAX_UPLOAD_RETRIES:
        print "Too many retries, giving up..."
        raise ex
    print "Upload failed ($(ex.Message)), retrying..."
    goto retry

print 'Upload successful.'

""" Simple script to upload a new release asset to GitHub.

    Authentication is done using oAuth2 tokens. The token to use should
    be available as an environment variable named GITHUB_TOKEN.

    usage: booi github-release.boo -- <release> <path/to/build.zip>

    Note that It will remove any prior assets with the same name before
    uploading the new one.

"""

from System import Environment
from System.IO import Path
from System.Net import WebClient, WebException, HttpWebResponse
from System.Collections.Generic import List
import System.Web.Script.Serialization from System.Web.Extensions

class Release:
""" Models the Json responses from GitHub http://developer.github.com/v3/repos/releases/
    Using dynamic types is buggy under some Mono versions, so this is the
    only solution to make it work.
"""
    class Asset:
        property url as string
        property id as int
        property name as string
        property label as string
        property state as string
        property content_type as string
        property size as int
        property download_count as int
        property created_at as string
        property updated_at as string

    property assets as List[of Asset]
    property url as string
    property html_url as string
    property assets_url as string
    property upload_url as string
    property id as int
    property tag_name as string
    property target_commitish as string
    property name as string
    property body as string
    property draft as bool
    property prerelease as bool
    property created_at as string
    property published_at as string

def client():
""" WebClient doesn't keep the headers between requests, so we instantiate
    a new one every time we want to use it.
"""
    wc = WebClient()
    wc.Headers['Authorization'] = 'token ' + Environment.GetEnvironmentVariable('GITHUB_TOKEN')
    # The API requires this custom Accept header while in beta
    wc.Headers.Add('Accept', 'application/vnd.github.manifold-preview')
    wc.Headers.Add('user-agent', 'boo-lang')
    return wc

if len(argv) < 2:
    print 'Usage: github-release <release> <path/to/build.zip>'
    return

if not Environment.GetEnvironmentVariable('GITHUB_TOKEN'):
    raise 'GITHUB_TOKEN environment variable not set'

MAX_UPLOAD_RETRIES = 5
USERNAME = 'boo-lang'
REPO = 'boo'
API_URL = "https://api.github.com/repos/$USERNAME/$REPO/releases"
RELEASE_NAME = argv[0]
ASSET_FILE = argv[1]
ASSET_NAME = Path.GetFileName(ASSET_FILE)

json = JavaScriptSerializer()

print "Looking for release $RELEASE_NAME"
try:
    result = client().DownloadString(API_URL)
except wx as WebException:
    var e = wx.Response cast HttpWebResponse
    using sr = System.IO.StreamReader(e.GetResponseStream()):
        print sr.ReadToEnd()
    raise
releases = json.Deserialize[of List[of Release]](result)
release = releases.Find({rel | rel.tag_name == RELEASE_NAME})

if not release:
    raise "Release $RELEASE_NAME not found"

for asset in release.assets:
    if asset.name == ASSET_NAME:
        print "Removing asset $(asset.id)"
        client().UploadData(asset.url as string, "DELETE", array(byte, 0))
        break

upload_url = (release.upload_url as string).Replace('{?name,label}', "?name=$ASSET_NAME,label=$RELEASE_NAME")
print "Uploading $ASSET_FILE to $upload_url"
retries = 0
:retry
try:
    client().UploadFile(upload_url, ASSET_FILE)
except ex as WebException:
    var e2 = ex.Response cast HttpWebResponse
    using sr = System.IO.StreamReader(e2.GetResponseStream()):
        print sr.ReadToEnd()
    retries += 1
    if retries >= MAX_UPLOAD_RETRIES:
        print "Too many retries, giving up..."
        raise ex
    print "Upload failed ($(ex.Message)), retrying..."
    goto retry


improved a bit the flawed detection of module symbols

namespace BooJs.Compiler.Steps

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

import Boo.Lang.Environments
import Boo.Lang.Compiler.TypeSystem.Services.RuntimeMethodCache as BooRuntimeMethodCache
import Boo.Lang.Compiler.TypeSystem.Reflection(ExternalType)
import BooJs.Compiler.TypeSystem(RuntimeMethodCache)
import BooJs.Lang.Extensions

import System.Runtime.CompilerServices(CompilerGeneratedAttribute)

class PrepareAst(AbstractTransformerCompilerStep):
"""
    Prepare the AST for its conversion to Javascript

        - Remove unneeded nodes
        - Convert builtins and runtime references

"""
    [getter(MethodCache)]
    private _methodCache as RuntimeMethodCache

    [getter(BooMethodCache)]
    private _booMethodCache as BooRuntimeMethodCache

    protected def GetAttribute[of T(System.Attribute)](node as Node) as T:
        entity = node.Entity as TypeSystem.IExternalEntity
        return (GetAttribute[of T](entity) if entity else null as T)

    protected def GetAttribute[of T(System.Attribute)](entity as TypeSystem.IExternalEntity) as T:
        return System.Attribute.GetCustomAttribute(entity.MemberInfo, typeof(T))

    protected def IsBuiltin(node as Node) as bool:
        entity = node.Entity as TypeSystem.Reflection.ExternalType
        if not entity and centity = node.Entity as TypeSystem.ExternalMethod:
            entity = centity.DeclaringType

        return false if not entity
        return true if entity.Type is TypeSystemServices.BuiltinsType
        return entity.DeclaringType == TypeSystemServices.BuiltinsType

    protected def IsGlobal(node as Node) as bool:
        if entity = node.Entity as TypeSystem.Reflection.ExternalType:
            return entity.ActualType.Namespace == 'BooJs.Lang.Globals'

        return false

    def Initialize(context as CompilerContext):
        super(context)

        _methodCache = EnvironmentProvision[of RuntimeMethodCache]()
        _booMethodCache = EnvironmentProvision[of BooRuntimeMethodCache]()

    def EnterModule(node as Module):
        if node.Name == 'CompilerGenerated':
            RemoveCurrentNode()
            return false

        return true

    def EnterClassDefinition(node as ClassDefinition):
        if node.Name == 'CompilerGeneratedExtensions':
            RemoveCurrentNode()
            return false

        if GetAttribute[of CompilerGeneratedAttribute](node):
            RemoveCurrentNode()
            return false

        return true

    def OnExpressionStatement(node as ExpressionStatement):
        # Ignore the assignment of locals produced by closures instrumentation
        be = node.Expression as BinaryExpression
        if be and be.Operator == BinaryOperatorType.Assign and be.Left.ToString() == '$locals':
            RemoveCurrentNode
            return

        # Ignore initialization value calls (__initobj__). We do it elsewhere.
        mie = node.Expression as MethodInvocationExpression
        if mie and mie.Target.Entity == TypeSystem.BuiltinFunction.InitValueType:
            RemoveCurrentNode
            return

        super(node)

    protected def ProcessReference(node as ReferenceExpression) as Node:
        if TransformAttribute.HasAttribute(node):
            result = TransformAttribute.Resolve(node, null)
            Visit result
            return result

        # Primitive type references
        if entity = node.Entity as ExternalType and TypeSystemServices.IsLiteralPrimitive(entity):
            return StringLiteralExpression(node.LexicalInfo, entity.FullName)

        # Check for builtins references
        if IsBuiltin(node):
            name = node.Name.Split(char('.'))[-1]
            refe = [| Boo.$(ReferenceExpression(Name: name)) |].withLexicalInfoFrom(node)
            refe.Entity = node.Entity
            return refe

        # Entities in the Global namespace are never prefixed
        if IsGlobal(node):
            node.Name = node.Name.Split(char('.'))[-1]
            return node

        # Members of the module are placed in the top scope
        # TODO: This detection algorithm is very weak! We need to come out with something better
        ientity = node.Entity as TypeSystem.IMember
        if ientity and ientity.DeclaringType and ientity.DeclaringType.IsClass and ientity.DeclaringType.Name == ientity.DeclaringType.FullName:
            print node
            mre = MemberReferenceExpression(node.LexicalInfo)
            mre.ExpressionType = node.ExpressionType
            mre.Entity = node.Entity
            mre.Target = ReferenceExpression('exports', LexicalInfo: node.LexicalInfo)
            mre.Name = node.Name.Split(char('.'))[-1]
            return mre

        return node

    def OnReferenceExpression(node as ReferenceExpression):
        ReplaceCurrentNode ProcessReference(node)

    def OnMemberReferenceExpression(node as MemberReferenceExpression):
        if TransformAttribute.HasAttribute(node):
            result = TransformAttribute.Resolve(node, null)
            Visit result
            ReplaceCurrentNode result
            return

        # Convert from `$locals.$variable` to `variable`
        if node.Target.NodeType == NodeType.ReferenceExpression:
            if (node.Target as ReferenceExpression).Name == '$locals':
                refexp = ReferenceExpression(node.Name[1:], LexicalInfo: node.LexicalInfo)
                ReplaceCurrentNode refexp
                return

        # Members of the module are placed in the top scope
        # TODO: This detection algorithm is very weak! We need to come out with something better
        ientity = node.Entity as TypeSystem.IMember
        if ientity and ientity.DeclaringType and ientity.DeclaringType.IsClass and ientity.DeclaringType.Name == ientity.DeclaringType.FullName:
            #refexp = ReferenceExpression(node.Name, LexicalInfo: node.LexicalInfo)
            #ReplaceCurrentNode refexp
            node.Target = ReferenceExpression('exports', LexicalInfo: node.LexicalInfo)
            return

        # Check for builtins references
        if IsBuiltin(node.Target):
            node.Target = [| Boo |].withLexicalInfoFrom(node.Target)

        super(node)

    def OnMethodInvocationExpression(node as MethodInvocationExpression):
        # Convert Transform attribute metadata to an AST annotation
        if TransformAttribute.HasAttribute(node.Target):
            result = TransformAttribute.Resolve(node.Target, node.Arguments)
            result = VisitNode(result)
            ReplaceCurrentNode result
            return

        if GetAttribute[of VarArgsAttribute](node.Target) and not node.ContainsAnnotation('varargs-processed'):
            node['varargs-processed'] = true
            if node.Arguments[-1] isa ListLiteralExpression:
                last = node.Arguments[-1] as ListLiteralExpression
                node.Arguments.Remove(last)
                for itm in last.Items:
                    node.Arguments.Add(itm)
                return

            params = ListLiteralExpression()
            for i in range(0, len(node.Arguments)-1):
                params.Items.Add( node.Arguments[i] )
            mie = [| $(params).concat($(node.Arguments[-1])) |]
            if node.Target isa MemberReferenceExpression:
                target = (node.Target as MemberReferenceExpression).Target
            else:
                target = [| Boo.UNDEF |]
            node = [| $(node.Target).apply($target, $mie) |]
            ReplaceCurrentNode node

        /*
        rts = TypeSystemServices.RuntimeType
        rtsInvoke = ResolveMethod(rts, 'Invoke')
        rtsInvokeCallable = ResolveMethod(rts, 'InvokeCallable')
        rtsInvokeBinaryOperator = ResolveMethod(rts, 'InvokeBinaryOperator')
        rtsInvokeUnaryOperator = ResolveMethod(rts, 'InvokeUnaryOperator')
        rtsSetProperty = ResolveMethod(rts, 'SetProperty')
        rtsGetProperty = ResolveMethod(rts, 'GetProperty')
        rtsSetSlice = ResolveMethod(rts, 'SetSlice')
        rtsGetSlice = ResolveMethod(rts, 'GetSlice')
        */

        if node.Target.Entity == MethodCache.InvokeBinaryOperator:

            # Convert it back into a binary expression
            be = BinaryExpression(node.LexicalInfo)
            be.ExpressionType = node.ExpressionType
            be.Left = node.Arguments[1]
            be.Right = node.Arguments[2]

            # Remove the op_ to find the operator type
            operator = (node.Arguments[0] as StringLiteralExpression).Value[3:]
            be.Operator = System.Enum.Parse(BinaryOperatorType, operator)

            ReplaceCurrentNode Visit(be)
            return

        if node.Target.Entity == MethodCache.InvokeUnaryOperator:
            # Convert it back into a unary expression
            ue = UnaryExpression(node.LexicalInfo)
            ue.ExpressionType = node.ExpressionType
            ue.Operand = node.Arguments[1]

            # Remove the op_ to find the operator type
            operator = (node.Arguments[0] as StringLiteralExpression).Value[3:]
            ue.Operator = System.Enum.Parse(UnaryOperatorType, operator)

            ReplaceCurrentNode Visit(ue)
            return

        super(node)

    protected def LocalsToDecls(locals as LocalCollection):
        # Convert locals back to simple declaration statements
        found = ['$locals']
        for local in locals:
            # Avoid duplicates
            if local.Name is null or local.Name in found:
                continue

            found.Push(local.Name)

            decl = Declaration(LexicalInfo: local.LexicalInfo)
            decl.Name = local.Name

            # Detect local type
            initializer as Expression = NullLiteralExpression()
            entity = local.Entity as TypeSystem.Internal.InternalLocal
            if entity:
                if entity.OriginalDeclaration:
                    # Skip declarations for those flagged as global
                    continue if entity.OriginalDeclaration.ContainsAnnotation('global')
                    decl.Type = entity.OriginalDeclaration.Type
                else:
                    # TODO: Can we generate proper type annotations with this?
                    decl.Type = SimpleTypeReference(Entity: entity)

                # Initialize with a default value
                if TypeSystemServices.IsNumber(entity.Type):
                    initializer = IntegerLiteralExpression(0)
                elif entity.Type == TypeSystemServices.TimeSpanType:
                    initializer = IntegerLiteralExpression(0)
                elif entity.Type == TypeSystemServices.BoolType:
                    initializer = BoolLiteralExpression(false)
                elif entity.Type == TypeSystemServices.StringType:
                    initializer = StringLiteralExpression('')

            yield DeclarationStatement(LexicalInfo: local.LexicalInfo, Declaration: decl, Initializer: initializer)

    def OnBlockExpression(node as BlockExpression):
    """ Convert locals annotation to declarations
    """
        if node.ContainsAnnotation('locals'):
            for st in LocalsToDecls(node['locals']):
                node.Body.Insert(0, st)
            node.RemoveAnnotation('locals')

        Visit node.Body

    def OnMethod(node as Method):
    """ Process locals and detect the Main method to move its statements into the Module globals
    """
        # Skip compiler generated methods
        if node.IsSynthetic and node.IsInternal:
            RemoveCurrentNode
            return

        for st in LocalsToDecls(node.Locals):
            node.Body.Insert(0, st)

        # Detect the Main method and move its statements to the Module globals
        if IsEntryPoint(node):
            Visit node.Body
            node.EnclosingModule.Globals = node.Body
            RemoveCurrentNode
            return

        super(node)

    def IsEntryPoint(node as Method):
        # Note: We cannot use ContextAnnotations.GetEntryPoint(Context) to detect
        # the entry point since when emitting the assembly we have to clone the AST
        # and then the node instance is different.
        return node.Name == IntroduceModuleClasses.EntryPointMethodName \
               and node.IsSynthetic and node.IsStatic and node.IsPrivate

    def OnCastExpression(node as CastExpression):
        mie = [| Boo.$(ReferenceExpression('cast'))() |]
        mie.LexicalInfo = node.LexicalInfo
        mie.Arguments.Add(node.Target)
        ReplaceCurrentNode( ProcessCast(mie, node.Type) )

    def OnTryCastExpression(node as TryCastExpression):
        mie = [| Boo.trycast() |]
        mie.LexicalInfo = node.LexicalInfo
        mie.Arguments.Add(node.Target)
        ReplaceCurrentNode( ProcessCast(mie, node.Type) )

    protected def ProcessCast(mie as MethodInvocationExpression, type as Node) as MethodInvocationExpression:
        if not type isa Expression:
            type = TypeofExpression(type.LexicalInfo, type)

        mie.Arguments.Add(type)
        return Visit(mie)

    def OnCharLiteralExpression(node as CharLiteralExpression):
    """ There is no char type in BooJs. char('c') and char(int) are converted to a string of length 1
    """
        ReplaceCurrentNode StringLiteralExpression(LexicalInfo: node.LexicalInfo, Value: node.Value)

    def OnRELiteralExpression(node as RELiteralExpression):
    """ Almost a direct translation to Javascript regexp literals. The only modification
        is that we ignore whitespace to allow the regexps to be somehow more readable while
        Boo's handling is to escape it as part of the match.

            / foo | bar /i  ->  /foo|bar/i
            @/ foo | bar /  ->  /foo|bar/i

        TODO: The ones prefixed with @ should be preprocessed to support new lines
    """
        re = node.Value
        start = re.IndexOf('/')
        stop = re.LastIndexOf('/')

        modifier = re[stop:]
        re = re[start:stop]

        # Ignore white space
        re = /\s+/.Replace(re, '')

        # Javascript does not support the single-line modifier (dot matches new lines).
        # We emulate it by converting dots to the [\s\S] expression in the regex.
        # NOTE: The algorithm will break when a dot is preceeded by an escaped back slash (\\.)
        if modifier.Contains('s'):
            re = /(?<!\\)\./.Replace(re, '[\\s\\S]')
            modifier = modifier.Replace('s', '')

        node.Value = re + modifier

    def OnArrayLiteralExpression(node as ArrayLiteralExpression):
    """ In BooJs arrays are muttable (Boo's Lists)
    """
        super(node)
        list = ListLiteralExpression(node.LexicalInfo, Items: node.Items)
        ReplaceCurrentNode list

    def OnUnlessStatement(node as UnlessStatement):
    """ Negate the condition to convert it into a simple if statement
    """
        ifst = IfStatement(LexicalInfo: node.LexicalInfo)
        ifst.Condition = UnaryExpression(
            Operator: UnaryOperatorType.LogicalNot,
            Operand: node.Condition,
            LexicalInfo: node.LexicalInfo
        )
        ifst.TrueBlock = node.Block

        ReplaceCurrentNode ifst

    def OnBinaryExpression(node as BinaryExpression):
        if node.Operator == BinaryOperatorType.TypeTest:
            mie = MethodInvocationExpression(node.LexicalInfo)
            mie.Target = ReferenceExpression(node.LexicalInfo, 'Boo.isa')
            mie.Arguments.Add(node.Left)
            mie.Arguments.Add(node.Right)
            Visit mie.Arguments
            ReplaceCurrentNode mie
            return

        super(node)

    def OnTypeofExpression(node as TypeofExpression):
        if st = node.Type as SimpleTypeReference:

            # Primitives are just enclosed as strings
            if TypeSystemServices.IsPrimitive(st.Name):
                ReplaceCurrentNode StringLiteralExpression(node.LexicalInfo, st.Name)
                return

            # If we have an entity use it
            if exent = st.Entity as TypeSystem.Reflection.ExternalType:
                refe = CodeBuilder.CreateReference(node.LexicalInfo, exent.ActualType)
                ReplaceCurrentNode ProcessReference(refe)
                return

            # Just rely on the type name
            refe = ReferenceExpression(node.LexicalInfo, st.Name)
            ReplaceCurrentNode ProcessReference(refe)

        elif node.Type isa ArrayTypeReference:

            refe = CodeBuilder.CreateReference(node.LexicalInfo, TypeSystemServices.ArrayType)
            ReplaceCurrentNode ProcessReference(refe)
            return

        else:
            raise 'Unsupported TypeReference: ' + node.Type + ' (' + node.Type.NodeType + ')'

    def OnUnaryExpression(node as UnaryExpression):
        # Logical Not folding
        if node.Operator == UnaryOperatorType.LogicalNot:
            # not(not(expr)) -> expr
            if ue = node.Operand as UnaryExpression and ue.Operator == UnaryOperatorType.LogicalNot:
                ReplaceCurrentNode Visit(ue.Operand)
                return

            if be = node.Operand as BinaryExpression:
                inverses = {
                    BinaryOperatorType.Equality: BinaryOperatorType.Inequality,
                    BinaryOperatorType.Inequality: BinaryOperatorType.Equality,
                    BinaryOperatorType.GreaterThan: BinaryOperatorType.LessThanOrEqual,
                    BinaryOperatorType.LessThanOrEqual: BinaryOperatorType.GreaterThan,
                    BinaryOperatorType.LessThan: BinaryOperatorType.GreaterThanOrEqual,
                    BinaryOperatorType.GreaterThanOrEqual: BinaryOperatorType.LessThan,
                }

                if be.Operator in inverses:
                    be.Operator = inverses[be.Operator]
                    ReplaceCurrentNode Visit(be)
                    return

        super(node)

    def OnBlock(node as Block):

        Visit node.Statements

        # Flatten nested blocks
        ofs = 0
        while ofs < len(node.Statements):
            st = node.Statements[ofs]
            if st.NodeType == NodeType.Block:
                node.Statements.Remove(st)
                for st2 in (st as Block).Statements:
                    node.Statements.Insert(ofs, st2)
                    ofs++
            else:
                ofs++

namespace BooJs.Compiler.Steps

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

import Boo.Lang.Environments
import Boo.Lang.Compiler.TypeSystem.Services.RuntimeMethodCache as BooRuntimeMethodCache
import Boo.Lang.Compiler.TypeSystem.Reflection(ExternalType)
import BooJs.Compiler.TypeSystem(RuntimeMethodCache)
import BooJs.Lang.Extensions

import System.Runtime.CompilerServices(CompilerGeneratedAttribute)

class PrepareAst(AbstractTransformerCompilerStep):
"""
    Prepare the AST for its conversion to Javascript

        - Remove unneeded nodes
        - Convert builtins and runtime references

"""
    [getter(MethodCache)]
    private _methodCache as RuntimeMethodCache

    [getter(BooMethodCache)]
    private _booMethodCache as BooRuntimeMethodCache

    protected def GetAttribute[of T(System.Attribute)](node as Node) as T:
        entity = node.Entity as TypeSystem.IExternalEntity
        return (GetAttribute[of T](entity) if entity else null as T)

    protected def GetAttribute[of T(System.Attribute)](entity as TypeSystem.IExternalEntity) as T:
        return System.Attribute.GetCustomAttribute(entity.MemberInfo, typeof(T))

    protected def IsBuiltin(node as Node) as bool:
        entity = node.Entity as TypeSystem.Reflection.ExternalType
        if not entity and centity = node.Entity as TypeSystem.ExternalMethod:
            entity = centity.DeclaringType

        return false if not entity
        return true if entity.Type is TypeSystemServices.BuiltinsType
        return entity.DeclaringType == TypeSystemServices.BuiltinsType

    protected def IsGlobal(node as Node) as bool:
        if entity = node.Entity as TypeSystem.Reflection.ExternalType:
            return entity.ActualType.Namespace == 'BooJs.Lang.Globals'

        return false

    def Initialize(context as CompilerContext):
        super(context)

        _methodCache = EnvironmentProvision[of RuntimeMethodCache]()
        _booMethodCache = EnvironmentProvision[of BooRuntimeMethodCache]()

    def EnterModule(node as Module):
        if node.Name == 'CompilerGenerated':
            RemoveCurrentNode()
            return false

        return true

    def EnterClassDefinition(node as ClassDefinition):
        if node.Name == 'CompilerGeneratedExtensions':
            RemoveCurrentNode()
            return false

        if GetAttribute[of CompilerGeneratedAttribute](node):
            RemoveCurrentNode()
            return false

        return true

    def OnExpressionStatement(node as ExpressionStatement):
        # Ignore the assignment of locals produced by closures instrumentation
        be = node.Expression as BinaryExpression
        if be and be.Operator == BinaryOperatorType.Assign and be.Left.ToString() == '$locals':
            RemoveCurrentNode
            return

        # Ignore initialization value calls (__initobj__). We do it elsewhere.
        mie = node.Expression as MethodInvocationExpression
        if mie and mie.Target.Entity == TypeSystem.BuiltinFunction.InitValueType:
            RemoveCurrentNode
            return

        super(node)

    protected def ProcessReference(node as ReferenceExpression) as Node:
        if TransformAttribute.HasAttribute(node):
            result = TransformAttribute.Resolve(node, null)
            Visit result
            return result

        # Primitive type references
        if entity = node.Entity as ExternalType and TypeSystemServices.IsLiteralPrimitive(entity):
            return StringLiteralExpression(node.LexicalInfo, entity.FullName)

        # Check for builtins references
        if IsBuiltin(node):
            name = node.Name.Split(char('.'))[-1]
            refe = [| Boo.$(ReferenceExpression(Name: name)) |].withLexicalInfoFrom(node)
            refe.Entity = node.Entity
            return refe

        # Entities in the Global namespace are never prefixed
        if IsGlobal(node):
            node.Name = node.Name.Split(char('.'))[-1]
            return node

        # Members of the module are placed in the top scope
        ientity = node.Entity as TypeSystem.IMember
        if ientity and ientity.DeclaringType and ientity.DeclaringType.IsClass and ientity.DeclaringType.IsFinal:
            mre = MemberReferenceExpression(node.LexicalInfo)
            mre.Target = ReferenceExpression('exports', LexicalInfo: node.LexicalInfo)
            mre.Name = node.Name.Split(char('.'))[-1]
            return mre

        return node

    def OnReferenceExpression(node as ReferenceExpression):
        ReplaceCurrentNode ProcessReference(node)

    def OnMemberReferenceExpression(node as MemberReferenceExpression):
        if TransformAttribute.HasAttribute(node):
            result = TransformAttribute.Resolve(node, null)
            Visit result
            ReplaceCurrentNode result
            return

        # Convert from `$locals.$variable` to `variable`
        if node.Target.NodeType == NodeType.ReferenceExpression:
            if (node.Target as ReferenceExpression).Name == '$locals':
                refexp = ReferenceExpression(node.Name[1:], LexicalInfo: node.LexicalInfo)
                ReplaceCurrentNode refexp
                return

        # Members of the module are placed in the top scope
        ientity = node.Target.Entity as TypeSystem.Internal.AbstractInternalType
        if node.Target.IsSynthetic and ientity and ientity.IsClass and ientity.IsFinal:
            #refexp = ReferenceExpression(node.Name, LexicalInfo: node.LexicalInfo)
            #ReplaceCurrentNode refexp
            node.Target = ReferenceExpression('exports', LexicalInfo: node.LexicalInfo)
            return

        # Check for builtins references
        if IsBuiltin(node.Target):
            node.Target = [| Boo |].withLexicalInfoFrom(node.Target)

        super(node)

    def OnMethodInvocationExpression(node as MethodInvocationExpression):
        # Convert Transform attribute metadata to an AST annotation
        if TransformAttribute.HasAttribute(node.Target):
            result = TransformAttribute.Resolve(node.Target, node.Arguments)
            result = VisitNode(result)
            ReplaceCurrentNode result
            return

        if GetAttribute[of VarArgsAttribute](node.Target) and not node.ContainsAnnotation('varargs-processed'):
            node['varargs-processed'] = true
            if node.Arguments[-1] isa ListLiteralExpression:
                last = node.Arguments[-1] as ListLiteralExpression
                node.Arguments.Remove(last)
                for itm in last.Items:
                    node.Arguments.Add(itm)
                return

            params = ListLiteralExpression()
            for i in range(0, len(node.Arguments)-1):
                params.Items.Add( node.Arguments[i] )
            mie = [| $(params).concat($(node.Arguments[-1])) |]
            if node.Target isa MemberReferenceExpression:
                target = (node.Target as MemberReferenceExpression).Target
            else:
                target = [| Boo.UNDEF |]
            node = [| $(node.Target).apply($target, $mie) |]
            ReplaceCurrentNode node

        /*
        rts = TypeSystemServices.RuntimeType
        rtsInvoke = ResolveMethod(rts, 'Invoke')
        rtsInvokeCallable = ResolveMethod(rts, 'InvokeCallable')
        rtsInvokeBinaryOperator = ResolveMethod(rts, 'InvokeBinaryOperator')
        rtsInvokeUnaryOperator = ResolveMethod(rts, 'InvokeUnaryOperator')
        rtsSetProperty = ResolveMethod(rts, 'SetProperty')
        rtsGetProperty = ResolveMethod(rts, 'GetProperty')
        rtsSetSlice = ResolveMethod(rts, 'SetSlice')
        rtsGetSlice = ResolveMethod(rts, 'GetSlice')
        */

        if node.Target.Entity == MethodCache.InvokeBinaryOperator:

            # Convert it back into a binary expression
            be = BinaryExpression(node.LexicalInfo)
            be.ExpressionType = node.ExpressionType
            be.Left = node.Arguments[1]
            be.Right = node.Arguments[2]

            # Remove the op_ to find the operator type
            operator = (node.Arguments[0] as StringLiteralExpression).Value[3:]
            be.Operator = System.Enum.Parse(BinaryOperatorType, operator)

            ReplaceCurrentNode Visit(be)
            return

        if node.Target.Entity == MethodCache.InvokeUnaryOperator:
            # Convert it back into a unary expression
            ue = UnaryExpression(node.LexicalInfo)
            ue.ExpressionType = node.ExpressionType
            ue.Operand = node.Arguments[1]

            # Remove the op_ to find the operator type
            operator = (node.Arguments[0] as StringLiteralExpression).Value[3:]
            ue.Operator = System.Enum.Parse(UnaryOperatorType, operator)

            ReplaceCurrentNode Visit(ue)
            return

        super(node)

    protected def LocalsToDecls(locals as LocalCollection):
        # Convert locals back to simple declaration statements
        found = ['$locals']
        for local in locals:
            # Avoid duplicates
            if local.Name is null or local.Name in found:
                continue

            found.Push(local.Name)

            decl = Declaration(LexicalInfo: local.LexicalInfo)
            decl.Name = local.Name

            # Detect local type
            initializer as Expression = NullLiteralExpression()
            entity = local.Entity as TypeSystem.Internal.InternalLocal
            if entity:
                if entity.OriginalDeclaration:
                    # Skip declarations for those flagged as global
                    continue if entity.OriginalDeclaration.ContainsAnnotation('global')
                    decl.Type = entity.OriginalDeclaration.Type
                else:
                    # TODO: Can we generate proper type annotations with this?
                    decl.Type = SimpleTypeReference(Entity: entity)

                # Initialize with a default value
                if TypeSystemServices.IsNumber(entity.Type):
                    initializer = IntegerLiteralExpression(0)
                elif entity.Type == TypeSystemServices.TimeSpanType:
                    initializer = IntegerLiteralExpression(0)
                elif entity.Type == TypeSystemServices.BoolType:
                    initializer = BoolLiteralExpression(false)
                elif entity.Type == TypeSystemServices.StringType:
                    initializer = StringLiteralExpression('')

            yield DeclarationStatement(LexicalInfo: local.LexicalInfo, Declaration: decl, Initializer: initializer)

    def OnBlockExpression(node as BlockExpression):
    """ Convert locals annotation to declarations
    """
        if node.ContainsAnnotation('locals'):
            for st in LocalsToDecls(node['locals']):
                node.Body.Insert(0, st)
            node.RemoveAnnotation('locals')

        Visit node.Body

    def OnMethod(node as Method):
    """ Process locals and detect the Main method to move its statements into the Module globals
    """
        # Skip compiler generated methods
        if node.IsSynthetic and node.IsInternal:
            RemoveCurrentNode
            return

        for st in LocalsToDecls(node.Locals):
            node.Body.Insert(0, st)

        # Detect the Main method and move its statements to the Module globals
        if IsEntryPoint(node):
            Visit node.Body
            node.EnclosingModule.Globals = node.Body
            RemoveCurrentNode
            return

        super(node)

    def IsEntryPoint(node as Method):
        # Note: We cannot use ContextAnnotations.GetEntryPoint(Context) to detect
        # the entry point since when emitting the assembly we have to clone the AST
        # and then the node instance is different.
        return node.Name == IntroduceModuleClasses.EntryPointMethodName \
               and node.IsSynthetic and node.IsStatic and node.IsPrivate

    def OnCastExpression(node as CastExpression):
        mie = [| Boo.$(ReferenceExpression('cast'))() |]
        mie.LexicalInfo = node.LexicalInfo
        mie.Arguments.Add(node.Target)
        ReplaceCurrentNode( ProcessCast(mie, node.Type) )

    def OnTryCastExpression(node as TryCastExpression):
        mie = [| Boo.trycast() |]
        mie.LexicalInfo = node.LexicalInfo
        mie.Arguments.Add(node.Target)
        ReplaceCurrentNode( ProcessCast(mie, node.Type) )

    protected def ProcessCast(mie as MethodInvocationExpression, type as Node) as MethodInvocationExpression:
        if not type isa Expression:
            type = TypeofExpression(type.LexicalInfo, type)

        mie.Arguments.Add(type)
        return Visit(mie)

    def OnCharLiteralExpression(node as CharLiteralExpression):
    """ There is no char type in BooJs. char('c') and char(int) are converted to a string of length 1
    """
        ReplaceCurrentNode StringLiteralExpression(LexicalInfo: node.LexicalInfo, Value: node.Value)

    def OnRELiteralExpression(node as RELiteralExpression):
    """ Almost a direct translation to Javascript regexp literals. The only modification
        is that we ignore whitespace to allow the regexps to be somehow more readable while
        Boo's handling is to escape it as part of the match.

            / foo | bar /i  ->  /foo|bar/i
            @/ foo | bar /  ->  /foo|bar/i

        TODO: The ones prefixed with @ should be preprocessed to support new lines
    """
        re = node.Value
        start = re.IndexOf('/')
        stop = re.LastIndexOf('/')

        modifier = re[stop:]
        re = re[start:stop]

        # Ignore white space
        re = /\s+/.Replace(re, '')

        # Javascript does not support the single-line modifier (dot matches new lines).
        # We emulate it by converting dots to the [\s\S] expression in the regex.
        # NOTE: The algorithm will break when a dot is preceeded by an escaped back slash (\\.)
        if modifier.Contains('s'):
            re = /(?<!\\)\./.Replace(re, '[\\s\\S]')
            modifier = modifier.Replace('s', '')

        node.Value = re + modifier

    def OnArrayLiteralExpression(node as ArrayLiteralExpression):
    """ In BooJs arrays are muttable (Boo's Lists)
    """
        super(node)
        list = ListLiteralExpression(node.LexicalInfo, Items: node.Items)
        ReplaceCurrentNode list

    def OnUnlessStatement(node as UnlessStatement):
    """ Negate the condition to convert it into a simple if statement
    """
        ifst = IfStatement(LexicalInfo: node.LexicalInfo)
        ifst.Condition = UnaryExpression(
            Operator: UnaryOperatorType.LogicalNot,
            Operand: node.Condition,
            LexicalInfo: node.LexicalInfo
        )
        ifst.TrueBlock = node.Block

        ReplaceCurrentNode ifst

    def OnBinaryExpression(node as BinaryExpression):
        if node.Operator == BinaryOperatorType.TypeTest:
            mie = MethodInvocationExpression(node.LexicalInfo)
            mie.Target = ReferenceExpression(node.LexicalInfo, 'Boo.isa')
            mie.Arguments.Add(node.Left)
            mie.Arguments.Add(node.Right)
            Visit mie.Arguments
            ReplaceCurrentNode mie
            return

        super(node)

    def OnTypeofExpression(node as TypeofExpression):
        if st = node.Type as SimpleTypeReference:

            # Primitives are just enclosed as strings
            if TypeSystemServices.IsPrimitive(st.Name):
                ReplaceCurrentNode StringLiteralExpression(node.LexicalInfo, st.Name)
                return

            # If we have an entity use it
            if exent = st.Entity as TypeSystem.Reflection.ExternalType:
                refe = CodeBuilder.CreateReference(node.LexicalInfo, exent.ActualType)
                ReplaceCurrentNode ProcessReference(refe)
                return

            # Just rely on the type name
            refe = ReferenceExpression(node.LexicalInfo, st.Name)
            ReplaceCurrentNode ProcessReference(refe)

        elif node.Type isa ArrayTypeReference:

            refe = CodeBuilder.CreateReference(node.LexicalInfo, TypeSystemServices.ArrayType)
            ReplaceCurrentNode ProcessReference(refe)
            return

        else:
            raise 'Unsupported TypeReference: ' + node.Type + ' (' + node.Type.NodeType + ')'

    def OnUnaryExpression(node as UnaryExpression):
        # Logical Not folding
        if node.Operator == UnaryOperatorType.LogicalNot:
            # not(not(expr)) -> expr
            if ue = node.Operand as UnaryExpression and ue.Operator == UnaryOperatorType.LogicalNot:
                ReplaceCurrentNode Visit(ue.Operand)
                return

            if be = node.Operand as BinaryExpression:
                inverses = {
                    BinaryOperatorType.Equality: BinaryOperatorType.Inequality,
                    BinaryOperatorType.Inequality: BinaryOperatorType.Equality,
                    BinaryOperatorType.GreaterThan: BinaryOperatorType.LessThanOrEqual,
                    BinaryOperatorType.LessThanOrEqual: BinaryOperatorType.GreaterThan,
                    BinaryOperatorType.LessThan: BinaryOperatorType.GreaterThanOrEqual,
                    BinaryOperatorType.GreaterThanOrEqual: BinaryOperatorType.LessThan,
                }

                if be.Operator in inverses:
                    be.Operator = inverses[be.Operator]
                    ReplaceCurrentNode Visit(be)
                    return

        super(node)

    def OnBlock(node as Block):

        Visit node.Statements

        # Flatten nested blocks
        ofs = 0
        while ofs < len(node.Statements):
            st = node.Statements[ofs]
            if st.NodeType == NodeType.Block:
                node.Statements.Remove(st)
                for st2 in (st as Block).Statements:
                    node.Statements.Insert(ofs, st2)
                    ofs++
            else:
                ofs++


license

license

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

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

import System
import System.Threading

def run():
    print("executing")

print("started")
result = run.BeginInvoke({ print("called back") })
Thread.Sleep(50ms)
run.EndInvoke(result)

print("done")


fix the test case to make sure it also runs on .net

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

"""
False
True
"""

import System
import System.Collections
import System.Collections.Generic

c1 = Comparer.Default
c2 = Comparer[of string].Default

print c1.GetType().IsGenericType
print c2.GetType().IsGenericType

"""
System.Collections.Comparer
System.Collections.Generic.IComparableOfTComparer`1[System.String]
"""

import System
import System.Collections
import System.Collections.Generic

c1 = Comparer.Default
c2 = Comparer[of string].Default

print c1.GetType()
print c2.GetType()