Tuesday, July 24, 2007

Creating Java refactorings with Scala and Eclipse LTK - Part 2

This is the second of a two-part series of posts on creating a simple Java refactoring using the Scala programming language and the Eclipse Language Toolkit.

As mentioned in the first part, the refactoring is implemented by inheriting from the Refactoring class. This is an abstract class, in order to implement it we need to override the following methods:


  • abstract RefactoringStatus checkFinalConditions(IProgressMonitor pm)

  • abstract RefactoringStatus checkInitialConditions(IProgressMonitor pm)

  • abstract Change createChange(IProgressMonitor pm)

  • abstract String getName()



According to Unleashing the Power of Refactoring the checkInitialConditions method is used to verify that the refactoring can be performed. The checkFinalConditions is used to "perform long running checks before change generation...", but also can contain most of the work involved in the change generation. The createChange method is used to create the Change object that represents the actual change that will be performed in the code.


class InvertIfStatementRefactoring extends Refactoring {
var propagateNegation : boolean = false
var compilationUnit : ICompilationUnit = null
var selection : ITextSelection = null
var textChange : TextFileChange = null

override def checkInitialConditions( pm : IProgressMonitor) : RefactoringStatus = {
val status = new RefactoringStatus
if (compilationUnit == null || selection == null) {
status.merge(
RefactoringStatus.createFatalErrorStatus(
"Expression not identified yet!"));
}
return status
}
override def checkFinalConditions(pm : IProgressMonitor) : RefactoringStatus = {
val status = new RefactoringStatus
val requestor =
new ASTRequestor() {
override def acceptAST(source : ICompilationUnit,
ast : CompilationUnit) =
performRewrite(source,ast,status)
}

val parser = ASTParser.newParser(AST.JLS3);
parser.setResolveBindings(false);

try {
parser.createASTs(
Array[ICompilationUnit](this.compilationUnit),
new Array[String](0),
requestor,
pm)
} catch {
case ex : RuntimeException => {
status.merge(
RefactoringStatus.createFatalErrorStatus(
ex.getMessage()));
}
}
return status

}

override def createChange(pm : IProgressMonitor) : Change =
return textChange


override def getName() = "Invert if statement"

...
}


For this experiment most of the work is done from checkFinalConditionsMethod, in this method the code is parsed and the resulting AST is manipulated to generate a TextChange object that will be returned by the createChange method. The performRewrite and rewriteIfStatement methods do this by identifying the elements of the IF statement that will be manipulated.

   
def performRewrite(source : ICompilationUnit,
ast : CompilationUnit,
status : RefactoringStatus) : Unit = {

val rewrite = ASTRewrite.create(ast.getAST())
val theAst = ast.getAST()
val n = NodeFinder.perform(ast,
selection.getOffset(),
selection.getLength())

n match {
case ifStatNode@IfStatement(_,_,_) =>
rewriteIfStatement(
rewrite,
theAst,
ifStatNode.asInstanceOf[IfStatement])
case _ =>
throw new RuntimeException("Expecting IfStatement found: "+n.getClass().toString()+n.toString())
}

textChange = new TextFileChange(source.getElementName(),
source.getResource().asInstanceOf[IFile])
textChange.setTextType("java")
textChange.setEdit(rewrite.rewriteAST())
}


def rewriteIfStatement(rewrite : ASTRewrite,
theAst : AST,
ifStatement : IfStatement ) : Unit = {

val newIfStatement = theAst.newIfStatement

val resultIfCondition =
if (this.propagateNegation) {
propagateNegationInCondition(ifStatement.getExpression,rewrite,theAst) }
else {
val pExpr = theAst.newParenthesizedExpression
pExpr.setExpression(
rewrite.createCopyTarget(
ifStatement.getExpression()).asInstanceOf[Expression]
)

val negatedExpression = theAst.newPrefixExpression()
negatedExpression.setOperand(pExpr)
negatedExpression.setOperator(PrefixExpression.Operator.NOT)
negatedExpression
}

newIfStatement.setExpression( resultIfCondition )

if (ifStatement.getElseStatement != null) {
newIfStatement.setThenStatement(
rewrite.createMoveTarget(
ifStatement.getElseStatement()).asInstanceOf[Statement])
} else {
newIfStatement.setThenStatement(
theAst.newBlock)
}

newIfStatement.setElseStatement(
rewrite.createMoveTarget(
ifStatement.getThenStatement()).asInstanceOf[Statement])
rewrite.replace(ifStatement,newIfStatement,null)
}


