CASTY

Casty is a fork of CAST, a C parser and abstract syntax tree for Ruby.

It does not choke on #-sequences (source map etc.) left in by preprocessor. #line-support may be coming, also C::Preprocessor has more options.

Example

require 'casty'

source = File.read('file.c')
ast = C.parse(source)
ast.entities.each do |declaration|
  declaration.declarator.each do |declarator|
    puts "#{declarator.name}: declarator.type"
  end
end

Or in irb:

irb> ast = C.parse('int main(void) { return 0; }')
 => TranslationUnit
    entities:
        - FunctionDef
            type: Function
                type: Int
                params: []
            name: "main"
            def: Block
                stmts:
                    - Return
                        expr: IntLiteral
                            val: 0

irb> puts ast
int main(void) {
    return 0;
}
 => nil

Nodes

C.parse returns a tree of Node objects. Here's the class hierarchy:

  • Node
    • TranslationUnit
    • Comment
    • Declaration
    • Declarator
    • FunctionDef
    • Parameter
    • Enumerator
    • MemberInit
    • Member
    • Statement
      • Block
      • If
      • Switch
      • While
      • For
      • Goto
      • Continue
      • Break
      • Return
      • ExpressionStatement
    • Label
      • PlainLabel
      • Default
      • Case
    • Type
      • IndirectType
        • Pointer
        • Array
        • Function
      • DirectType
        • Struct
        • Union
        • Enum
        • CustomType
        • PrimitiveType
          • Void
          • Int
          • Float
          • Char
          • Bool
          • Complex
          • Imaginary
  • Node
    • Expression
      • Comma
      • Conditional
      • Variable
      • UnaryExpression
        • PostfixExpression
          • Index
          • Call
          • Dot
          • Arrow
          • PostInc
          • PostDec
        • PrefixExpression
          • Cast
          • Address
          • Dereference
          • Sizeof
          • Plus
          • Minus
          • PreInc
          • PreDec
          • BitNot
          • Not
      • BinaryExpression
        • Add
        • Subtract
        • Multiply
        • Divide
        • Mod
        • Equal
        • NotEqual
        • Less
        • More
        • LessOrEqual
        • MoreOrEqual
        • BitAnd
        • BitOr
        • BitXor
        • ShiftLeft
        • ShiftRight
        • And
        • Or
  • Node
    • Expression
      • AssignmentExpression
        • Assign
        • MultiplyAssign
        • DivideAssign
        • ModAssign
        • AddAssign
        • SubtractAssign
        • ShiftLeftAssign
        • ShiftRightAssign
        • BitAndAssign
        • BitXorAssign
        • BitOrAssign
      • Literal
        • StringLiteral
        • CharLiteral
        • CompoundLiteral
        • IntLiteral
        • FloatLiteral
    • NodeList
      • NodeArray
      • NodeChain

The highlighted ones are abstract.

The last 2 (NodeLists) represent lists of Nodes. They quack like standard ruby Arrays. NodeChain is a doubly linked list; NodeArray is an array.

Node Methods

  • parent: return the parent in the tree (a Node or nil).

  • pos, pos=: the position in the source file (a Node::Pos).

  • to_s: return the code for the tree (a String).

  • inspect: return a pretty string for inspection, makes irb fun.

  • match?(str), =~(str): return true iff str parses as a Node equal to this one.

  • detach: remove this node from the tree (parent becomes nil) and return it.

  • detached?, attached?: return true if parent is nil or non-nil respectively.

  • replace_with(node): replace this node with node in the tree.

  • swap_with(node): exchange this node with node in their trees.

  • insert_prev(*nodes), insert_next(*nodes): insert nodes before this node in the parent list. Parent must be a NodeList! Useful for adding statements before a node in a block, for example.

  • Foo?: (where Foo is a module name) return self.is_a?(Foo). This is a convienience for a common need. Example:

    
    \# print all global variables
    ast.entities.each do |node|
      node.Declaration? or next
      node.declarators.each do |decl|
        unless decl.type.Function?
          puts "#{decl.name}: #{decl.type}"
        end
      end
    end
    

The =~ method lets you do:

if declarator.type =~ 'const int *'
  puts "Ooh, a const int pointer!"
end

This is not the same as declarator.type.to_s == 'const int *'; that'd require you to guess how to_s formats its strings (most notably, the whitespace).

Fields and Children

The big table down below lists the fields of each Node. A field is an attribute which:

  • is used in equality checks (== and eql?).
  • are copied recursively by dup and clone.

Fields listed as children form the tree structure. They only have a Node or nil value, and are yielded/returned/affected by the traversal methods:

  • next, prev: return the next/prev sibling.
  • list_next, list_prev: like next/prev, but also requires the parent to be NodeList. I'll be honest; I don't remember why I added these methods. They may well suddenly disappear.
  • each, reverse_each: Yield all (non-nil) children. Node includes Enumerable, so, you know.
  • depth_first, reverse_depth_first: Walk the tree in that order, yielding two args (event, node) at each node. event is :down on the way down, :up on the way up. If the block throws :prune, it won't descend any further.
  • preorder, reverse_preorder, postorder, reverse_postorder: Walk the tree depth first, yielding nodes in the given order. For the preorders, if the block throws :prune, it won't descend any further.
  • node_after(child), node_before(child): return the node before/after child (same as child.next).
  • remove_node(child): remove child from this node (same as child.detach).
  • replace_node(child, new_child): replace child with yeah you guessed it (same as child.replace_with(newchild)).

Note: don't modify the tree during traversal!

Other notes about the table:

  • Field names that end in '?' are always true-or-false.
  • If no default is listed:
    • it is false if the field name ends in a '?'
    • it is a NodeArray if it is a NodeList.
    • it is nil otherwise.