astgen now uses the recommended approach of parsing the file through a pipeline

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

#region license
// boo - an extensible programming language for the CLI
// Copyright (C) 2004 Rodrigo B. de Oliveira
//
// This program 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.
//
// This program 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 this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//
// As a special exception, if you link this library with other files to
// produce an executable, this library does not by itself cause the
// resulting executable to be covered by the GNU General Public License.
// This exception does not however invalidate any other reasons why the
// executable file might be covered by the GNU General Public License.
//
// Contact Information
//
// mailto:rbo@acm.org
#endregion

import System
import System.IO
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Pipelines
import Boo.Lang.Compiler.Ast

def WriteNodeTypeEnum(module as Module):
	using writer=OpenFile(GetPath("NodeType.cs")):
		WriteLicense(writer)
		WriteWarning(writer)
		writer.WriteLine("""
namespace Boo.Lang.Compiler.Ast
{
	using System;

	[Serializable]
	public enum NodeType
	{""")

		nodes = GetConcreteAstNodes(module)
		last = nodes[-1]
		for item as TypeDefinition in nodes:
			writer.Write("\t\t${item.Name}")
			if item is not last:
				writer.WriteLine(", ")

		writer.Write("""
	}
}""")

def WriteEnum(node as EnumDefinition):
	using writer=OpenFile(GetPathFromNode(node)):
		WriteLicense(writer)
		writer.Write("""
namespace Boo.Lang.Compiler.Ast
{
	using System;

	[Serializable]
	public enum ${node.Name}
	{
""")
		last = node.Members[-1]
		for field as EnumMember in node.Members:
			writer.Write("\t\t${field.Name}")
			if field.Initializer:
				writer.Write(" = ${field.Initializer.Value}")
			if field is not last:
				writer.Write(",")
			writer.WriteLine()

		writer.Write("""
	}
}
""")

def FormatParameterList(fields as List):
	return join(
			"${field.Type} ${GetParameterName(field)}"
			for field as Field in fields,
			", ")

def GetParameterName(field as Field):
	name = field.Name
	name = name[0:1].ToLower() + name[1:]
	if name in "namespace", "operator":
		name += "_"
	return name

def WriteAssignmentsFromParameters(writer as TextWriter, fields as List):
	for field as Field in fields:
		writer.Write("""
			${field.Name} = ${GetParameterName(field)};""")

def WriteClassImpl(node as ClassDefinition):

	allFields = GetAllFields(node)

	using writer=OpenFile(GetPath("Impl/${node.Name}Impl.cs")):
		WriteLicense(writer)
		WriteWarning(writer)
		writer.WriteLine("""
namespace Boo.Lang.Compiler.Ast.Impl
{
	using Boo.Lang.Compiler.Ast;
	using System.Collections;
	using System.Runtime.Serialization;

	[System.Serializable]
	public abstract class ${node.Name}Impl : ${join(node.BaseTypes, ', ')}
	{
""")

		for field as Field in node.Members:
			writer.WriteLine("\t\tprotected ${field.Type} ${GetPrivateName(field)};");

		writer.WriteLine("""
		protected ${node.Name}Impl()
		{
			InitializeFields();
		}

		protected ${node.Name}Impl(LexicalInfo info) : base(info)
		{
			InitializeFields();
		}
		""")

		simpleFields = GetSimpleFields(node)
		if len(simpleFields):
			writer.Write("""
		protected ${node.Name}Impl(${FormatParameterList(simpleFields)})
		{
			InitializeFields();""")

			WriteAssignmentsFromParameters(writer, simpleFields)
			writer.Write("""
		}

		protected ${node.Name}Impl(LexicalInfo lexicalInfo, ${FormatParameterList(simpleFields)}) : base(lexicalInfo)
		{
			InitializeFields();""")
			WriteAssignmentsFromParameters(writer, simpleFields)
			writer.Write("""
		}
			""")

		writer.WriteLine("""
		new public ${node.Name} CloneNode()
		{
			return Clone() as ${node.Name};
		}""")

		unless IsAbstract(node):
			writer.WriteLine("""
		override public NodeType NodeType
		{
			get
			{
				return NodeType.${node.Name};
			}
		}""")

			writer.WriteLine("""
		override public bool Replace(Node existing, Node newNode)
		{
			if (base.Replace(existing, newNode))
			{
				return true;
			}""")

			for field as Field in allFields:
				fieldType=ResolveFieldType(field)
				if not fieldType:
					continue

				fieldName=GetPrivateName(field)
				if IsCollection(fieldType):
					collectionItemType = GetCollectionItemType(fieldType)
					writer.WriteLine("""
			if (${fieldName} != null)
			{
				${collectionItemType} item = existing as ${collectionItemType};
				if (null != item)
				{
					${collectionItemType} newItem = (${collectionItemType})newNode;
					if (${fieldName}.Replace(item, newItem))
					{
						return true;
					}
				}
			}""")
				else:
					unless IsEnum(fieldType):
						writer.WriteLine("""
			if (${fieldName} == existing)
			{
				this.${field.Name} = (${field.Type})newNode;
				return true;
			}""")

			writer.WriteLine("""
			return false;
		}""")

			writer.WriteLine("""
		override public object Clone()
		{
			${node.Name} clone = FormatterServices.GetUninitializedObject(typeof(${node.Name})) as ${node.Name};
			clone._lexicalInfo = _lexicalInfo;
			clone._documentation = _documentation;
			clone._entity = _entity;
			""")

			if IsExpression(node):
				writer.WriteLine("""
			clone._expressionType = _expressionType;
			""");

			for field as Field in allFields:
				fieldType = ResolveFieldType(field)
				fieldName = GetPrivateName(field)
				if fieldType and not IsEnum(fieldType):
					writer.WriteLine("""
			if (null != ${fieldName})
			{
				clone.${fieldName} = ${fieldName}.Clone() as ${field.Type};
				clone.${fieldName}.InitializeParent(clone);
			}""")
				else:
					writer.WriteLine("""
			clone.${fieldName} = ${fieldName};""")

			writer.Write("""
			return clone;
		}
			""")

		for field as Field in node.Members:
			writer.WriteLine("""
		public ${field.Type} ${field.Name}
		{
			get
			{
				return ${GetPrivateName(field)};
			}
			""")

			fieldType = ResolveFieldType(field)
			if fieldType and not IsEnum(fieldType):
				writer.WriteLine("""
			set
			{
				if (${GetPrivateName(field)} != value)
				{
					${GetPrivateName(field)} = value;
					if (null != ${GetPrivateName(field)})
					{
						${GetPrivateName(field)}.InitializeParent(this);""")

				if field.Attributes.Contains("LexicalInfo"):
					writer.WriteLine("""
						LexicalInfo = value.LexicalInfo;""")

				writer.WriteLine("""
					}
				}
			}
			""")
			else:
				writer.WriteLine("""
			set
			{
				${GetPrivateName(field)} = value;
			}""")

			writer.WriteLine("""
		}
		""")

		writer.WriteLine("""
		private void InitializeFields()
		{""")

		for field as Field in node.Members:
			if IsCollectionField(field):
				writer.WriteLine("\t\t\t${GetPrivateName(field)} = new ${field.Type}(this);")
			else:
				if field.Attributes.Contains("auto"):
					writer.WriteLine("""
			${GetPrivateName(field)} = new ${field.Type}();
			${GetPrivateName(field)}.InitializeParent(this);
			""")

		writer.WriteLine("""
		}
	}
}""")

