Wednesday, June 20, 2007

Calling the Boo compiler from a Boo program

In this post I'm going to show a little demo of creating a piece of a Boo program on the fly, compile it and execute it as part of the main program.

The Boo language implementation comes with two very useful and interesting libraries: Boo.Language.Parser.dll and Boo.Language.Compiler.dll. These libraries give the developer access to Boo's parser and compiler for use within any Boo(or .NET) program. Also the AST classes( Boo.Lang.Compiler.Ast) and visitors(Boo.Lang.Compiler.Ast.Visitors) are available to create and manipulate pieces of programs .

The ast-to-string.boo,ast-to-xml.boo,run-ast.boo and run-ast-without-compiler.boo examples included with the Boo distributions shows how to use both the AST and the compiler.

The little demo I worked on, is a program that shows the content of an AST tree using a Winforms TreeView control. In order create this little experiment I wanted to create a class that inherits from Boo.Lang.Compiler.Ast.DepthFirstVisitor that will create the TreeNode instances used to visualize the tree.

Creating a class that inherits from Boo.Lang.Compiler.Ast.DepthFirstVisitor is a tedious work since you have to create a method for each kind of AST node with the same body. Because of this the Ast classes and the compiler to create the class and compile it on the fly. Here's the code.


import System
import System.Reflection
import System.IO
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast
import Boo.Lang.Parser
import System.Windows.Forms from System.Windows.Forms


// Get the type instance for DepthFirstVisitor
a = Assembly.Load("Boo.Lang.Compiler")
t = a.GetType("Boo.Lang.Compiler.Ast.DepthFirstVisitor")

// Create a class definition for a custom visitor
myVisitor = ClassDefinition(Name:"DynamicVisitor")
myVisitor.BaseTypes.Add(SimpleTypeReference("Boo.Lang.Compiler.Ast.DepthFirstVisitor"))

// Add a new field for our node stack
myVisitor.Members.Add(Field(Name:"stck",
Type:SimpleTypeReference("System.Collections.Stack"),
Modifiers:TypeMemberModifiers.Public))

// Create all 'Enter' methods
for m as MethodInfo in [m for m in t.GetMethods() if m.Name.StartsWith("Enter")]:
nm = Method(Name: m.Name, Modifiers: TypeMemberModifiers.Override,
Body:Block(),ReturnType:SimpleTypeReference("System.Boolean"))
nm.Parameters.Add(ParameterDeclaration(Name:"p",
Type:SimpleTypeReference(
Name:m.GetParameters()[0].ParameterType.FullName)))

// Node for TreeNode('')
tnCreation = MethodInvocationExpression(ReferenceExpression("TreeNode"))
tnCreation.Arguments.Add(
StringLiteralExpression(m.GetParameters()[0].ParameterType.Name))

// Create node for 't = TreeNode('')'
decl = DeclarationStatement(
Declaration: Declaration(Name:"t",
Type: SimpleTypeReference("System.Windows.Forms.TreeNode")),
Initializer: tnCreation)

nm.Body.Add(decl)

// Create nodes for '(stck.Peek() as TreeNode).Nodes.Add(t)'
at = MethodInvocationExpression(
MemberReferenceExpression(
Target:MemberReferenceExpression(
Target: TryCastExpression(
Target:MethodInvocationExpression(
MemberReferenceExpression(
Target:ReferenceExpression("stck"),
Name:"Peek")),
Type:SimpleTypeReference("System.Windows.Forms.TreeNode")),
Name:"Nodes"),
Name:"Add"))
at.Arguments.Add(ReferenceExpression("t"))

nm.Body.Add(ExpressionStatement(at))

// Create node for 'stck.Push(t)'
mie = ExpressionStatement(
MethodInvocationExpression(
MemberReferenceExpression(
Target:ReferenceExpression("stck"),Name:"Push")))

(mie.Expression as MethodInvocationExpression).Arguments.Add(ReferenceExpression("t"))

nm.Body.Add(mie)

// Add 'return true' statement
nm.Body.Add(ReturnStatement(Expression:BoolLiteralExpression(Value:true)))

myVisitor.Members.Add(nm)


// Create all 'Leave' methods
for m as MethodInfo in [m for m in t.GetMethods() if m.Name.StartsWith("Leave")]:
// Create 'stck.Pop()' node
mie = ExpressionStatement(
MethodInvocationExpression(
MemberReferenceExpression(
Target:ReferenceExpression("stck"),Name:"Pop")))
// CreateNode
nm = Method(Name: m.Name, Modifiers: TypeMemberModifiers.Override,
Body:Block())
nm.Body.Add(mie)
nm.Parameters.Add(ParameterDeclaration(Name:"p",
Type:SimpleTypeReference(
Name:m.GetParameters()[0].ParameterType.FullName)))

myVisitor.Members.Add(nm)

// Compile unit and module creation
cu = CompileUnit()
mod = Boo.Lang.Compiler.Ast.Module(Name:"Module")
cu.Modules.Add(mod)
mod.Imports.Add(Import(Namespace:"System.Windows.Forms"))
mod.Members.Add(myVisitor)

// Initialize compiler preferences
pipeline = Pipelines.CompileToMemory()
ctxt = CompilerContext(cu)

// Run the compiler
pipeline.Run(ctxt)

// Check the results
if (ctxt.GeneratedAssembly != null):
// Create an instance of the new visitor and initialize it
i as object = ctxt.GeneratedAssembly.CreateInstance("DynamicVisitor");
t = ctxt.GeneratedAssembly.GetType("DynamicVisitor")
tStack = System.Collections.Stack()
tStack.Push(System.Windows.Forms.TreeNode("root"))
t.GetField("stck").SetValue(i,tStack );
v as DepthFirstVisitor = i

// Parse a file an run the visitor
//tast = BooParser.ParseString("myUnit","x = w.foo(1)")
tast = BooParser.ParseFile(argv[0])
tast.Accept(i)

//Initialize a form an show the results
frm = Form(Text: "AST content",Width: 300,Height: 300)
tv = TreeView(Dock: DockStyle.Fill)
tv.Nodes.Add(tStack.Pop() as TreeNode)
frm.Controls.Add(tv)
Application.Run(frm)
else:
for e in ctxt.Errors:
print e




Here's a sample of the output.