関数型のテンプレートエンジンを作ってみる(0.2.3) - 実行系を書く

前回の続きで、テンプレートエンジンのエンジン部分を書く。
今回実装したところまでをVers_0.2というブランチにしてみた。

方針

  • 0.1の時点ではモナド則とか考えたけど、あんまり純粋さには拘らない。
  • パース結果をASTとして実行部分は別個に評価器として実装するのが素直だと思うのだけど、今回はまだごっちゃに実装する。


では順番に実装していく。

インタフェースの変更

0.1で作ったコアのインタフェース群を少し修正する。
Templateインタフェースのapply()は1.0ではTemplateを返していた。今回は「define」タグがコンテキストに影響を与える仕組みを実現するため、(Template, Context)を戻すようにする。(Javaはタプル無いのでTemplatePairというクラスを作った。)


意味としては、「自分自身にcontextを適用した結果(「旧」の戻り値と同じ)」と「contextに対する変化分」を戻す。contextに対する追加が無ければ空のContextを戻す。
旧:

public interface Template<V, T extends Template<V, T>> {
    T apply(Context<V, T> context);
    boolean isReducible();
}

↓↓↓
新:

public interface Template<V, T extends Template<V, T>> {
    TemplatePair<V, T> apply(Context<V, T> context);
    boolean isReducible();
}
public class TemplatePair<V, T extends Template<V, T>> {
    private final T template;
    private final Context<V, T> context;

    private TemplatePair(T template, Context<V, T> context) {
        this.template = template;
        this.context = context;
    }
    public T template() {
        return template;
    }
    public Context<V, T> context() {
        return context;
    }
    // template, contextからなるTemplatePairを戻す。
    public static <V, T extends Template<V, T>>
    TemplatePair<V, T> pairOf(T template, Context<V, T> context) {
        return new TemplatePair<V, T>(template, context);
    }
    // templateと空のContextからなるTemplatePairを戻す。
    public static <V, T extends Template<V, T>>
    TemplatePair<V, T> pairOf(T template) {
        return pairOf(template, ContextUtils.<V, T>emptyContext());
    }
}


それ以外の基本インタフェースは変更ないけど、ContextはUtilクラスを作っておく。
Contextの合成や空のContextを作りやすくしておく。

public final class ContextUtils {
    private ContextUtils() {
    }
    // symbolに対してvalueを戻すContextを生成する。
    public static <V, T extends Template<V, T>> Context<V, T>
        newContext(final Symbol symbol, final T value) {
        return new Context<V, T>() {
            public T get(Symbol s) {
                return symbol.equals(s) ? value : null;
            }
        };
    }
    // symbolに対してvalueを戻し、それ以外にはcontextの戻り値を戻すContextを生成する。
    public static <V, T extends Template<V, T>> Context<V, T>
            put(final Context<V, T> context, final Symbol symbol, final T value) {
        return merge(newContext(symbol, value), context);
    }
    // 二つのContextを合成する。firstを探し、値が無ければnextを探す。
    public static <V, T extends Template<V, T>> Context<V, T>
            merge(final Context<V, T> first, final Context<V, T> next) {
        return new Context<V, T>() {
            public T get(Symbol s) {
                T value = first.get(s);
                if (value != null) {
                    return value;
                }
                return next.get(s);
            }
        };
    }
    @SuppressWarnings("rawtypes")
    private static Context EMPTY_CONTEXT = 
        new Context() {
            public Template get(Symbol name) {
                return null;
            }
        };
    // 空のContextを戻す。
    @SuppressWarnings("unchecked")
    public static <V, T extends Template<V, T>>
            Context<V, T> emptyContext() {
        return EMPTY_CONTEXT;
    }
}

テンプレートクラス(既存)の変更

apply()の戻り値の型が変わっているので、SimpleContainerとSimpleLiteralの実装も変更する必要がある。
いずれもapply()の中身だけの変更。
SimpleLiteralは変数を持たないためapply()しても変化しないし、Contextにも影響を与えないので自分自身と空のContextのペアを戻す。

public class SimpleLiteral implements SimpleTemplate {
...
    // 自分自身と空のContextを戻す。
    public TemplatePair<Object, SimpleTemplate> apply(Context<Object, SimpleTemplate> context) {
        return TemplatePair.<Object, SimpleTemplate>pairOf(this);
    }
...
}

SimpleContainerは各要素をapply()した結果を蓄積して戻す。