def WriteClass(node as ClassDefinition):
	path = GetPathFromNode(node);
	return if File.Exists(path)

	using writer=OpenFile(path):
		WriteLicense(writer)
		writer.Write("""
namespace Boo.Lang.Compiler.Ast
{
	using System;

	[Serializable]
	public class ${node.Name} : Boo.Lang.Compiler.Ast.Impl.${node.Name}Impl
	{
		public ${node.Name}()
		{
		}

		public ${node.Name}(LexicalInfo lexicalInfo) : base(lexicalInfo)
		{
		}
	}
}
""")

def WriteCollection(node as ClassDefinition):
	path = GetPathFromNode(node)
	return if File.Exists(path)

	using writer=OpenFile(path):
		WriteLicense(writer)
		writer.Write("""
namespace Boo.Lang.Compiler.Ast
{
	using System;

	[Serializable]
	public class ${node.Name} : Boo.Lang.Compiler.Ast.Impl.${node.Name}Impl
	{
		public ${node.Name}()
		{
		}

		public ${node.Name}(Boo.Lang.Compiler.Ast.Node parent) : base(parent)
		{
		}
	}
}
""")

def GetCollectionItemType(node as ClassDefinition):
	attribute = node.Attributes.Get("collection")[0]
	reference as ReferenceExpression = attribute.Arguments[0]
	return reference.Name

def ResolveFieldType(field as Field):
	return field.DeclaringType.DeclaringType.Members[(field.Type as SimpleTypeReference).Name]

def IsExpression(node as ClassDefinition):
	return IsSubclassOf(node, "Expression")

def GetResultingTransformerNode(node as ClassDefinition):
	for subclass in "Statement", "Expression", "TypeReference":
		if IsSubclassOf(node, subclass):
			return subclass
	return node.Name

def IsSubclassOf(node as ClassDefinition, typename as string):
	for typeref as SimpleTypeReference in node.BaseTypes:
		if typename == typeref.Name:
			return true
		baseType = node.DeclaringType.Members[typeref.Name]
		if baseType and IsSubclassOf(baseType, typename):
			return true
	return false

def WriteCollectionImpl(node as ClassDefinition):
	path = GetPath("Impl/${node.Name}Impl.cs")
	using writer=OpenFile(path):
		WriteLicense(writer)
		WriteWarning(writer)

		itemType = "Boo.Lang.Compiler.Ast." + GetCollectionItemType(node)
		writer.Write("""
namespace Boo.Lang.Compiler.Ast.Impl
{
	using System;
	using Boo.Lang.Compiler.Ast;

	[Serializable]
	[Boo.Lang.EnumeratorItemType(typeof(${itemType}))]
	public class ${node.Name}Impl : NodeCollection
	{
		protected ${node.Name}Impl()
		{
		}

		protected ${node.Name}Impl(Node parent) : base(parent)
		{
		}

		public ${itemType} this[int index]
		{
			get
			{
				return (${itemType})InnerList[index];
			}
		}

		public void Add(${itemType} item)
		{
			base.AddNode(item);
		}

		public void Extend(params ${itemType}[] items)
		{
			base.AddNodes(items);
		}

		public void Extend(System.Collections.ICollection items)
		{
			foreach (${itemType} item in items)
			{
				base.AddNode(item);
			}
		}

		public void ExtendWithClones(System.Collections.ICollection items)
		{
			foreach (${itemType} item in items)
			{
				base.AddNode(item.CloneNode());
			}
		}

		public void Insert(int index, ${itemType} item)
		{
			base.InsertNode(index, item);
		}

		public bool Replace(${itemType} existing, ${itemType} newItem)
		{
			return base.ReplaceNode(existing, newItem);
		}

		public new ${itemType}[] ToArray()
		{
			return (${itemType}[])InnerList.ToArray(typeof(${itemType}));
		}
	}
}
""")