The changes in the AST are recorded using the ASTRewrite class. More details on how to manipulate JDT AST trees can be found in AST Syntax Tree.

The NodeFinder class is used although its use is not encouraged because of its visibility. This class identifies the selected node under the tree of the compilation unit. In future posts I'll try to replace the use of this class.

If the propagate negation check box is not checked the only thing that we need to do with the condition expression is to wrap it with parenthesis (ParenthesizedExpression) and with a NOT expression(PrefixExpression with the NOT operator).

In order to propagate negation several scenarios must be considered. For example if the original expression is (x == 1) the desired resulting expression must be (x != 1) , also if the condition is (x == 1) && (y == 4) then it can be transformed to (x != 1) || (y != 4). Inspired by the arithmetic simplification examples presented in Matching Objects With Patterns paper, this was implemented using Scala extractors . The following definitions of extractors were defined:


object NotExpression {
def unapply(e : ASTNode) =
if (e.isInstanceOf[PrefixExpression] &&
e.asInstanceOf[PrefixExpression].getOperator == PrefixExpression.Operator.NOT) {
Some(e.asInstanceOf[PrefixExpression].getOperand)
}
else {
None
}
}

object AndExpression {
def unapply(e : ASTNode) =
Utilities.identifyInfixExpression(e,InfixExpression.Operator.CONDITIONAL_AND)
}

object OrExpression {
def unapply(e : ASTNode) =
Utilities.identifyInfixExpression(e,InfixExpression.Operator.CONDITIONAL_OR)
}

object EqualsExpression {
def unapply(e : ASTNode) =
Utilities.identifyInfixExpression(e,InfixExpression.Operator.EQUALS)
}

object NotEqualsExpression {
def unapply(e : ASTNode) =
Utilities.identifyInfixExpression(e,InfixExpression.Operator.NOT_EQUALS)
}

object LessExpression {
def unapply(e : ASTNode) =
Utilities.identifyInfixExpression(e,InfixExpression.Operator.LESS)
}

object GreaterExpression {
def unapply(e : ASTNode) =
Utilities.identifyInfixExpression(e,InfixExpression.Operator.GREATER)
}

object LessEqualsExpression {
def unapply(e : ASTNode) =
Utilities.identifyInfixExpression(e,InfixExpression.Operator.LESS_EQUALS)
}

object GreaterEqualsExpression {
def unapply(e : ASTNode) =
Utilities.identifyInfixExpression(e,InfixExpression.Operator.GREATER_EQUALS)
}

object Parenthesis {
def unapply(node : ASTNode) =
if (node.isInstanceOf[ParenthesizedExpression]) {
Some(node.asInstanceOf[ParenthesizedExpression].getExpression)
}
else {
None
}
}

object IfStatement {
def unapply(node : ASTNode) =
if (node.isInstanceOf[IfStatement]) {
val ifStatement = node.asInstanceOf[IfStatement]
Some(ifStatement.getExpression,
ifStatement.getThenStatement,
ifStatement.getElseStatement)
} else {
None
}
}


Given this definition we can implement the propagateNegationInCodition method by checking every case that we want to transform:


