Wednesday, November 7, 2007

Exploring JRuby Syntax Trees With Scala, Part 1

In this post I'm going to show how to use Scala to have access to JRuby syntax trees.

Abstract syntax trees(AST) provide a representation of the source code that can be manipulated by another program. These trees are used by compilers and IDEs to do their work.

The JRuby distribution includes a parser and a nice tree representation for Ruby source code. This AST is used by the JRuby implementation to do its work, and also recently I noticed that the Netbeans Ruby support uses this representation for some tasks.

Calling JRuby Parser is pretty straightforward, just do the following:

package org.langexplr.jrubyasttests;
import org.jruby._
import org.jruby.ast._

object ScalaTestApp {
def main(args : Array[String]) : Unit = {
val rubyCode = "puts \"Hello\""

val r = Ruby.getDefaultInstance
val rootNode = r.parse(rubyCode,"dummy.rb",r.getCurrentContext.getCurrentScope,0).asInstanceOf[RootNode]

The rootNode variable contains the root of the syntax tree for the code stored in rubyCode.

In order to explore the syntax tree I wrote a small program that shows a Swing JTreecomponent with the result of parsing a snippet. Creating this program was easy because Node, the base class of all the elements in JRuby AST, provides a childNodes method. The model for the AST is the following:

package org.langexpr.jrubyasttest;

import javax.swing.tree.TreeModel
import javax.swing.event.TreeModelListener
import javax.swing.tree.TreePath
import org.jruby._
import org.jruby.ast._

class JRubyTreeModel() extends TreeModel {
var rootNode : Node = null;
def this(rubyCode : String) = {
val r = Ruby.getDefaultInstance
this.rootNode = r.parse(rubyCode,"dummy.rb",r.getCurrentContext.getCurrentScope,0)

def getRoot : Object = rootNode

def isLeaf(node: Object ) =
node.asInstanceOf[Node].childNodes.size == 0

def getChildCount( parent : Object) : int =

def getChild(parent : Object , index : int) : Object = {
def getIndexOfChild( parent : Object, child : Object) : int = {
def valueForPathChanged( path : TreePath, newValue : Object) : unit ={

def addTreeModelListener( l : TreeModelListener) : unit = {

def removeTreeModelListener( l : TreeModelListener) : unit = {


With this model now we can create the main program:

package org.langexpr.jrubyasttest;

import javax.swing._
import java.awt.event._
import java.awt.BorderLayout
import java.awt.Dimension

object JRubyAstTest {
val treeVisualizer = new JTree
val input = new JTextArea

def createFrame = {
val theFrame = new JFrame("JRuby AST test")
theFrame.setSize(new Dimension(640,450))
val content = theFrame.getContentPane
content.setLayout(new BorderLayout)

val bottomPanel = new JPanel
bottomPanel.setLayout(new BorderLayout)

bottomPanel.add(new JScrollPane(input),BorderLayout.CENTER)
input.setText("x=1\ny = 2\n")

treeVisualizer.setModel(new JRubyTreeModel(input.getText))

val button = new JButton("Parse!")
button.addActionListener( new ActionListener() {
def actionPerformed(ae : ActionEvent) =
treeVisualizer.setModel(new JRubyTreeModel(input.getText))


def main(args : Array[String]) : Unit = {
val f = createFrame

Now we can visually explore the AST using this little program.

The following code:

def foo(x)
return x*x

Is visualized like this:

Examples in this post could also be implemented easily using JRuby but in the second part I'm going to a couple of Scala features that I specially like to manipulate this data structure.


thomas.enebo said...

You inspired me! I ported your code to JRuby. JRuby exploring JRuby Parse Tree. Who would have thought :)

My entry

Luis Diego Fallas said...

Looks very nice!

roger said...

now can you get the AST of a previously existing method at runtime...