How to make a macro?

72 views
Skip to first unread message

Michal Hantl

unread,
Aug 24, 2010, 3:55:47 AM8/24/10
to mi...@googlegroups.com
Hello,
I would like to learn to make a simple macro.


For example, I would like to learn to make these:

a++ (a += 1)

s = "abc"
s << "def" (s += "def")

"abc" == "abc" ("abc".equals("abc"))

"abc" === "abc" ("abc" == "abc")


And then some more complex ones like:

"here is my [[wiki syntax]]".gsub(/\[\([[^\]])\]\]/, { |m| '<a
href="'+make_link(m[1])+'>'+m[1]+'</a>" })

assert "abc".length == 3 (compiled only when the target environment
is development)

--
S pozdravem, Regards
Michal Hantl

gtalk/jabber: mic...@hantl.cz
icq: 241813215

takeru sasaki

unread,
Aug 24, 2010, 6:29:48 AM8/24/10
to mi...@googlegroups.com
Hi,

I tried "macro" too, last week.

My codes may help you.
http://d.hatena.ne.jp/urekat/20100818/1282154238
http://d.hatena.ne.jp/urekat/20100815/1281860093
http://d.hatena.ne.jp/urekat/20100814/1281780042

I think "a++" is difficult. Because "Ruby parser" can't parse it.

First, try to parse code by mirahp. And find "class" and "method" you need to define.

takeru

===============
tkrmb:~% mirahp -V -e "a=1; a+=1"
Script
 body:
  Body
   LocalAssignment(name = a, scope = Script, captured = false)
    value:
     Fixnum(1)
   LocalAssignment(name = a, scope = Script, captured = false)
    value:
     Call(+)
      target:
       FunctionalCall(a)
        parameters:
      parameters:
       Fixnum(1)