def propagateNegationInCondition(condition : Expression, rewrite : ASTRewrite,ast : AST) : Expression = {
condition match {
case EqualsExpression(x,y) =>
Utilities.createInfixExpression(
rewrite.createMoveTarget(x).asInstanceOf[Expression],
rewrite.createMoveTarget(y).asInstanceOf[Expression],
InfixExpression.Operator.NOT_EQUALS,
ast)

case NotEqualsExpression(x,y) =>
Utilities.createInfixExpression(
rewrite.createMoveTarget(x).asInstanceOf[Expression],
rewrite.createMoveTarget(y).asInstanceOf[Expression],
InfixExpression.Operator.EQUALS,
ast)

case OrExpression(x,y) =>
Utilities.createInfixExpression(
propagateNegationInCondition(x,rewrite,ast),
propagateNegationInCondition(y,rewrite,ast),
InfixExpression.Operator.CONDITIONAL_AND,
ast)

case AndExpression(x,y) =>
Utilities.createInfixExpression(
propagateNegationInCondition(x,rewrite,ast),
propagateNegationInCondition(y,rewrite,ast),
InfixExpression.Operator.CONDITIONAL_OR,
ast)

case NotExpression(negatedExpression) =>
rewrite.createMoveTarget(negatedExpression).asInstanceOf[Expression]

case x if x.isInstanceOf[InfixExpression] => {
val parenthesis = ast.newParenthesizedExpression
parenthesis.setExpression(
rewrite.createMoveTarget(condition).asInstanceOf[Expression])
val notExpression = ast.newPrefixExpression
notExpression.setOperand(parenthesis)
notExpression.setOperator(PrefixExpression.Operator.NOT)
notExpression
}
case _ => {
val notExpression = ast.newPrefixExpression
notExpression.setOperand(
rewrite.createMoveTarget(condition).asInstanceOf[Expression])
notExpression.setOperator(PrefixExpression.Operator.NOT)
notExpression
}
}
}


Given the following code

if(goo(3) && !(x < 3)) {
System.out.print(2);
} else {
System.out.println(4);
}


By executing the refactoring without propagating the negation:


By executing the refactoring propagating the negation:




Code for this experiment can be found here.

Monday, July 23, 2007

Creating Java refactorings with Scala and Eclipse LTK - Part 1

This is the first of a two-part series of posts on creating a simple Java refactoring using the Scala programming language and the Eclipse Language Toolkit.

The first part is about adding the required elements for the refactoring to appear in the Eclipse UI, the second part (which I think is the most interesting) is about manipulating the Java AST to actually perform the refactoring.

The purpose of this post is to show that Scala can be used to work with existing/complex Java APIs . The purpose of this post is not to how to create Eclipse refactorings, there's already nice documentation that does that.

The refactoring that will be implemented is called "Invert IF statement blocks", the purpose of this refactoring is to swap the THEN and ELSE sections of an IF statement preserving its behavior.

For example applying this refactoring to the following code:


if (x == 20) {
System.out.println("x is 20!");
} else {
System.out.println("x is NOT 20!");
}


Will result in the following code:


if (!(x == 20)) {
System.out.println("x is NOT 20!");
} else {
System.out.println("x is 20!");
}


Also, as an option, the refactoring is going to propagate the negation of the IF's condition expression. For example for code above the resulting statement will be:


if (x != 20) {
System.out.println("x is NOT 20!");
} else {
System.out.println("x is 20!");
}


In order to add the refactoring to Eclipse we're going to use the Eclipse Language Toolkit which makes it easy to access existing Eclipse infrastructure elements such as refactoring wizards, diff window, etc. A very nice explanation on how to create a refactoring using the Language Toolkit is found in Unleashing the Power of Refactoring, the code in this post is based on this article .

The first step for creating the refactoring, was to create an Eclipse Plugin . The first challenge was that using File -> New Project -> Plugin project will create a Java project will the required elements to create the plugin. But what we need is to create a Scala project with the same elements.

After trying several unsuccessful attempts, I found a couple of very useful posts from Neil’s point-free blog the first An OSGi Bundle… built in Scala and also Eclipse PDE does Scala . With this information I was able to create an Eclipse Plugin project with the Scala nature.

Based on Unleashing the Power of Refactoring, the minimal code required for a refactoring includes:


  • An action that is used to add the refactoring the UI

  • A refactoring wizard page for the refactoring arguments

  • A class that inherits from Refactoring that represents the refactoring and contains the AST manipulation code.


The action used for this post is associated with the menubar ( for future posts, I think the most appropriate location of this refactoring is on the Refactor menu of Eclipse). In order to add this to the UI the following information was added to the plugin.xml file:


