Using the Crystal compiler API

  • Published on January 1, 2020

In one of my projects, I needed the ability to generate an AST from valid Crystal code. I couldn't find any documentation about using the Crystal compiler API's, only gathered information here and there from random GitHub issues–thought I'd write things down here for the next person. Hope it's helpful for you:

As of v0.32.1

You can parse Crystal code for an AST tree by using Crystal::Parser from the syntax module:

require "compiler/crystal/syntax"

parser = Crystal::Parser.new("class HelloWorld\nend")

# This will give you a Crystal::ASTNode
ast = parser.parse

Crystal::ASTNode is the parent class for all AST nodes. You can find the different AST node types here: https://github.com/crystal-lang/crystal/blob/0.32.1/src/compiler/crystal/syntax/ast.cr. You'll need to use the .class, .is_a?, ... API's to work with the specific types.

A small gotcha: beware that class Path < ASTNode collides with struct Path. You'll have to use the fully qualified ::Crystal::Path when referring to it.

Another thing that comes with the syntax module is Crystal::Transformer. This lets you inspect/transform different node types. For example:

# ... continue from above

class MyTransformer < Crystal::Transformer
  def transform(node : Crystal::ClassDef) : Crystal::ASTNode
    puts "Class definition for #{node.name} at #{node.location}"
    super(node)
  end
end

tx = MyTransformer.new
transformed_node = ast.transform(tx)

Older versions

According to this GitHub issue, seems like the modules were in a different place in older versions:

require "compiler/crystal/parser"
require "compiler/crystal/transformer"