public class SimpleContainer implements SimpleTemplate {
    private final List<SimpleTemplate> elements;
...
    @Override
    public TemplatePair<Object, SimpleTemplate> apply(Context<Object, SimpleTemplate> context) {
        if (!isReducible()) {
            return TemplatePair.<Object, SimpleTemplate>pairOf(this);
        }

        // 評価後の子要素のTemplateを保持する
        List<SimpleTemplate> newElements = new ArrayList<SimpleTemplate>();
        // contextへの変更を保持する
        Context<Object, SimpleTemplate> newContext = ContextUtils.emptyContext();

        for (SimpleTemplate element : elements) {
            // contextの差分と引数のcontextをマージする。
            Context<Object, SimpleTemplate> totalContext = ContextUtils.merge(newContext, context);
            // 各要素にapply
            TemplatePair<Object, SimpleTemplate> applied = element.apply(totalContext);
            // 評価結果のTemplateを追加
            newElements.add(applied.template());
            // 評価結果のContextの差分を追加
            newContext = ContextUtils.merge(applied.context(), newContext);
        }
        // 評価済みの子要素を保持するSimpleContainerと、Contextの差分を足し込んだ結果を戻す。
        return TemplatePair.pairOf(new SimpleContainer(newElements), newContext);
    }
...
}

こうすることで、前でdefineした変数をその後ろで参照できるようになるはず。


前回はプレースホルダ(シンボル名を「{」「}」で囲ったもの)をSimpleReferenceという名前のテンプレートとして実現していたが、今回はinsert/defineというタグを実装するので、SimpleReferenceクラスは廃止して、各タグを「○○プロセッサ(XXProcessor)」という名前で実装する。

defineタグの実装

defineには3種類の書式がある。name常にシンボルだが、valueは即値とシンボルと、valueを書かずにコンテナに内容を記述する方式がある。

// 即値
{define name=yourName value="terazzo" /}
// シンボル(参照)
{define name=yourName value=parameterName /}
// コンテナに記述
{define name=helloWold}
Hello, {insert value=yourName /} !
{/define}

3種類の値を持ってif文で処理を切り分けるのは嫌なのでインタフェースを切って多態で対処する。(実装クラスは後ほど。)

public interface SimpleSource {
    SimpleTemplate getTemplate(Context<Object, SimpleTemplate> context);
}

defineタグは、評価すると、コンテキストに対して「name属性の値(シンボル)」という名前で「value属性の値」という影響を与えるようにしたい。
また、defineタグ自体は表示しないので、一旦評価すると何か無害なTemplateに変化させたい。

public class SimpleDefineProcessor implements SimpleTemplate {
    // name属性で指定したSymbol
    private final Symbol symbol;
    // value属性またはコンテナの中身で定義した値
    private final SimpleSource source;
...
    @Override
    public TemplatePair<Object, SimpleTemplate> apply(final Context<Object, SimpleTemplate> context) {
        // 値を取り出す
        SimpleTemplate definition = source.getTemplate(context);
        if (definition == null) {
            // 定義が見つからない場合は自分自身を戻す=未評価
            return TemplatePair.<Object, SimpleTemplate>pairOf(this);
        }

	// 「(symbolの値)に対して(definitionの値)を戻す」コンテキストを生成
        Context<Object, SimpleTemplate> newContext = ContextUtils.newContext(symbol, definition);
        // 「無害なTemplate」と差分のコンテキストを戻す
        return TemplatePair.pairOf(new SimpleNop(), newContext);
    }
...
}

「無害なTemplate」はapplyしてもテンプレートにもコンテキストにも影響を与えないTemplateであるSimpleNopとして実装する。また、SimpleNopは最後にgetString()で文字列化する時に空文字列を戻すようにする。*1

public class SimpleNop implements SimpleTemplate {
    @Override
    public TemplatePair<Object, SimpleTemplate> apply(Context<Object, SimpleTemplate> context) {
        return TemplatePair.<Object, SimpleTemplate>pairOf(this);
    }
    @Override
    public String getString() {
        return "";
    }
    @Override
    public boolean isReducible() {
        return false;
    }
}

SimpleSourceは、それぞれのパターンに合わせて3種類の実装を持たせる。
まず即値の場合(value="hoge")。getTemplate()するとその文字列を表すSimpleLiteralになる。