def OpenFile(fname as string):
	print(fname)
	return StreamWriter(fname, false, System.Text.Encoding.UTF8)

def GetPath(fname as string):
	return Path.Combine("src/Boo.Lang.Compiler/Ast", fname)

def GetPathFromNode(node as TypeMember):
	return GetPath("${node.Name}.cs")

def IsCollection(node as TypeMember):
	return node.Attributes.Contains("collection")

def IsCollectionField(field as Field):
	return (field.Type as SimpleTypeReference).Name.EndsWith("Collection")

def IsEnum(node as TypeMember):
	return NodeType.EnumDefinition == node.NodeType

def IsAbstract(member as TypeMember):
	return member.IsModifierSet(TypeMemberModifiers.Abstract)

def GetConcreteAstNodes(module as Module):
	nodes = []
	for member in module.Members:
		nodes.Add(member) if IsConcreteAstNode(member)
	return nodes

def IsConcreteAstNode(member as TypeMember):
	return not (IsCollection(member) or IsEnum(member) or IsAbstract(member))

def WriteWarning(writer as TextWriter):
	writer.Write(
"""
//
// DO NOT EDIT THIS FILE!
//
// This file was generated automatically by
// astgenerator.boo on ${date.Now}
//
""")

def WriteLicense(writer as TextWriter):
	writer.Write(
"""#region license
// boo - an extensible programming language for the CLI
// Copyright (C) 2004 Rodrigo B. de Oliveira
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// Contact Information
//
// mailto:rbo@acm.org
#endregion
""")

def WriteDepthFirstTransformer(module as Module):

	using writer=OpenFile(GetPath("DepthFirstTransformer.cs")):
		WriteLicense(writer)
		WriteWarning(writer)
		writer.Write("""
namespace Boo.Lang.Compiler.Ast
{
	using System;

	public class DepthFirstTransformer : IAstVisitor
	{

		protected Node _resultingNode = null;

	""")

		for item as TypeMember in module.Members:
			continue unless IsConcreteAstNode(item)

			switchableFields = GetAcceptableFields(item)
			resultingNodeType = GetResultingTransformerNode(item)

			writer.WriteLine("""
		public virtual void On${item.Name}(Boo.Lang.Compiler.Ast.${item.Name} node)
		{""")

			if len(switchableFields):
				writer.WriteLine("""
			if (Enter${item.Name}(node))
			{""")
				for field as Field in switchableFields:
					if IsCollectionField(field):
						writer.WriteLine("""
				Visit(node.${field.Name});""")
					else:
						writer.WriteLine("""
				${field.Type} current${field.Name}Value = node.${field.Name};
				if (null != current${field.Name}Value)
				{
					node.${field.Name} = (${field.Type})VisitNode(current${field.Name}Value);
				}""")

				writer.WriteLine("""
				Leave${item.Name}(node);
			}""")

			writer.WriteLine("""
		}""")

			if len(switchableFields):
				writer.WriteLine("""
		public virtual bool Enter${item.Name}(Boo.Lang.Compiler.Ast.${item.Name} node)
		{
			return true;
		}

		public virtual void Leave${item.Name}(Boo.Lang.Compiler.Ast.${item.Name} node)
		{
		}""")

		writer.WriteLine("""

		protected void RemoveCurrentNode()
		{
			_resultingNode = null;
		}

		protected void ReplaceCurrentNode(Node replacement)
		{
			_resultingNode = replacement;
		}

		public Node VisitNode(Node node)
		{
			if (null != node)
			{
				try
				{
					Node saved = _resultingNode;
					_resultingNode = node;
					node.Accept(this);
					Node result = _resultingNode;
					_resultingNode = saved;
					return result;
				}
				catch (Boo.Lang.Compiler.CompilerError)
				{
					throw;
				}
				catch (Exception error)
				{
					throw Boo.Lang.Compiler.CompilerErrorFactory.InternalError(node, error);
				}
			}
			return null;
		}

		public Node Visit(Node node)
		{
			return VisitNode(node);
		}

		public Expression Visit(Expression node)
		{
			return (Expression)VisitNode(node);
		}

		public Statement Visit(Statement node)
		{
			return (Statement)VisitNode(node);
		}

		public bool Visit(NodeCollection collection)
		{
			if (null != collection)
			{
				int removed = 0;

				Node[] nodes = collection.ToArray();
				for (int i=0; i<nodes.Length; ++i)
				{
					Node currentNode = nodes[i];
					Node resultingNode = VisitNode(currentNode);
					if (currentNode != resultingNode)
					{
						int actualIndex = i-removed;
						if (null == resultingNode)
						{
							++removed;
							collection.RemoveAt(actualIndex);
						}
						else
						{
							collection.ReplaceAt(actualIndex, resultingNode);
						}
					}
				}
				return true;
			}
			return false;
		}
	}
}
""")

def WriteVisitor(module as Module):
	using writer=OpenFile(GetPath("IAstVisitor.cs")):
		WriteLicense(writer)
		WriteWarning(writer)
		writer.Write(
"""
namespace Boo.Lang.Compiler.Ast
{
	using System;

	public interface IAstVisitor
	{
""")
		for member as TypeMember in module.Members:
			if IsConcreteAstNode(member):
				writer.WriteLine("\t\tvoid On${member.Name}(${member.Name} node);")

		writer.Write(
"""
	}
}
""")

