ループでEval(その1)

evalを再帰で書くとすぐStackOverflowExceptionで死んじゃうので、ループで書いてみようという試み。
単純に走査するだけのコードを前回のエントリで書いたので、それを使いつつ。
こんな感じ。

using System;
using System.Collections.Generic;

abstract class SExp { }

class Pair : SExp
{
  public SExp Car { get; set; }
  public SExp Cdr { get; set; }

  public Pair(SExp car, SExp cdr)
  {
    this.Car = car;
    this.Cdr = cdr;
  }

  public override string ToString()
  {
    return "(" + this.Car + " . " + this.Cdr + ")";
  }
}

class Atom : SExp
{
  public string Value { get; set; }

  public Atom(string str)
  {
    this.Value = str;
  }

  public override string ToString()
  {
    return this.Value;
  }
}

class Nil : SExp 
{
  public override string ToString()
  {
    return "()";
  }
}

class App
{
  static void Main(string[] args)
  {
    // (a b c)
    SExp exp1 = new Pair(new Atom("a"), new Pair(new Atom("b"), new Pair(new Atom("c"), new Nil())));
    // (a b c . d)
    SExp exp2 = new Pair(new Atom("a"), new Pair(new Atom("b"), new Pair(new Atom("c"), new Atom("d"))));
    // (x y (a b c) z)
    SExp exp3 = new Pair(new Atom("x"), new Pair(new Atom("y"), new Pair(exp1, new Pair(new Atom("z"), new Nil()))));

    SExp result = Scan(exp1);
    Console.WriteLine("result: " + result);
    Console.WriteLine("==========");

    result = Scan(exp2);
    Console.WriteLine("result: " + result);
    Console.WriteLine("==========");

    result = Scan(exp3);
    Console.WriteLine("result: " + result);
    Console.WriteLine("==========");

    result = Scan(new Atom("hoge"));
    Console.WriteLine("result: " + result);
    Console.WriteLine("==========");

    result = Scan(new Nil());
    Console.WriteLine("result: " + result);
    Console.WriteLine("==========");
  }

  static SExp Scan(SExp exp)
  {
    Stack<SExp> stk1 = new Stack<SExp>();
    Stack<Frame> stk2 = new Stack<Frame>();
    Frame frame = new Frame();
    int sqn = 0;

    SExp result = null;

    while (true)
    {
      if (exp is Pair)
      {
        SExp car = (exp as Pair).Car;
        SExp cdr = (exp as Pair).Cdr;

        if (cdr is Nil || cdr is Atom)
        {
          frame.State = Frame.StateForEval.LastCellScaned;
        }

        stk1.Push(cdr);
        exp = car;

        if (car is Pair)
        {
          Console.WriteLine("push");
          stk2.Push(frame);
          frame = new Frame();
        }
      }
      else
      {
        Console.WriteLine("eval: " + exp);
        result = exp;
        if (frame.State == Frame.StateForEval.LastCarEvaled)
        {
          frame.State = Frame.StateForEval.CanEval;
        }
        if (frame.State == Frame.StateForEval.LastCellScaned)
        {
          frame.State = Frame.StateForEval.LastCarEvaled;
        }

        if (frame.State == Frame.StateForEval.CanEval)
        {
          if (frame.List is Nil)
          {
            frame.List = new Pair(exp, new Nil());
          }
          else
          {
            Pair last = GetLastPair(frame.List as Pair);
            if (exp is Atom)
            {
              last.Cdr = exp;
            }
          }

          // 評価した部分リストは、表示短縮のために E1, E2, E3,... で表記しておく
          sqn++;
          Console.WriteLine("EVAL: " + frame.List + " => E" + sqn);
          Atom a = new Atom("E" + sqn);

          if (stk2.Count == 0)
          {
            result = a;
          }
          else
          {
            Console.WriteLine("pop");
            frame = stk2.Pop();

            if (frame.State == Frame.StateForEval.LastCarEvaled)
            {
              frame.State = Frame.StateForEval.CanEval;
            }
            if (frame.State == Frame.StateForEval.LastCellScaned)
            {
              frame.State = Frame.StateForEval.LastCarEvaled;
            }

            if (frame.List is Nil)
            {
              frame.List = new Pair(exp, new Nil());
            }
            else
            {
              Pair last = GetLastPair(frame.List as Pair);
              last.Cdr = new Pair(a, new Nil());
            }
          }
        }
        else
        {
          if (frame.List is Nil)
          {
            frame.List = new Pair(exp, new Nil());
          }
          else
          {
            Pair last = GetLastPair(frame.List as Pair);
            last.Cdr = new Pair(exp, new Nil());
          }
        }

        if (0 == stk1.Count) break;
        exp = stk1.Pop();
      }
    }

    return result;
  }

  class Frame
  {
    public SExp List { get; set; }
    public StateForEval State { get; set; }

    public Frame()
    {
      List = new Nil();
      State = StateForEval.None;
    }

    public enum StateForEval
    {
      // まだ最後のコンスセルまで来てない
      None,
      // 最後のコンスセル読み込んだ
      LastCellScaned,
      // 最後のコンスセルのcarを処理した
      LastCarEvaled,
      // 最後のコンスセルのcdrを処理した(リストの要素全部を評価した)
      CanEval,
    }
  }

  // 最後のノード取得は番兵でやるべきだろ
  static Pair GetLastPair(Pair list)
  {
    while (list.Cdr is Pair)
    {
      list = list.Cdr as Pair;
    }
    return list;
  }
}

実行結果→Ideone.com - sh0FXd - Online C# Compiler & Debugging Tool
スタック2個使ったけど、大丈夫なのかな。変な事してないかな...。

  • 構文木を走査するためのスタック(stk1)
  • リストの評価がどこまで終わってるか覚えておくためのスタック(stk2)

今回は手続きだけを想定して書いたけど、構文(defineやlambdaなど)は、それ独自の評価ルールがあるから、どげんかせんといかん。