public class SimpleExpression implements SimpleSource {
    private final String expression;
    public SimpleExpression(String expression) {
        this.text = expression;
    }
    @Override
    public SimpleTemplate getTemplate(Context<Object, SimpleTemplate> context) {
        return new SimpleLiteral(expression);
    }
}

シンボルの場合(value=hoge)。getTemplate()するとContextからそのシンボルで値を取り戻して戻す。

public class SimpleReference implements SimpleSource {
    private final Symbol symbol;
    public SimpleReference(Symbol symbol) {
        this.symbol = symbol;
    }

    @Override
    public SimpleTemplate getTemplate(Context<Object, SimpleTemplate> context) {
        return context.get(symbol);
    }
}

コンテナの内容として指定した場合、getTemplate()するとそのコンテナを表すSimpleContainerになる。
SimpleTemplatePrototypeはパーサの時に出て来た、変数をバインドする前の式にあたるもの。
SimpleContainerPrototype自身もSimpleTemplatePrototypeとして実装する。

public class SimpleContainerPrototype implements SimpleTemplatePrototype, SimpleSource {
    private final List<SimpleTemplatePrototype> elements;

    public SimpleContainerPrototype(List<SimpleTemplatePrototype> elements) {
        super();
        this.elements = elements;
    }

    @Override
    public SimpleTemplate instantiate(Context<Object, SimpleTemplate> lexicalContext) {
        List<SimpleTemplate> tempalteElements = new ArrayList<SimpleTemplate>();
        for (SimpleTemplatePrototype source : elements) {
            tempalteElements.add(source.instantiate(lexicalContext));
        }
        return new SimpleContainer(tempalteElements);
    }
    @Override
    public SimpleTemplate getTemplate(Context<Object, SimpleTemplate> context) {
        return instantiate(context);
    }
}


さて、パース時はSimpleDefineProcessorではなく変数をバインドするまえの形であるSimpleDefineProcessorPrototypeするようにしていた。
そこで、SimpleDefineProcessorを生成できるようにSimpleDefineProcessorPrototypeを実装する。

public class SimpleDefineProcessorPrototype implements SimpleTemplatePrototype {
    private Symbol symbol;
    private SimpleSource source;
    public SimpleDefineProcessorPrototype(Symbol symbol, SimpleSource source) {
        super();
        this.symbol = symbol;
        this.source = source;
    }
    public SimpleTemplate instantiate(Context<Object, SimpleTemplate> lexicalContext) {
         return new SimpleDefineProcessor(lexicalContext, symbol, source);
    }
}

先ほどのSimpleDefineProcessorはコンストラクタ等が実装されていなかったので書き直す。
また、instantiate時に受け取ったContext(=構文的に静的に決定されるContext)を保持して評価時に利用するようにする。クロージャ的なイメージ。

public class SimpleDefineProcessor implements SimpleTemplate {
    /** 初期化時のContext */
    private final Context<Object, SimpleTemplate> lexicalContext;
    /** name属性で指定したSymbol */
    private final Symbol symbol;
    /** value属性またはコンテナの中身で定義した値 */
    private final SimpleSource source;

    public SimpleDefineProcessor(
            Context<Object, SimpleTemplate> lexicalContext, Symbol symbol, SimpleSource source) {
        super();
        this.lexicalContext = lexicalContext;
        this.symbol = symbol;
        this.source = source;
    }
    @Override
    public TemplatePair<Object, SimpleTemplate> apply(final Context<Object, SimpleTemplate> context) {
        // 初期化時のlexicalContext + 引数のcontextを使用する(引数の方を優先)
        Context<Object, SimpleTemplate> totalContext = ContextUtils.merge(context, lexicalContext);

        // 値を取り出す
        SimpleTemplate definition = source.getTemplate(totalContext);
        if (definition == null) {
            // 定義が見つからない場合は自分自身を戻す=未評価
            return TemplatePair.<Object, SimpleTemplate>pairOf(this);
        }

	// 「(symbolの値)に対して(definitionの値)を戻す」コンテキストを生成
        Context<Object, SimpleTemplate> newContext = ContextUtils.newContext(symbol, definition);
        // 「無害なTemplate」と差分のコンテキストを戻す
        return TemplatePair.pairOf(new SimpleNop(), newContext);
    }
    @Override
    public boolean isReducible() {
        return true; // 評価されると変化するのでtrue
    }
    @Override
    public String getString() {
        throw new IllegalStateException("Unevaluated define:" + source); // 残っているとNG
    }
}