def GetTypeHierarchy(item as ClassDefinition):
	types = []
	ProcessTypeHierarchy(types, item)
	return types

def ProcessTypeHierarchy(types as List, item as ClassDefinition):
	module as Module = item.ParentNode
	for baseTypeRef as SimpleTypeReference in item.BaseTypes:
		if baseType = module.Members[baseTypeRef.Name]:
			ProcessTypeHierarchy(types, baseType)
	types.Add(item)

def GetPrivateName(field as Field):
	name = field.Name
	return "_" + name[0:1].ToLower() + name[1:]

def GetSimpleFields(node as ClassDefinition):

	return [field
			for field as Field in node.Members
			unless IsCollectionField(field) or field.Attributes.Contains("auto")]

def GetAllFields(node as ClassDefinition):
	fields = []

	for item as TypeDefinition in GetTypeHierarchy(node):
		fields.Extend(item.Members)

	return fields

def GetAcceptableFields(item as ClassDefinition):

	fields = []

	for item as TypeDefinition in GetTypeHierarchy(item):
		for field as Field in item.Members:
			type = ResolveFieldType(field)
			fields.Add(field) if type and not IsEnum(type)

	return fields

def WriteDepthFirstAccept(writer as TextWriter, item as ClassDefinition):
	fields = GetAcceptableFields(item)

	if len(fields):
		writer.WriteLine("""
		public virtual void On${item.Name}(Boo.Lang.Compiler.Ast.${item.Name} node)
		{
			if (Enter${item.Name}(node))
			{""")

		for field as Field in fields:
			writer.WriteLine("\t\t\tVisit(node.${field.Name});")

		writer.Write(
"""				Leave${item.Name}(node);
			}
		}

		public virtual bool Enter${item.Name}(Boo.Lang.Compiler.Ast.${item.Name} node)
		{
			return true;
		}

		public virtual void Leave${item.Name}(Boo.Lang.Compiler.Ast.${item.Name} node)
		{
		}
			""")
	else:
		writer.Write("""
		public virtual void On${item.Name}(Boo.Lang.Compiler.Ast.${item.Name} node)
		{
		}
			""")

def WriteDepthFirstVisitor(module as Module):
	using writer=OpenFile(GetPath("DepthFirstVisitor.cs")):
		WriteLicense(writer)
		WriteWarning(writer)
		writer.Write(
"""
namespace Boo.Lang.Compiler.Ast
{
	using System;

	public class DepthFirstVisitor : IAstVisitor
	{
		public bool Visit(Node node)
		{
			if (null != node)
			{
				try
				{
					node.Accept(this);
					return true;
				}
				catch (Boo.Lang.Compiler.CompilerError)
				{
					throw;
				}
				catch (Exception error)
				{
					throw Boo.Lang.Compiler.CompilerErrorFactory.InternalError(node, error);
				}
			}
			return false;
		}

		public void Visit(Node[] array, NodeType nodeType)
		{
			foreach (Node node in array)
			{
				if (node.NodeType == nodeType)
				{
					Visit(node);
				}
			}
		}

		public bool Visit(NodeCollection collection, NodeType nodeType)
		{
			if (null != collection)
			{
				Visit(collection.ToArray(), nodeType);
				return true;
			}
			return false;
		}

		public void Visit(Node[] array)
		{
			foreach (Node node in array)
			{
				Visit(node);
			}
		}

		public bool Visit(NodeCollection collection)
		{
			if (null != collection)
			{
				Visit(collection.ToArray());
				return true;
			}
			return false;
		}
		""")

		for member as TypeMember in module.Members:
			if IsConcreteAstNode(member):
				WriteDepthFirstAccept(writer, member)

		writer.Write("""
	}
}
""")

def parse(fname):
	compiler = BooCompiler()
	compiler.Parameters.Pipeline = Parse()
	compiler.Parameters.Input.Add(Boo.Lang.Compiler.IO.FileInput(fname))
	result = compiler.Run()
	raise join(result.Errors, "\n") if len(result.Errors)
	return result.CompileUnit

start = date.Now

module = parse("ast.model.boo").Modules[0]

WriteVisitor(module)
WriteDepthFirstVisitor(module)
WriteDepthFirstTransformer(module)
WriteNodeTypeEnum(module)
for member as TypeMember in module.Members:

	if member.Attributes.Contains("ignore"):
		continue

	if IsEnum(member):
		WriteEnum(member)
	else:
		if IsCollection(member):
			WriteCollection(member)
			WriteCollectionImpl(member)
		else:
			WriteClass(member)
			WriteClassImpl(member)

stop = date.Now
print("ast classes generated in ${stop-start}.")

#region license
// boo - an extensible programming language for the CLI
// Copyright (C) 2004 Rodrigo B. de Oliveira
//
// This program 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.
//
// This program 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 this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//
// As a special exception, if you link this library with other files to
// produce an executable, this library does not by itself cause the
// resulting executable to be covered by the GNU General Public License.
// This exception does not however invalidate any other reasons why the
// executable file might be covered by the GNU General Public License.
//
// Contact Information
//
// mailto:rbo@acm.org
#endregion

import System
import System.IO
import Boo.AntlrParser from Boo.AntlrParser
import Boo.Lang.Compiler.Ast

