Published on

Lisp-like macros for ruby!

Published in: Technology, Education
1 Like
  • Be the first to comment

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide


  1. 2. Ruby Macros
  2. 5. “ print 'hello'” :(print 'hello')‏ proc{ print 'hello' }
  3. 6.“print 'hello'”).parse :(print 'hello')‏ RedParse::CallNode[nil, "print", [RedParse::StringNode["hello", {:@line=>1, :@close=>"'", :@open=>"'", :@char=>"""}]], nil, nil, {:@line=>1, :@not_real_parens=>true, :@offset=>0, :@lvalue=>nil}]
  4. 7. str=:(“hello”)‏ :(print ^str)‏
  5. 8. macro simple(a,b) :(^a+^b) end def simple_user p simple(1,2)‏ end
  6. 9. if $Debug macro assert(cond)‏ if RedParse::OpNode===cond and /A[=!]=/===cond.op left,op,right=*cond :(fail 'expected '+^left.unparse({})+"(==#{^left}) to be "+ ^op+" "+^right.unparse({})+"(==#{^right})" unless ^cond) else :(fail "expected #{:(^^cond)}, but was not true" unless ^cond)‏ end end else macro assert(cond)‏ end end
  7. 10. def test_assert a=1 b=2 assert a #ok assert a!=b #ok assert(a==b) #oops, fails. msg="expected a(==1) to be == b(==2)" assert(nil) #oops, fails. msg="expected nil, but was not true" #ok, that message didn't make a lot of sense... end
  8. 11. Syntax trees are represented by trees of nested Nodes. All Nodes descend from Array, and their subnodes can be addressed by numeric index, just like normal Arrays. However, many subnodes want to have names as well, thus most (but not all) array slots within the various Node classes have names. The general rule is that Node slots may contain a Node, a VarNameToken, a plain Array, a String, or nil. However, many cases are more specific than that.
  9. 12. VarNameToken<RubyLexer::Token #represents variables and constants (ident: String)‏ Node<Array #abstract ancestor of all nodes (except VarNameToken)‏ +RescueNode #a rescue clause in a def of begin statement | (exceptions: Array[Value*], varname: VarNameToken|nil, action: Value)‏ +WhenNode #a when clause in a case statement | (when: Value|Array[Value+] then: Value|nil )‏ +ElsifNode #an elsif clause in an if statement | (elsif: Value, then: Value|nil)‏ +ValueNode #abstract, a node which has a value (an expression)‏ |+ListOpNode #abstract, ancestor for nodes which are lists of ||| #things separated by some op ||+SequenceNode #a sequence of statements ||| (Array[Value*])‏ ||+ConstantNode #a constant expression of the form A::B::C or the like || #first expression can be anything || (Array[String|Value|nil,String+])‏ |+RawOpNode #ancestor of all operators (but not . :: ; , ?..:)‏ ||| (left: Value, op: String, right: Value)‏ ||+OpNode #ancestor of some operators |||+RangeNode #a range literal node |||+KeywordOpNode #abstract, ancestor of keyword operators ||||+LogicalNode #and or && || expressions ||||+WhileOpNode #while as an operator ||||+UntilOpNode #until as an operator ||||+IfOpNode #if as an operator ||||+UnlessOpNode #unless as an operator |||+NotEqualNode #!= expressions
  10. 13. |||+MatchNode #=~ expressions |||+NotMatchNode #!~ expressions |+LiteralNode #literal symbols, integers || (val: Numeric|Symbol|StringNode)‏ |+StringNode #literal strings ||| (Array[(String|Value)+])‏ ||+HereDocNode #here documents |+StringCatNode #adjacent strings are catenated (&quot;foo&quot; &quot;bar&quot; == &quot;foobar&quot;)‏ || (Array[StringNode+])‏ |+NopNode #an expression with no tokens at all in it || (no attributes)‏ |+VarLikeNode #nil,false,true,__FILE__,__LINE__,self || (name: String)‏ |+UnOpNode #unary operators || (op: String, val: Value)‏ ||+UnaryStarNode #unary star (splat)‏ |||+DanglingStarNode #unary star with no argument ||||| (no attributes)‏ ||||+DanglingCommaNode #comma with no rhs || (no attributes)‏ |+ParenedNode #ugly, parenthesized expressions and begin..end || (body: Value) -OR- (parentheses)‏ || (body: Value|nil, rescues: Array[RescueNode*], || else: Value|nil, ensure: Value|nil) (begin...end and rescue as operator)‏ |+AssignNode #assignment (including eg +=)‏ || (left:AssigneeList|LValue, op:String ,right:Array[Value*]|Value)‏
  11. 14. |+AssigneeList #abstract, comma-delimited list of assignables ||| (Array[LValue*])‏ ||+NestedAssign #nested lhs, in parentheses ||+MultiAssign #regular top-level lhs ||+BlockParams #block formal parameter list |+CallSiteNode #abstract, method calls ||| (receiver: Value|nil, name: String, ||| params: nil|Array[Value+,UnaryStarNode?,UnAmpNode?], ||| block_params: BlockParams, block: Value)‏ ||+CallNode #normal method calls ||+KWCallNode #keywords that look (more or less) like methods || #(BEGIN END yield return break continue next)‏ |+ArrayLiteralNode #[..] || (Array[Value*])‏ |+IfNode #if..end and unless..end || (if: Value, then: Value|nil, elsifs: Array[ElsifNode+]|Nil, else: Value|nil)‏ |+LoopNode #while..end and until..end || (while: Value, do: Value:nil)‏ |+CaseNode #case..end || (case: Value|nil, whens: Array[WhenNode*], else: Value|nil)‏ |+ForNode #for..end || (for: LValue, in: Value, do: Value|nil)‏ |+HashLiteralNode #{..} || (Array[Value*]) (size must be even)‏ |+TernaryNode # ? .. : || (if: Value, then: Value, else: Value)‏
  12. 15. |+MethodNode #def..end || (receiver:Value|nil, name:String, || params:Array[VarNameToken*,AssignNode*,UnaryStarNode?,UnAmpNode?]|nil, || body: Value|nil, rescues: Array[RescueNode+]|nil, else: Value|nil, ensure: Value|nil)‏ |+AliasNode #alias foo bar || (to: String|VarNameToken|StringNode, from: String|VarNameToken|StringNode)‏ |+UndefNode #undef foo || (Array[String|StringNode+])‏ |+NamespaceNode #abstract ||+ModuleNode #module..end ||| (name: VarNameToken|ConstantNode, body: Value|nil)‏ ||+ClassNode #class..end ||| (name: VarNameToken|ConstantNode, parent: Value|nil, body: Value|nil)‏ ||+MetaClassNode #class<<x..end || (val: Value, body: Value|nil)‏ |+BracketsGetNode #a[b] | (receiver: Value, params: Array[Value+,UnaryStarNode?]|nil)‏ | ErrorNode #mixed in to nodes with a syntax error +MisparsedNode #mismatched braces or begin..end or the like
  13. 16. Drawbacks: <ul><li>Pre-processing is very, very slow (because of RedParse)‏ </li></ul><ul><li>Macro calls must be in some sort of method... straight out macros at the top level won't work </li></ul><ul><li>Macro blocks and receivers aren't supported </li></ul><ul><li>Some ruby syntax won't work in files using macros </li></ul><ul><li>Files using macros must be loaded via Macro.require... not normal require </li></ul><ul><li>RedParse Node tree format will be changing </li></ul><ul><li>Macros cannot be scoped </li></ul>