insertタグの実装

insertには2種類の書式がある。valueは即値とシンボルがある。即値の方はナンセンスな気もするけど。

こんにちは、{insert value="terazzo"/}様。
こんにちは、{insert value=yourName/}様。

また、シンボルの場合、参照先のテンプレートを評価する時に渡すContextをコンテナ内のdefineタグで指定できる。

{insert value=helloWold}
{define name=yourName value="terazzo" /}
{/insert}


では実装。
insertはコンテナを評価して(つまり中に含まれるdefineを評価して)得たContextをvalueで参照されるテンプレートに渡して評価し、評価結果を自分自身のapplyの結果として戻す。
但し、評価結果が既約でない場合はinsertタグを維持する。

public class SimpleInsertProcessor implements SimpleTemplate {
    /** 初期化時のContext */
    private final Context<Object, SimpleTemplate> lexicalContext;
    /** value属性で指定した値 */
    private final SimpleSource source;
    /** コンテナの中身を保持するSimpleTemplate。コンテナは引数を作るのに使用する。 */
    private final SimpleTemplate content;

    public SimpleInsertProcessor(
            Context<Object, SimpleTemplate>lexicalContext,
            SimpleSource source,
            SimpleTemplate content) {
        
        this.lexicalContext = lexicalContext;
        this.source = source;
        this.content = content;
    }

    @Override
    public TemplatePair<Object, SimpleTemplate> apply(final Context<Object, SimpleTemplate> context) {
        Context<Object, SimpleTemplate> totalContext = ContextUtils.merge(context, lexicalContext);

        // value値を取り出す
        SimpleTemplate template = this.source.getTemplate(totalContext);
        // 空なら、未評価。但しcontextは覚えておく
        if (template == null) {
            return TemplatePair.<Object, SimpleTemplate>pairOf(
                new SimpleInsertProcessor(totalContext, source, content));
        }

        // contentを評価すると、中に含まれるdefineがcontextを戻す。
        TemplatePair<Object, SimpleTemplate> appliedContent = content.apply(totalContext);
        Context<Object, SimpleTemplate> argumentContext = appliedContent.context();

        // insert先のテンプレートを引数のコンテキストで評価
        TemplatePair<Object, SimpleTemplate> result = template.apply(argumentContext);

        if (!result.template().isReducible()) {
            return TemplatePair.pairOf(result.template());
        }
        // insert先のテンプレートが既約でない場合、insertタグを維持。
        return TemplatePair.<Object, SimpleTemplate>pairOf(
                new SimpleInsertProcessor(
                        totalContext,
                        new SimpleWrapper(result.template()),
                        appliedContent.template()));
    }

    @Override
    public boolean isReducible() {
        return true; // 評価されると変化するのでtrue
    }
    @Override
    public String getString() {
        throw new IllegalStateException("Unevaluated define:" + source); // 残っているとNG
    }
}

defineタグで参照されるテンプレートの評価時には、テンプレートがinstantiateされたときのContextと、insertが渡すContextのみが使われる。insertタグ自体の評価時のコンテキストが渡されるわけではない。つまりある種のスコープゲートとして機能する。


取り出してしまったvalueの内容を再利用する為に、SimpleWrapperというSimpleSource実装クラスを用意している。

public class SimpleWrapper implements SimpleSource {
    private final SimpleTemplate content;
    public SimpleWrapper(SimpleTemplate content) {
        this.content = content;
    }
    @Override
    public SimpleTemplate getTemplate(Context<Object, SimpleTemplate> context) {
        return content;
    }
}


評価結果が既約でない場合、つまり変数を含むテンプレートの場合、それをそのままinsertの評価結果として戻すわけにはいかない。なぜならinsertの評価結果は、次回評価されるときはinsertがあった場合と同じContextで評価されるため、insert先のテンプレートをそれで評価すると静的スコープにならない。そこで、再度insertタグを戻す事でスコープゲートを維持しないといけない。


最後に、パース時に生成したSimpleInsertProcessorPrototypeからSimpleInsertProcessorを生成できるようにSimpleInsertProcessorPrototypeを実装する。