tkrmb:~% mirahp -V -e "a=1; a++"
DashE:1: , unexpected end-of-file
/Users/takeru/demo/mirah-sandbox/.github/mirah/lib/mirah.rb:187:in `parse': undefined method `message' for nil:NilClass (NoMethodError)
        from /Users/takeru/demo/mirah-sandbox/.github/mirah/lib/mirah.rb:29:in `parse'
        from /Users/takeru/demo/mirah-sandbox/.github/mirah/bin/mirahp:9



tkrmb:~% mirahp -V -e "s='abc'; s<<'def'"
Script
 body:
  Body
   LocalAssignment(name = s, scope = Script, captured = false)
    value:
     String("abc")
   Call(<<)
    target:
     FunctionalCall(s)
      parameters:
    parameters:
     String("def")

tkrmb:~% mirahp -V -e "s='abc'; s+='def'"
Script
 body:
  Body
   LocalAssignment(name = s, scope = Script, captured = false)
    value:
     String("abc")
   LocalAssignment(name = s, scope = Script, captured = false)
    value:
     Call(+)
      target:
       FunctionalCall(s)
        parameters:
      parameters:
       String("def")

tkrmb:~% mirahp -V -e "'abc'=='def'"  
Script
 body:
  Body
   Call(==)
    target:
     String("abc")
    parameters:
     String("def")

tkrmb:~% mirahp -V -e "'abc'==='def'"
Script
 body:
  Body
   Call(==_set)
    target:
     String("abc")
    parameters:
     String("def")






2010/8/24 Michal Hantl <michal...@gmail.com>

Rib Rdb

unread,
Aug 24, 2010, 1:56:12 PM8/24/10
to mi...@googlegroups.com
I've also started support for writing macros in mirah instead of in ruby.  Take a look at src/mirah/impl/builtins.mirah for some examples.  Right now you can't reopen a class and add macros to it.  However, if we added a way for plugins to access the Transformer object you could use find_class(x).load_extensions like builtins.mirah does.

macro def name (args)
...
end

defmacro name(args) do
...
end

The first one lets you use any valid ruby function name like ===. However, you can't use any do blocks inside the macro body (so use braces).

The second one allows normal function names but not operators. It supports do blocks, and it also supports optional block params
defmacro foo(&block=nil)

The block is the only optional parameter supported by macros and the only supported default value is nil.  The other arguments should have no type specified; they will be duby.lang.compiler.Node. The block is a duby.lang.compiler.Block.

Your macro should return a Node.  To build a node you'll use quote.  Quote is based on lisp quote -- you pass it a block and it returns the ast for that block.  Within the block you can use `x` to paste in the AST node x. With the current parser you can only use backticks in places where you can put a string literal, so this limits the AST you can build within a macro.  If you want to build more complicated AST you'll have to resort to writing ruby code.

So, for example

class Foo
  macro def ==(other)
    quote { if nil? then other.nil? else equals(other) end }
  end
end

Then if you wanted to add this to string, you'd need to do (in ruby)

  java_import 'mypackage.Foo'
  transformer.find_class('java.lang.String').load_extensions(Foo)

But like I said, there's not really a way to get ahold of the transformer from a plugin now so you'd probably just have to add this to builtins.mirah.

Also, defining both == and === is going to be tricky.  Once you've overridden == there's not going to be any way to use the original version. We'd probably need to implement === as an intrinsic.

Michal Hantl

unread,
Aug 26, 2010, 3:25:05 AM8/26/10
to The Mirah Programming Language
Thank you very much guys!

I've changed "src/mirah/impl/builtins.mirah" the way RibRdb suggested.

But still:
> mirah -e 'puts String.new("aa")=="aa"'
false

I guess I need to rebuild mirah after I changed?

rake outputs:

michal@domov:~/mystuff/mirah$ rake
(in /home/michal/mystuff/mirah)
/home/michal/mystuff/mirah/lib/mirah/jvm/types/factory.rb:4:
uninitialized constant Duby::JVM::Types::TypeFactory::Boolean
(NameError)
from /home/michal/mystuff/mirah/lib/mirah/jvm/types/factory.rb:3:in
`require'
from /home/michal/mystuff/mirah/lib/mirah/jvm/typer.rb:3
from /home/michal/mystuff/mirah/lib/mirah/jvm/typer.rb:15:in
`require'
from /home/michal/mystuff/mirah/lib/mirah.rb:15
from /home/michal/mystuff/mirah/lib/mirah.rb:1:in `require'
from /home/michal/mystuff/mirah/lib/mirah/jvm/compiler.rb:1
from /home/michal/mystuff/mirah/lib/mirah/jvm/compiler.rb:4:in
`require'
from /home/michal/mystuff/mirah/lib/mirah/jvm/types.rb:4
from /home/michal/mystuff/mirah/lib/mirah/jvm/types.rb:3:in `require'
from /home/michal/mystuff/mirah/lib/mirah/plugin/java.rb:3
from /home/michal/mystuff/mirah/lib/mirah/plugin/java.rb:5:in
`require'
from ./test/test_java_typer.rb:5
from ./test/test_java_typer.rb:5:in `load'
from /home/michal/.gem/jruby/1.8/gems/rake-0.8.7/lib/rake/
rake_test_loader.rb:5
from /home/michal/.gem/jruby/1.8/gems/rake-0.8.7/lib/rake/
rake_test_loader.rb:5:in `each'
from /home/michal/.gem/jruby/1.8/gems/rake-0.8.7/lib/rake/
rake_test_loader.rb:5
rake aborted!
Command failed with status (1): [/usr/local/jruby-1.5.1/bin/jruby -
I"lib:li...]


Btw being able to define macros in my own script sounds very good :)
> On Tue, Aug 24, 2010 at 3:29 AM, takeru sasaki <sasaki.tak...@gmail.com>
> > 2010/8/24 Michal Hantl <michal.ha...@gmail.com>

Rib Rdb

unread,
Aug 27, 2010, 12:18:04 PM8/27/10
to mi...@googlegroups.com
That's strange.  What if you put a require 'mirah' before the other requires in test_java_typer.rb?

Rib Rdb

unread,
Aug 27, 2010, 7:18:24 PM8/27/10
to mi...@googlegroups.com
Also, after you edit builtins.mirah, I think you need to run the bootstrap rake task to see your changes.

Nick Howard

unread,
Aug 30, 2010, 12:04:26 AM8/30/10
to mi...@googlegroups.com
On Fri, Aug 27, 2010 at 5:18 PM, Rib Rdb <rib...@gmail.com> wrote:
Also, after you edit builtins.mirah, I think you need to run the bootstrap rake task to see your changes.

That's really good to know. Also, the task is 'rake jar:bootstrap' and you need to change lib/mirah/transform.rb so it refers to the right jar file: http://github.com/baroquebobcat/mirah/commit/00ccc9098655ee2ee97dc06655b5babe191497b6
otherwise, you won't see your changes reflected in mirahc's behavior.



--
-Nick Howard
http://blog.baroquebobcat.com/


Reply all
Reply to author
Forward
0 new messages