<extension
point="org.eclipse.ui.actionSets">
<actionSet
id="ScalaTestRefactoring.actionSet1"
label="label">
<action
id="ScalaTestRefactoring.action1"
label="label">
</action>
</actionSet>
</extension>
<extension
point="org.eclipse.ui.actionSets">
<actionSet
description="Experimental actions"
id="org.eclipse.refactoring.actionSet"
label="Langexplr Section"
visible="true">
<menu
id="ScalaTestRefactoring.menu1"
label="Langexplr entries 2"
path="edit">
<separator
name="ScalaTestRefactoring.separator1">
</separator>
</menu>
<action
class="langexplr.eclipse.InvertIfStatementRefactoringAction"
definitionId="ScalaTestRefactoring.command1"
enablesFor="*"
id="ScalaTestRefactoring.action1"
label="Invert if statement(s)..."
menubarPath="ScalaTestRefactoring.menu1/ScalaTestRefactoring.separator1"
style="push">
</action>
</actionSet>
</extension>


Which says that the option "Invert if statement(s)..." will be added to a menu called "Langexplr entries 2" and that the action is implemented in the class langexplr.eclipse.InvertIfStatementRefactoringAction. More information about adding options to the Eclipse UI can be found in Contributing Actions to the Eclipse Workbench.

The code for the langexplr.eclipse.InvertIfStatementRefactoringAction class is the following:


class InvertIfStatementRefactoringAction extends IWorkbenchWindowActionDelegate {
var window : IWorkbenchWindow = null
var selection : ITextSelection = null
var compilationUnit : ICompilationUnit = null

override def dispose : unit = {
return
}
override def init( window : IWorkbenchWindow) = {
this.window = window;
}
override def run( action : IAction) = {
if (selection != null && window != null && compilationUnit != null) {
val refactoring = new InvertIfStatementRefactoring
refactoring.selection = selection
refactoring.compilationUnit = compilationUnit
val wizardOperation : RefactoringWizardOpenOperation =
new RefactoringWizardOpenOperation(
new InvertIfStatementRefactoringWizard(refactoring))
wizardOperation.run(window.getShell(), "Invert if statement");
}
}
override def selectionChanged( action : IAction, selection : ISelection) = {
this.selection = null;

if(selection.isInstanceOf[ITextSelection]) {
val s = selection.asInstanceOf[ITextSelection];
val cu = compilationUnitForCurrentEditor();
if (cu != null) {
compilationUnit = cu;
val selectionText = s.getText()
if (selectionText.indexOf("if") != -1) {
action.setEnabled(true)
this.selection = s
} else {
action.setEnabled(false)
}
}
} else {
action.setEnabled(false);
}
}
...
}


What this code actually does, is that it activates the action when the selected text contains the "if" string (which I know is a pretty weak criteria) more verifications are added later. Also when executed the action will run the wizard.

The refactoring wizard is only used to ask the user if the negation of the resulting IF statements needs to be propagated. In order to do this a wizard page most be created to ask that question. Here's the code for this page:


class InvertIfStatementRefactoringInputPage(name : String)
extends UserInputWizardPage(name) {
override def createControl(c : Composite) = {
val refactoringControl = new Composite(c,SWT.NONE)

this.setControl(refactoringControl)

val layout = new GridLayout
layout.marginTop = 10
layout.marginLeft = 10
layout.numColumns = 2

refactoringControl.setLayout(layout)

val iLabel = new Label(refactoringControl,SWT.NONE)
iLabel.setText("Propagate negation")
val cbButton = new Button(refactoringControl,SWT.CHECK)

cbButton.addSelectionListener(
new SelectionAdapter() {
override def widgetSelected(e : SelectionEvent) = {
val r = getRefactoring().asInstanceOf[InvertIfStatementRefactoring]
r.propagateNegation = cbButton.getSelection()
}
}
)
setPageComplete(true);
}
}


Given this we can create the class that represents the wizard:


class InvertIfStatementRefactoringWizard(r : Refactoring)
extends RefactoringWizard(r,
RefactoringWizard.DIALOG_BASED_USER_INTERFACE |
RefactoringWizard.PREVIEW_EXPAND_FIRST_NODE) {

override def addUserInputPages() = {
setDefaultPageTitle("Invert IF statement")
addPage(new InvertIfStatementRefactoringInputPage("InputPage"))
}
}


The resulting dialog looks like this:



In the second part, the implementation of the actual refactoring will be presented.

Code for this experiment can be found here.

Saturday, July 21, 2007

Tuesday, July 3, 2007

Creating FxCop rules with F#

In this post I'm going to show a little example of an FxCop rule created with F#. My goal is to show that F# features can be used to define code rules.

The purpose of the post is not to show how to create FxCop rules, a nice explanation can be found in this useful blog entry: FxCop This (also useful links are provided).

The rule that I'm going to show is used to detect assignments from a field to itself. For example:


public class AClass {

int aValue;
public void AMethod(int aValeu){
this.aValue = aValue;
...
}
...
}


In this example, an error typing the name of the first argument of AMethod leads to an incorrect assignment of field aValue to itself. The C# compiler gives you a warning on this, however the code is generated because it is a valid program.

In order to create an FxCop rule that detects this, we have to identify the IL code sequence that represents the assignment. By using ILDASM we can see this:


.method public hidebysig instance void AMethod(int32 aValeu) cil managed
{
// Code size 14 (0xe)
.maxstack 8
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldarg.0
IL_0003: ldfld int32 AClass::aValue
IL_0008: stfld int32 AClass::aValue
...
} // end of method AClass::AMethod



So what we need to find is the simple pattern of a LDFLD opcode followed by a STFLD to the same field. The code for this rule looks like this:


type SameFieldAssignment = class
inherit BaseIntrospectionRule
new() = {inherit BaseIntrospectionRule("SameFieldAssignment","SameFieldAssignment",Assembly.GetExecutingAssembly());}

override x.Check(m : Member) =
match m with
| (:? Method as meth) ->
let instrs = instructions_to_list meth.Instructions
in
x.find_assignment instrs
| _ -> x.Problems

member x.find_assignment (instructions : Instruction list) =
match instructions with
| (load::store::r) when
(load.OpCode = OpCode.Ldfld &&
store.OpCode = OpCode.Stfld &&
load.Value = store.Value)
-> x.Problems.Add(new Problem(x.GetNamedResolution("SameFieldAssignment",[||]),store.SourceContext))
x.find_assignment r
| (_::r) -> x.find_assignment r
| _ -> x.Problems
end


The find_assignment method is used to search for the desired pattern. The IL code sequence is converted to a F# list (via the instructions_to_list utility function), then a list pattern is the used to identify the desired pattern.

We can improve this code by using active patterns. For example we can define the following patterns:


let (|Ldfld|_|) (i : Instruction) =
if (i.OpCode = OpCode.Ldfld) then
Some ((i.Value :?> Field) , i.SourceContext)
else
None

let (|Stfld|_|) (i : Instruction) =
if (i.OpCode = OpCode.Stfld) then
Some((i.Value :?> Field),i.SourceContext)
else
None

let (|MethodContent|_|) (m : Member) =
match m with
| (:? Method as meth) ->
Some (meth.FullName,
instructions_to_list meth.Instructions)
| _ -> None


Now we can write:


type SameFieldAssignmentAp = class
inherit BaseIntrospectionRule
new() = {inherit BaseIntrospectionRule("SameFieldAssignmentAp","SameFieldAssignmentAp",Assembly.GetExecutingAssembly());}

override x.Check(m : Member) =
match m with
| MethodContent(_,instrs) -> x.find_assignment instrs
| _ -> x.Problems

member x.find_assignment (instructions : Instruction list) =
match instructions with
| (Ldfld(lField,_)::Stfld(sField,source)::r) when (lField = sField)
-> let p = new Problem(x.GetNamedResolution("SameFieldAssignmentAp",[|lField.FullName|]),source) in
x.Problems.Add(p)
x.find_assignment r
| (_::r) -> x.find_assignment r
| _ -> x.Problems
end


Code for this experiment can be found here.

Sunday, June 24, 2007