public class SimpleInsertProcessorPrototype implements SimpleTemplatePrototype {
    private final SimpleSource source;
    private final SimpleTemplatePrototype contentPrototype;
    public SimpleInsertProcessorPrototype(SimpleSource source, SimpleTemplatePrototype contentPrototype) {
        super();
        this.source = source;
        this.contentPrototype = contentPrototype;
    }
    public SimpleTemplate instantiate(
            Context<Object, SimpleTemplate> lexicalContext) {
         return new SimpleInsertProcessor(
                 lexicalContext, source, 
                 contentPrototype.instantiate(lexicalContext));
    }
}

自身のinstantiate()時にコンテナを同じ静的コンテキストを持つテンプレートしてinstantiate()している。
apply時にやるべきなのかどうかちょっと分からないけど、基本的に動作に変わりはない。

使用例

トン吉サンプルを新しいフォーマットで書くとこうなる。

    @Test
    public void testTonkichi() {
        SimpleTranslator translator = new SimpleTranslator();

        SimpleTemplate template = translator.toTemplate("" +
                "{insert value=aisatsu}" +
                    "{define name=okyakusamamei value=customerName /}" +
                "{/insert}" +
                "{insert value=raitenonrei}" +
                    "{define name=raitenbi value=visitDay /}" +
                "{/insert}"
                    );
        Context<Object, SimpleTemplate> context1 =
            new ContextBuilder<Object, SimpleTemplate>()
            .put(Symbol.of("aisatsu"),
                    translator.toTemplate("こんにちは、{insert value=okyakusamamei/}様。"))
            .put(Symbol.of("raitenonrei"),
                    translator.toTemplate("{insert value=raitenbi/}にはご来店いただき、\n" +
            		"まことにありがとうございます。"))
            .context();
        Context<Object, SimpleTemplate> context2 =
            new ContextBuilder<Object, SimpleTemplate>()
            .put(Symbol.of("customerName"), translator.toTemplate("板東トン吉"))
            .put(Symbol.of("visitDay"), translator.toTemplate("1月21日"))
            .context();
        SimpleTemplate applied = template.apply(context1).template();
        SimpleTemplate applied2 = applied.apply(context2).template();

        String result = translator.fromTemplate(applied2);
        assertEquals("こんにちは、板東トン吉様。1月21日にはご来店いただき、\n" +
        		"まことにありがとうございます。", result);
    }

一つ目のapply()でinsertの先のaisatsu、raitenonreiが評価されるが、customerNameとvisitDayが未定義であり、つまりaisatsu評価時のokyakusamameiやraitenonrei評価時のraitenbiが未定義であるため、insertは保持されたままになる。
二つ目のapply()でcustomerName:"板東トン吉"とvisitDay:"1月21日"を渡すと、それぞれaisatsuにokyakusamamei、raitenonreiにraitenbiが渡され、評価の結果既約のテンプレート(SimpleLiteralなど)となり、文字列として出力できるようになる。


insertがスコープゲートして機能しているかを見てみる。
テンプレート1をhelloWoldという名前で定義

Hello, {insert value=yourName /}!

テンプレート2(main)で、helloWoldをinsertする。

{insert value=helloWold}
    {define name=yourName value=inputName /}
{/insert}

テンプレート2評価時にinputNameを与えてやると、表示される。

       SimpleTranslator translator = new SimpleTranslator();
       SimpleTemplate helloWorld = translator.toTemplate(
               "Hello, {insert value=yourName /}!"
           );
       SimpleTemplate main = translator.toTemplate(
               "{insert value=helloWold}" +
               "{define name=yourName value=inputName /}" +
               "{/insert}"
           );
       Context<Object, SimpleTemplate> context =
           new ContextBuilder<Object, SimpleTemplate>()
           .put(Symbol.of("helloWold"), helloWorld)
           .put(Symbol.of("inputName"), translator.toTemplate("板東トン吉"))
           .context();
       String output = translator.fromTemplate(main.apply(context).template());
       System.out.println("output = " + output);

↓↓↓

output = Hello, 板東トン吉!

テンプレート2評価時にinputNameを与えずにyourNameを与えてやると、エラーになる。

       SimpleTranslator translator = new SimpleTranslator();
       SimpleTemplate helloWorld = translator.toTemplate(
               "Hello, {insert value=yourName /}!"
           );
       SimpleTemplate main = translator.toTemplate(
               "{insert value=helloWold}" +
               "{define name=yourName value=inputName /}" +
               "{/insert}"
           );
       Context<Object, SimpleTemplate> context =
           new ContextBuilder<Object, SimpleTemplate>()
           .put(Symbol.of("helloWold"), helloWorld)
           .put(Symbol.of("yourname"), translator.toTemplate("板東トン吉")) // insert先の変数名で追加
           .context();
       String output = translator.fromTemplate(main.apply(context).template());
       System.out.println("output = " + output);