def WriteNodeTypeEnum(module as Module):
	using writer=OpenFile(GetPath("NodeType.cs")):
		WriteLicense(writer)
		WriteWarning(writer)
		writer.WriteLine("""
namespace Boo.Lang.Compiler.Ast
{
	using System;

	[Serializable]
	public enum NodeType
	{""")

		nodes = GetConcreteAstNodes(module)
		last = nodes[-1]
		for item as TypeDefinition in nodes:
			writer.Write("\t\t${item.Name}")
			if item is not last:
				writer.WriteLine(", ")

		writer.Write("""
	}
}""")

def WriteEnum(node as EnumDefinition):
	using writer=OpenFile(GetPathFromNode(node)):
		WriteLicense(writer)
		writer.Write("""
namespace Boo.Lang.Compiler.Ast
{
	using System;

	[Serializable]
	public enum ${node.Name}
	{
""")
		last = node.Members[-1]
		for field as EnumMember in node.Members:
			writer.Write("\t\t${field.Name}")
			if field.Initializer:
				writer.Write(" = ${field.Initializer.Value}")
			if field is not last:
				writer.Write(",")
			writer.WriteLine()

		writer.Write("""
	}
}
""")

def FormatParameterList(fields as List):
	return join(
			"${field.Type} ${GetParameterName(field)}"
			for field as Field in fields,
			", ")

def GetParameterName(field as Field):
	name = field.Name
	name = name[0:1].ToLower() + name[1:]
	if name in "namespace", "operator":
		name += "_"
	return name

def WriteAssignmentsFromParameters(writer as TextWriter, fields as List):
	for field as Field in fields:
		writer.Write("""
			${field.Name} = ${GetParameterName(field)};""")

def WriteClassImpl(node as ClassDefinition):

	allFields = GetAllFields(node)

	using writer=OpenFile(GetPath("Impl/${node.Name}Impl.cs")):
		WriteLicense(writer)
		WriteWarning(writer)
		writer.WriteLine("""
namespace Boo.Lang.Compiler.Ast.Impl
{
	using Boo.Lang.Compiler.Ast;
	using System.Collections;
	using System.Runtime.Serialization;

	[System.Serializable]
	public abstract class ${node.Name}Impl : ${join(node.BaseTypes, ', ')}
	{
""")

		for field as Field in node.Members:
			writer.WriteLine("\t\tprotected ${field.Type} ${GetPrivateName(field)};");

		writer.WriteLine("""
		protected ${node.Name}Impl()
		{
			InitializeFields();
		}

		protected ${node.Name}Impl(LexicalInfo info) : base(info)
		{
			InitializeFields();
		}
		""")

		simpleFields = GetSimpleFields(node)
		if len(simpleFields):
			writer.Write("""
		protected ${node.Name}Impl(${FormatParameterList(simpleFields)})
		{
			InitializeFields();""")

			WriteAssignmentsFromParameters(writer, simpleFields)
			writer.Write("""
		}

		protected ${node.Name}Impl(LexicalInfo lexicalInfo, ${FormatParameterList(simpleFields)}) : base(lexicalInfo)
		{
			InitializeFields();""")
			WriteAssignmentsFromParameters(writer, simpleFields)
			writer.Write("""
		}
			""")

		writer.WriteLine("""
		new public ${node.Name} CloneNode()
		{
			return Clone() as ${node.Name};
		}""")

		unless IsAbstract(node):
			writer.WriteLine("""
		override public NodeType NodeType
		{
			get
			{
				return NodeType.${node.Name};
			}
		}""")

			writer.WriteLine("""
		override public bool Replace(Node existing, Node newNode)
		{
			if (base.Replace(existing, newNode))
			{
				return true;
			}""")

			for field as Field in allFields:
				fieldType=ResolveFieldType(field)
				if not fieldType:
					continue

				fieldName=GetPrivateName(field)
				if IsCollection(fieldType):
					collectionItemType = GetCollectionItemType(fieldType)
					writer.WriteLine("""
			if (${fieldName} != null)
			{
				${collectionItemType} item = existing as ${collectionItemType};
				if (null != item)
				{
					${collectionItemType} newItem = (${collectionItemType})newNode;
					if (${fieldName}.Replace(item, newItem))
					{
						return true;
					}
				}
			}""")
				else:
					unless IsEnum(fieldType):
						writer.WriteLine("""
			if (${fieldName} == existing)
			{
				this.${field.Name} = (${field.Type})newNode;
				return true;
			}""")

			writer.WriteLine("""
			return false;
		}""")

			writer.WriteLine("""
		override public object Clone()
		{
			${node.Name} clone = FormatterServices.GetUninitializedObject(typeof(${node.Name})) as ${node.Name};
			clone._lexicalInfo = _lexicalInfo;
			clone._documentation = _documentation;
			clone._entity = _entity;
			""")

			if IsExpression(node):
				writer.WriteLine("""
			clone._expressionType = _expressionType;
			""");

			for field as Field in allFields:
				fieldType = ResolveFieldType(field)
				fieldName = GetPrivateName(field)
				if fieldType and not IsEnum(fieldType):
					writer.WriteLine("""
			if (null != ${fieldName})
			{
				clone.${fieldName} = ${fieldName}.Clone() as ${field.Type};
				clone.${fieldName}.InitializeParent(clone);
			}""")
				else:
					writer.WriteLine("""
			clone.${fieldName} = ${fieldName};""")

			writer.Write("""
			return clone;
		}
			""")

		for field as Field in node.Members:
			writer.WriteLine("""
		public ${field.Type} ${field.Name}
		{
			get
			{
				return ${GetPrivateName(field)};
			}
			""")

			fieldType = ResolveFieldType(field)
			if fieldType and not IsEnum(fieldType):
				writer.WriteLine("""
			set
			{
				if (${GetPrivateName(field)} != value)
				{
					${GetPrivateName(field)} = value;
					if (null != ${GetPrivateName(field)})
					{
						${GetPrivateName(field)}.InitializeParent(this);""")

				if field.Attributes.Contains("LexicalInfo"):
					writer.WriteLine("""
						LexicalInfo = value.LexicalInfo;""")

				writer.WriteLine("""
					}
				}
			}
			""")
			else:
				writer.WriteLine("""
			set
			{
				${GetPrivateName(field)} = value;
			}""")

			writer.WriteLine("""
		}
		""")

		writer.WriteLine("""
		private void InitializeFields()
		{""")

		for field as Field in node.Members:
			if IsCollectionField(field):
				writer.WriteLine("\t\t\t${GetPrivateName(field)} = new ${field.Type}(this);")
			else:
				if field.Attributes.Contains("auto"):
					writer.WriteLine("""
			${GetPrivateName(field)} = new ${field.Type}();
			${GetPrivateName(field)}.InitializeParent(this);
			""")

		writer.WriteLine("""
		}
	}
}""")

