Amrita2を理解するための一歩

やっぱり私のAmrita2に関する知識はあまりに貧弱であるため、勝手な思い込みや期待、また誤解が多過ぎます。そこできちんとAmrita2を理解しようと思い、コードを読むことにしました。しかしAmrita2はなかなか複雑ですからすぐには理解できません。とりあえず以下のようなスクリプトでAmrita2の挙動を把握していくことから始めたいと思います。

require "rubygems"
require "amrita2"

$src = <<AM2
<< div <
  << a [['a', 'b', 'c', 'd']] <
    <span>a</span>
AM2

tmpl = Amrita2::Template.new($src)
tmpl.set_trace(STDOUT)
tmpl.setup
tmpl.instance_eval do
  puts "--- PreProcessor ---"
  puts @preprocessor.process($src)
end
puts "--- Render ---"
puts tmpl.render_with({:hello => "HELLO"})

この実行結果は次のようになります。

        include Amrita2
        include Amrita2::Runtime

def render_with(value,__binding__,__cnt__ = -1 )
  __stream__ = '' 
  $_ = value
  if $_.kind_of?(Amrita2::Enum) and not $_.kind_of?(ScalarData)
    $_.each_with_index  do |v, i| 
      __stream__.concat(render_with(v, __binding__, i) )
    end
    return __stream__
  end
  Tracer << "%s:%s:%s" % ["<root>","in", $_.inspect] 
  case $_
  when DictionaryData
    __stream__.concat("<div>")
    __stream__.concat(XX606266418Instance.render_with($_, __binding__))
    __stream__.concat("</div>")
  else
    __stream__.concat("<div>")
    __stream__.concat(XX606266418Instance.render_with($_, __binding__))
    __stream__.concat("</div>")
  end
  Tracer << "%s:%s:%s" % ["<root>","out", __stream__] 
  __stream__
end
class XX606266418
            include Amrita2
          include Amrita2::Runtime

  def render_with(value,__binding__,__cnt__ = -1 )
    __stream__ = '' 
    $_ = value
    if __cnt__ < 0
      $_ = 
      eval("['a', 'b', 'c', 'd']", __binding__)
      if $_.kind_of?(Amrita2::Enum) and not $_.kind_of?(ScalarData)
        $_.each_with_index  do |v, i| 
          __stream__.concat(render_with(v, __binding__, i) )
        end
        return __stream__
      end
    end
    Tracer << "%s:%s:%s" % ["<a>","in", $_.inspect] 
    case $_
    when DictionaryData
      __stream__.concat("<a>")
      __stream__.concat(C000)
      __stream__.concat(C001)
      __stream__.concat(C002)
      __stream__.concat("</a>")
    when ScalarData
      __stream__.concat("<a>")
      __stream__.concat($_.amrita_value)
      __stream__.concat("</a>")
    else
      __stream__.concat("<a>")
      __stream__.concat(C000)
      __stream__.concat(C001)
      __stream__.concat(C002)
      __stream__.concat("</a>")
    end
    Tracer << "%s:%s:%s" % ["<a>","out", __stream__] 
    __stream__
  end
  C000 = "    "
  C001 = "a"
  C002 = "\n"
end
XX606266418Instance = XX606266418.new
--- PreProcessor ---
<div ><a am:for="['a', 'b', 'c', 'd']" >    <span>a</span>
</a></div>
--- Render ---
<root>:in:{:hello=>"HELLO"}
<a>:in:"a"
<a>:out:<a>a</a>
<a>:in:"b"
<a>:out:<a>b</a>
<a>:in:"c"
<a>:out:<a>c</a>
<a>:in:"d"
<a>:out:<a>d</a>
<root>:out:<div><a>a</a><a>b</a><a>c</a><a>d</a></div>
<div><a>a</a><a>b</a><a>c</a><a>d</a></div>

これならAmXMLを展開した結果と展開済みrender_withを同時に眺められますので、一粒で二度美味しい感じです!おかげで"XX606266418"や"XX606266418Instance"などの非常に興味深い点を発見することが出来ました。この辺の定数やクラスは果たしてきちんと remove_const されているのでしょうか。Webアプリケーションやデーモンさんを書く時にはちょっと気になりますね。あと無意味なcase文があったりするのですが、これはなぜこうなっているのでしょう。まずはこのコードを追い掛けてみて動作を把握してから、この辺の秘密を調べていきたいと思います。

しかし

<< a [['a', 'b', 'c', 'd']] <

のような書き方はアリなのかなぁ。この辺を応用すると色々出来そうな感じ。

追記

syntax highlight と行番号を付与するために次のように改良しました。

require "rubygems"
require "amrita2"

$src = <<AM2
<<div :hello | Each[:class => ["a", "b"]] >>
AM2

tmpl = Amrita2::Template.new($src)
tmpl.set_trace(:all) do |msg|
  if msg.split("\n").size > 1
    IO.popen("highlight -l -A --syntax rb", "r+") do |io|
      io.puts msg
      io.close_write
      puts io.readlines
    end
  else
    puts msg
  end
end
tmpl.setup
tmpl.instance_eval do
  puts "--- PreProcessor ---"
  puts @preprocessor.process($src)
end
puts "--- Render ---"
puts tmpl.render_with(:hello => "Hello, World!")

こうするとエラーを起こした時にどこがどうなっているか分かりやすくなって良い感じです。