↓↓↓

Unevaluated insert:sample.hotplate.sample.processor.SimpleInsertProcessor@65b60280


テンプレートを引数として渡すサンプル。
テンプレート内で定義したテンプレートを、インサート先に渡してみる。
インサートされるテンプレート: 名前表示部分を可変とする。

Hello, {insert value=namePart /}!

インサートする側: namePartをdefineして渡す。

{insert value=helloWold}
{define name=namePart}
{insert value=familyName/} {insert value=firstName/}
{/define}
{/insert}"

familyName, firstNameはインサートする側で束縛される。

       SimpleTranslator translator = new SimpleTranslator();
       SimpleTemplate helloWorld = translator.toTemplate(
               "{define name=firstName value=\"花子\" /}" + 
               "Hello, {insert value=namePart /}!"
           );
       SimpleTemplate main = translator.toTemplate(
               "{define name=japaneseStyleNamePart}" +
               "{insert value=familyName/} {insert value=firstName/}" +
               "{/define}" + 
               "{insert value=helloWold}" +
               "{define name=namePart value=japaneseStyleNamePart /}" +
               "{/insert}"
           );
       Context<Object, SimpleTemplate> context =
           new ContextBuilder<Object, SimpleTemplate>()
           .put(Symbol.of("familyName"), translator.toTemplate("板東"))
           .put(Symbol.of("firstName"), translator.toTemplate("トン吉"))
           .put(Symbol.of("helloWold"), helloWorld)
           .context();
       SimpleTemplate applied = main.apply(context).template();
       String output = translator.fromTemplate(applied);
       System.out.println("output = " + output);

↓↓↓

output = Hello, 板東 トン吉!

インサートする側は以下のように間接的に書く事も出来る。

{define name=japaneseStyleNamePart}
{insert value=familyName/} {insert value=firstName/}
{/define}
{insert value=helloWold}
  {define name=namePart value=japaneseStyleNamePart /}
{/insert}"


渡したテンプレートに変数を残しておいて、includeされた側で評価時に決定しても良い。
インクルードされる側:

{insert value=message}
{define name=companyName value="山田商会" /}
{/insert}
---
Copyright(c) 山田商会 All Rights Reserved.

インクルードする側:

{insert value=layout}
{define name=message}
こんにちは、{insert value=companyName /}のホームページにようこそ!
{/define}
{/insert}

実行結果

       SimpleTranslator translator = new SimpleTranslator();
       SimpleTemplate yamadaStyle = translator.toTemplate(
               "{insert value=message}\n" +
               "{define name=companyName value=\"山田商会\" /}\n" +
               "{/insert}\n" +
               "---\n" +
               "Copyright(c) 山田商会 All Rights Reserved.\n"
               );
       SimpleTemplate main = translator.toTemplate(
               "{insert value=layout}\n" +
               "{define name=message}\n" +
               "こんにちは、{insert value=companyName /}のホームページにようこそ!" +
               "{/define}\n" +
               "{/insert}\n"
               );
       Context<Object, SimpleTemplate> context =
           new ContextBuilder<Object, SimpleTemplate>()
           .put(Symbol.of("layout"), yamadaStyle)
           .context();
       String output = 
           translator.fromTemplate(main.apply(context).template());
       System.out.println("output = " + output);

↓↓↓

output = 
こんにちは、山田商会のホームページにようこそ!
---
Copyright(c) 山田商会 All Rights Reserved.

ちょっと例が不自然だけど……。

感想とか課題

defineとinsertがわりと対称な関係にあるように思える。(define:contextに影響を及ぼす/insert:templateに影響を及ぼす。)
もう少し実装を詰めればコード的にもその関係が現れそうな気がするので少し考えてみたい。


ASTと評価器に分離するのはそのうちやりたい。
最後に文字列化するところも同様にtreeをtraverseすることで書けるようになるはず。
言語実装経験とか無いのでそういう普通の事が難しい。


評価順序によって結果が変わるのをなんとかしたい。引数を明示化して、自由変数が一つでも残っている場合はdefineを動かさない等の対処が必要かも。

*1:要素が空のSimpleContainerや中身が空文字列のSimpleLiteralでも良かったけど・・・