def WriteClass(node as ClassDefinition):
	path = GetPathFromNode(node);
	return if File.Exists(path)

	using writer=OpenFile(path):
		WriteLicense(writer)
		writer.Write("""
namespace Boo.Lang.Compiler.Ast
{
	using System;

	[Serializable]
	public class ${node.Name} : Boo.Lang.Compiler.Ast.Impl.${node.Name}Impl
	{
		public ${node.Name}()
		{
		}

		public ${node.Name}(LexicalInfo lexicalInfo) : base(lexicalInfo)
		{
		}
	}
}
""")

def WriteCollection(node as ClassDefinition):
	path = GetPathFromNode(node)
	return if File.Exists(path)

	using writer=OpenFile(path):
		WriteLicense(writer)
		writer.Write("""
namespace Boo.Lang.Compiler.Ast
{
	using System;

	[Serializable]
	public class ${node.Name} : Boo.Lang.Compiler.Ast.Impl.${node.Name}Impl
	{
		public ${node.Name}()
		{
		}

		public ${node.Name}(Boo.Lang.Compiler.Ast.Node parent) : base(parent)
		{
		}
	}
}
""")

def GetCollectionItemType(node as ClassDefinition):
	attribute = node.Attributes.Get("collection")[0]
	reference as ReferenceExpression = attribute.Arguments[0]
	return reference.Name

def ResolveFieldType(field as Field):
	return field.DeclaringType.DeclaringType.Members[(field.Type as SimpleTypeReference).Name]

def IsExpression(node as ClassDefinition):
	return IsSubclassOf(node, "Expression")

def GetResultingTransformerNode(node as ClassDefinition):
	for subclass in "Statement", "Expression", "TypeReference":
		if IsSubclassOf(node, subclass):
			return subclass
	return node.Name

def IsSubclassOf(node as ClassDefinition, typename as string):
	for typeref as SimpleTypeReference in node.BaseTypes:
		if typename == typeref.Name:
			return true
		baseType = node.DeclaringType.Members[typeref.Name]
		if baseType and IsSubclassOf(baseType, typename):
			return true
	return false

def WriteCollectionImpl(node as ClassDefinition):
	path = GetPath("Impl/${node.Name}Impl.cs")
	using writer=OpenFile(path):
		WriteLicense(writer)
		WriteWarning(writer)

		itemType = "Boo.Lang.Compiler.Ast." + GetCollectionItemType(node)
		writer.Write("""
namespace Boo.Lang.Compiler.Ast.Impl
{
	using System;
	using Boo.Lang.Compiler.Ast;

	[Serializable]
	[Boo.Lang.EnumeratorItemType(typeof(${itemType}))]
	public class ${node.Name}Impl : NodeCollection
	{
		protected ${node.Name}Impl()
		{
		}

		protected ${node.Name}Impl(Node parent) : base(parent)
		{
		}

		public ${itemType} this[int index]
		{
			get
			{
				return (${itemType})InnerList[index];
			}
		}

		public void Add(${itemType} item)
		{
			base.AddNode(item);
		}

		public void Extend(params ${itemType}[] items)
		{
			base.AddNodes(items);
		}

		public void Extend(System.Collections.ICollection items)
		{
			foreach (${itemType} item in items)
			{
				base.AddNode(item);
			}
		}

		public void ExtendWithClones(System.Collections.ICollection items)
		{
			foreach (${itemType} item in items)
			{
				base.AddNode(item.CloneNode());
			}
		}

		public void Insert(int index, ${itemType} item)
		{
			base.InsertNode(index, item);
		}

		public bool Replace(${itemType} existing, ${itemType} newItem)
		{
			return base.ReplaceNode(existing, newItem);
		}

		public new ${itemType}[] ToArray()
		{
			return (${itemType}[])InnerList.ToArray(typeof(${itemType}));
		}
	}
}
""")

def OpenFile(fname as string):
	print(fname)
	return StreamWriter(fname, false, System.Text.Encoding.UTF8)

def GetPath(fname as string):
	return Path.Combine("src/Boo.Lang.Compiler/Ast", fname)

def GetPathFromNode(node as TypeMember):
	return GetPath("${node.Name}.cs")

def IsCollection(node as TypeMember):
	return node.Attributes.Contains("collection")

def IsCollectionField(field as Field):
	return (field.Type as SimpleTypeReference).Name.EndsWith("Collection")