Creating fractal images using C# 3.0 features

In this post I'm going to try to implement the basic Escape time algorithm for creating fractal images. My main goal is to use as many C# 3.0 features as possible .

This post is inspired by the very nice blog post A Ray Tracer in C#3.0.


The Escape time algorithm is very simple, but depending on its input it can generate very complex images.

Although not mandatory, the first thing to create is a class from complex numbers. The complex number class contains the following members:


public class Complex
{
public Complex(double real, double img)
public Complex Multiply(Complex c)
public Complex Add(Complex c)
public double Norm()
public double NormSquared()
public static Complex operator *(Complex c1,Complex c2)
public static Complex operator +(Complex c1, Complex c2)
public double Real
public double Img
}


Now we need something to convert from the image coordinate system to the real coordinate system. To do this the following function was created:


public static Func<double, double> InterpFunc(double x1,double y1,double x2,double y2)
{
double m = (y2 - y1) / (x2 - x1);
double b = y1 - (m * x1);
return (x => m * x + b);
}


The InterpFunc method creates a function that maps values from one coordinate system to the other.

The code that creates the image is the following:


void CreateFractal(double minX, double minY,double maxX, double maxY,int imageWidth, int imageHeight)
{
Func<double, double> xF = MathUtils.InterpFunc(0, minX, imageWidth, maxX);
Func<double, double> yF = MathUtils.InterpFunc(0, minY, imageHeight, maxY);

foreach (var p in from yi in Enumerable.Range(0, imageHeight)
from xi in Enumerable.Range(0, imageWidth)
select new
{
x = xi,
y = yi,
xD = xF(xi),
yD = yF(yi)
})
{

Complex p0 = new Complex(p.xD, p.yD);
Func<Complex, Complex> function = functionConstructor(p0);

int i = ApplyFunction(function, p0)
.TakeWhile(
(x, j) => j < maxIteration && x.NormSquared() < 4.0)
.Count();

HandlePixel(p.x, p.y, i);
}

}


Basically select expression generates the coordinates for all the points in the image . The functionConstructor function is used to create the function that will be applied to generate the fractal. One example of this functions is the one used for the Mandelbrot fractal f(x) = x^2 + p0 . The escape time is calculated by counting the number of elements in the generated sequence of function applications before the value escaped.

The ApplyFunction method is interesting since is the one that creates the sequence of recursive function applications required for this algorithm. The method looks like this:


IEnumerable<Complex> ApplyFunction(Func<Complex, Complex> function,Complex initial)
{
Complex last = initial;
while (true)
{
last = function(last);
yield return last;
}
}



By running this algorithm using the Mandelbrot formula:


Func<Complex,Func<Complex,Complex>> fc =
p0 => (c => c.Multiply(c).Add(p0));

EscapeTimeFractal p = new EscapeTimeFractal(300, 300, fc);
p.MinX = -0.03;
p.MinY = 0.68;
p.MaxX = 0.03;
p.MaxY = 0.62;

p.Run();


The generated image is the following:




By running it using the Szegedi Butterfly 1 formula:


Func<Complex,Func<Complex,Complex>> fc =
p0 =>
(c => (new Complex(
((c.Img * c.Img) - Math.Sqrt(Math.Abs(c.Real))),
((c.Real * c.Real) - Math.Sqrt(Math.Abs(c.Img)))))
+ p0);

EscapeTimeFractal p = new EscapeTimeFractal(300, 300,fc);
p.MinX = -2;
p.MinY = 2;
p.MaxX =2;
p.MaxY = -2;
p.MaxIteration = 127;
p.OutputFileName = @"c:\temp\output.bmp";

p.Run();


The generated image is the following:



The code for this experiment can be found here.

Thursday, June 21, 2007

Why AutoCAD uses Lisp as extension/scripting language

The following links provide historic information on AutoLISP:


  1. Why Lisp? from The Autodesk File

  2. The AutoLISP Wikipedia entry

  3. Introduction to AutoLISP



I always wanted to know why Lisp was used as the scripting/extension language of AutoCAD. Mainly because Lisp may be perceived as a Computer Science only language.

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.