CodeTree - Reusable block expressions

synopsis

This part of SByC provides a safe, reusable, extensible, mashallable, and non-intrusive (no monkey patching of Ruby core classes) implementation of block expressions. Block expressions are parsed using a generic DSL and converted to a parse tree, which may be analyzed, rewrited, compiled, and so on. The following example illustrates typical usage of CodeTree::parse.

code = CodeTree::parse{ x > 10 & y < 20 }   
# => (& (> x, 10), (< y, 20))

# See the evaluation section for details
code.eval{:x => 5, :y => 5}                
# => true

# See the code-generation section for details
code.object_compile('scope', :[])          
# => "scope[:x].>(10) & scope[:y].<(20)"

# CodeTree expressions may be marshaled
Marshal.dump(code)

syntax

The following styles are recognized:

# Hash-style
CodeTree::parse{|t| (t[:x] > 5) & (t[:y] <= 10) }  # => (& (> x, 5), (<= y, 10))

# Object-style
CodeTree::parse{|t| (t.x > 5) & (t.y <= 10)     }  # => (& (> x, 5), (<= y, 10))

# Context-style
CodeTree::parse{ (x > 5) & (y <= 10)            }  # => (& (> x, 5), (<= y, 10))

# Functional-style
CodeTree::parse{ (both (gt x, 5), (lte y, 10))  }  # => (both (gt x, 5), (lte y, 10))

The parsing stage returns a code tree, which can be passed to a next stage (rewriting, evaluation, compilation or whatever).

semantics

Branch nodes in the code tree correspond to a function call with arguments (see AstNode for details):

(function arg0, arg1, arg2, ..., argn)

where arg0, arg1 ... argn always are AstNode instances, expect for leaf nodes. Function name is accessible through the function attribute (aliased as name). Arguments are accessible through the arg (aliased as children) attribute.

Leaf nodes encode literals through the special '\_' function. The later takes only one argument, which can be any ruby object, and is accessible through the literal attribute. All and only leaf nodes have that function.

(_ literal)

Variable references are denoted through a special function '?'. This operator expects one argument, i.e. the variable name, typically (but not necessarily) represented by a symbol literal.

(? variable_name)

examples

CodeTree::parse{ 12 }.inspect       # (_ 12)
CodeTree::parse{ :x }.inspect       # (_ :x)
CodeTree::parse{ x  }.inspect       # (? (_ :x))

‘_’ and ‘?’ operators only appear when invoking inspect:

CodeTree::parse{ x + 12 }.inspect   # (+ (? (_ :x)), (_ 12))
CodeTree::parse{ x + 12 }.to_s      # (+ x, 12)

limitations

No imperative code: SByC implicitly assumes that block’s code can be interpreted functionally: no concept of variable, no sequence of code, no if/then/else, no loop. Said otherwise: your block code should always “compute a value”, without having any other (state) side effect. The following will NOT work:

CodeTree::parse{ if x then 0 else 1 end }  # 0

Evaluation at parsing time: SByC does not uses ruby2ruby or similar libraries to get an AST. It simply executes the block inside a specific DSL. Therefore, ruby expressions on literals will be evaluated at parsing time, according to operator precedence and ordering:

CodeTree::parse{ x + 12 + 17     }      # => (+ (+ x, 12), 17)
CodeTree::parse{ x + (12 + 17)   }      # => (+ x, 29)
CodeTree::parse{ (x + 12) + 17   }      # => (+ (+ x, 12), 17)

Ruby operator limitations: for the same reason, only operators that rely on overridable methods are recognized. In particular, the following expressions will NOT work:

CodeTree::parse{ x and y }              # => y
CodeTree::parse{ x && y  }              # => y
CodeTree::parse{ x or y  }              # => x
CodeTree::parse{ x || y  }              # => x
CodeTree::parse{ not(x)  }              # => false ... but works with Ruby >= 1.9
CodeTree::parse{ !x      }              # => false ... but works with Ruby >= 1.9

credits

SByC – CodeTree © 2010 by Bernard Lambeau. SByC is distributed under the MIT licence. Please see the LICENCE.md document for details.