def IsEnum(node as TypeMember):
	return NodeType.EnumDefinition == node.NodeType

def IsAbstract(member as TypeMember):
	return member.IsModifierSet(TypeMemberModifiers.Abstract)

def GetConcreteAstNodes(module as Module):
	nodes = []
	for member in module.Members:
		nodes.Add(member) if IsConcreteAstNode(member)
	return nodes

def IsConcreteAstNode(member as TypeMember):
	return not (IsCollection(member) or IsEnum(member) or IsAbstract(member))

def WriteWarning(writer as TextWriter):
	writer.Write(
"""
//
// DO NOT EDIT THIS FILE!
//
// This file was generated automatically by
// astgenerator.boo on ${date.Now}
//
""")

def WriteLicense(writer as TextWriter):
	writer.Write(
"""#region license
// boo - an extensible programming language for the CLI
// Copyright (C) 2004 Rodrigo B. de Oliveira
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// Contact Information
//
// mailto:rbo@acm.org
#endregion
""")

def WriteDepthFirstTransformer(module as Module):

	using writer=OpenFile(GetPath("DepthFirstTransformer.cs")):
		WriteLicense(writer)
		WriteWarning(writer)
		writer.Write("""
namespace Boo.Lang.Compiler.Ast
{
	using System;

	public class DepthFirstTransformer : IAstVisitor
	{

		protected Node _resultingNode = null;

	""")

		for item as TypeMember in module.Members:
			continue unless IsConcreteAstNode(item)

			switchableFields = GetAcceptableFields(item)
			resultingNodeType = GetResultingTransformerNode(item)

			writer.WriteLine("""
		public virtual void On${item.Name}(Boo.Lang.Compiler.Ast.${item.Name} node)
		{""")

			if len(switchableFields):
				writer.WriteLine("""
			if (Enter${item.Name}(node))
			{""")
				for field as Field in switchableFields:
					if IsCollectionField(field):
						writer.WriteLine("""
				Visit(node.${field.Name});""")
					else:
						writer.WriteLine("""
				${field.Type} current${field.Name}Value = node.${field.Name};
				if (null != current${field.Name}Value)
				{
					node.${field.Name} = (${field.Type})VisitNode(current${field.Name}Value);
				}""")

				writer.WriteLine("""
				Leave${item.Name}(node);
			}""")

			writer.WriteLine("""
		}""")

			if len(switchableFields):
				writer.WriteLine("""
		public virtual bool Enter${item.Name}(Boo.Lang.Compiler.Ast.${item.Name} node)
		{
			return true;
		}

		public virtual void Leave${item.Name}(Boo.Lang.Compiler.Ast.${item.Name} node)
		{
		}""")

		writer.WriteLine("""

		protected void RemoveCurrentNode()
		{
			_resultingNode = null;
		}

		protected void ReplaceCurrentNode(Node replacement)
		{
			_resultingNode = replacement;
		}

		public Node VisitNode(Node node)
		{
			if (null != node)
			{
				try
				{
					Node saved = _resultingNode;
					_resultingNode = node;
					node.Accept(this);
					Node result = _resultingNode;
					_resultingNode = saved;
					return result;
				}
				catch (Boo.Lang.Compiler.CompilerError)
				{
					throw;
				}
				catch (Exception error)
				{
					throw Boo.Lang.Compiler.CompilerErrorFactory.InternalError(node, error);
				}
			}
			return null;
		}

		public Node Visit(Node node)
		{
			return VisitNode(node);
		}

		public Expression Visit(Expression node)
		{
			return (Expression)VisitNode(node);
		}

		public Statement Visit(Statement node)
		{
			return (Statement)VisitNode(node);
		}

		public bool Visit(NodeCollection collection)
		{
			if (null != collection)
			{
				int removed = 0;

				Node[] nodes = collection.ToArray();
				for (int i=0; i<nodes.Length; ++i)
				{
					Node currentNode = nodes[i];
					Node resultingNode = VisitNode(currentNode);
					if (currentNode != resultingNode)
					{
						int actualIndex = i-removed;
						if (null == resultingNode)
						{
							++removed;
							collection.RemoveAt(actualIndex);
						}
						else
						{
							collection.ReplaceAt(actualIndex, resultingNode);
						}
					}
				}
				return true;
			}
			return false;
		}
	}
}
""")

def WriteVisitor(module as Module):
	using writer=OpenFile(GetPath("IAstVisitor.cs")):
		WriteLicense(writer)
		WriteWarning(writer)
		writer.Write(
"""
namespace Boo.Lang.Compiler.Ast
{
	using System;

	public interface IAstVisitor
	{
""")
		for member as TypeMember in module.Members:
			if IsConcreteAstNode(member):
				writer.WriteLine("\t\tvoid On${member.Name}(${member.Name} node);")

		writer.Write(
"""
	}
}
""")

def GetTypeHierarchy(item as ClassDefinition):
	types = []
	ProcessTypeHierarchy(types, item)
	return types

def ProcessTypeHierarchy(types as List, item as ClassDefinition):
	module as Module = item.ParentNode
	for baseTypeRef as SimpleTypeReference in item.BaseTypes:
		if baseType = module.Members[baseTypeRef.Name]:
			ProcessTypeHierarchy(types, baseType)
	types.Add(item)

def GetPrivateName(field as Field):
	name = field.Name
	return "_" + name[0:1].ToLower() + name[1:]

def GetSimpleFields(node as ClassDefinition):

	return [field
			for field as Field in node.Members
			unless IsCollectionField(field) or field.Attributes.Contains("auto")]

def GetAllFields(node as ClassDefinition):
	fields = []

	for item as TypeDefinition in GetTypeHierarchy(node):
		fields.Extend(item.Members)

	return fields

def GetAcceptableFields(item as ClassDefinition):

	fields = []

	for item as TypeDefinition in GetTypeHierarchy(item):
		for field as Field in item.Members:
			type = ResolveFieldType(field)
			fields.Add(field) if type and not IsEnum(type)

	return fields

def WriteDepthFirstAccept(writer as TextWriter, item as ClassDefinition):
	fields = GetAcceptableFields(item)

	if len(fields):
		writer.WriteLine("""
		public virtual void On${item.Name}(Boo.Lang.Compiler.Ast.${item.Name} node)
		{
			if (Enter${item.Name}(node))
			{""")

		for field as Field in fields:
			writer.WriteLine("\t\t\tVisit(node.${field.Name});")

		writer.Write(
"""				Leave${item.Name}(node);
			}
		}

		public virtual bool Enter${item.Name}(Boo.Lang.Compiler.Ast.${item.Name} node)
		{
			return true;
		}

		public virtual void Leave${item.Name}(Boo.Lang.Compiler.Ast.${item.Name} node)
		{
		}
			""")
	else:
		writer.Write("""
		public virtual void On${item.Name}(Boo.Lang.Compiler.Ast.${item.Name} node)
		{
		}
			""")

def WriteDepthFirstVisitor(module as Module):
	using writer=OpenFile(GetPath("DepthFirstVisitor.cs")):
		WriteLicense(writer)
		WriteWarning(writer)
		writer.Write(
"""
namespace Boo.Lang.Compiler.Ast
{
	using System;

	public class DepthFirstVisitor : IAstVisitor
	{
		public bool Visit(Node node)
		{
			if (null != node)
			{
				try
				{
					node.Accept(this);
					return true;
				}
				catch (Boo.Lang.Compiler.CompilerError)
				{
					throw;
				}
				catch (Exception error)
				{
					throw Boo.Lang.Compiler.CompilerErrorFactory.InternalError(node, error);
				}
			}
			return false;
		}

		public void Visit(Node[] array, NodeType nodeType)
		{
			foreach (Node node in array)
			{
				if (node.NodeType == nodeType)
				{
					Visit(node);
				}
			}
		}

		public bool Visit(NodeCollection collection, NodeType nodeType)
		{
			if (null != collection)
			{
				Visit(collection.ToArray(), nodeType);
				return true;
			}
			return false;
		}

		public void Visit(Node[] array)
		{
			foreach (Node node in array)
			{
				Visit(node);
			}
		}

		public bool Visit(NodeCollection collection)
		{
			if (null != collection)
			{
				Visit(collection.ToArray());
				return true;
			}
			return false;
		}
		""")

		for member as TypeMember in module.Members:
			if IsConcreteAstNode(member):
				WriteDepthFirstAccept(writer, member)

		writer.Write("""
	}
}
""")

start = date.Now

module = BooParser.ParseFile("ast.model.boo").Modules[0]

WriteVisitor(module)
WriteDepthFirstVisitor(module)
WriteDepthFirstTransformer(module)
WriteNodeTypeEnum(module)
for member as TypeMember in module.Members:

	if member.Attributes.Contains("ignore"):
		continue

	if IsEnum(member):
		WriteEnum(member)
	else:
		if IsCollection(member):
			WriteCollection(member)
			WriteCollectionImpl(member)
		else:
			WriteClass(member)
			WriteClassImpl(member)

stop = date.Now
print("ast classes generated in ${stop-start}.")


support for tests in subfolders

support for tests in subfolders

"""
Finds all test cases under tests/integration and generates runTestCase calls
in IntegrationTestFixture.Generated.boo
"""
import System.IO

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

def writeTestCases(writer as TextWriter, baseDir as string):
	count = 0
	for fname in Directory.GetFiles(baseDir):
		continue unless fname.EndsWith(".boo")
		++count
		writeTestCase(writer, fname)
	for subDir in Directory.GetDirectories(baseDir):
		writeTestCases(writer, subDir)
	print("${count} test cases found in ${baseDir}.")

def writeTestCase(writer as TextWriter, fname as string):
	writer.Write("""
	[Test]
	def ${testCaseName(fname)}():
		runTestCase("../boojay/${fname.Replace('\\', '/')}")
		""")

using writer = StreamWriter("src/Boojay.Compilation.Tests/IntegrationTestFixture.Generated.boo"):
	writer.Write("""
namespace Boojay.Compilation.Tests

import NUnit.Framework

partial class IntegrationTestFixture:
""")

	writeTestCases(writer, "tests/integration")

"""
Finds all test cases under tests/integration and generates runTestCase calls
in IntegrationTestFixture.Generated.boo
"""
import System.IO

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

def writeTestCases(writer as TextWriter, baseDir as string):
	count = 0
	for fname in Directory.GetFiles(baseDir):
		continue unless fname.EndsWith(".boo")
		++count
		writer.Write("""
	[Test]
	def ${testCaseName(fname)}():
		runTestCase("../boojay/${fname.Replace('\\', '/')}")
		""")
	print("${count} test cases found in ${baseDir}.")

using writer = StreamWriter("src/Boojay.Compilation.Tests/IntegrationTestFixture.Generated.boo"):
	writer.Write("""
namespace Boojay.Compilation.Tests

import NUnit.Framework

partial class IntegrationTestFixture:
""")

	writeTestCases(writer, "tests/integration")