MooseでLisp(MoospのFunction::Setq)

やさしい Lisp の作り方 by Java and by C#を参考に書いてみる。

sub fun {
  my ($self, $arguments) = @_;
  my $arg1 = $self->eval->eval($arguments->cdr)->car;
  my $sym = $arguments->car;
  $sym->value($arg1);
  $self->env->put($sym,$sym->name);
  return $arg1;
}

束縛するものがリストだと、まだうまくいかない。

Moosp> (setq a 5)
5
Moosp> (setq b 2)
2
Moosp> (+ a b)
7
Moosp> (setq c (+ 1 2))
Attribute (value) does not pass the type constraint because: Validation
failed for 'Moosp::Sexp' failed with value Moosp::List=HASH(0xa2a110) at lib/Moosp/Function/Setq.pm line 22
Moosp::Function::Setq::fun('Moosp::Function::Setq=HASH(0xa208a0)',
'Moosp::List=HASH(0xa29a80)') called at lib/Moosp/Eval.pm line 93
Moosp::Eval::eval('Moosp::Eval=HASH(0xa1dcc0)',
'Moosp::List=HASH(0xa29fa0)') called at lib/Moosp.pm line 67
Moosp::run('Moosp=HASH(0x804320)') called at moosp.pl line 7

MooseでLisp(MoospのFunction::Add)

やさしい Lisp の作り方 by Java and by C#を参考に書いてみる。

REPLまで出来て

(+ 1 2)

は動くのに

(+ (+ 1 2) 3)

が動かなくて、evalの再帰が悪いのかと思ったら、Functionのほうだった。

sub fun {
  my ($self, $arguments) = @_;
  my $arg1;
  my $ret =  Moosp::Integer->new(value => 0);
  return $ret if ($arguments->isa('Moosp::Nil'));

  while (1) {
    $arg1 = $self->eval->eval($arguments->car);
    $ret = $ret->add($arg1);
    last if ($arguments->cdr->isa('Moosp::Nil'));
    $arguments = $arguments->cdr;
  }
  return $ret;
}

carがfunctionの場合はcdr部は全て評価済みにしておかないといけないんだった。

やっとうまく動いた

Moosp> (+ (+ 1 2) (+ 3 4))
10

tak実行が見えてきた。

MooseでLispを実装してみる(REPL)

やさしい Lisp の作り方 by Java and by C#を参考に書いてみる。(14日目のあたり)

REPLまでやって対話環境にしてみたら、エラーが出まくる。readerよりはevaluatorまわりの気がしないでもない。なんか細かいとこから見直さないといけなさそうなのでTest::Pod::Coverageとかちゃんと通るようにという気晴らしを。

明日から、帰省するので向こうでちまちまデバッグをする予定。ゴールデンウィーク中には最後(takの実行)までは行きたい。

MooseでLispを実装してみる(Eval)

やさしい Lisp の作り方 by Java and by C#を参考に書いてみる。(13日目のあたり)

REPLのE、つまり評価器をつくる。これが終われば統合して処理系を完成させるのみ。

しかし10日目のCARがevalしたあとのcarを返すようになっているのだけど、evalはlistを戻さんよ?最後の処理系の完成のところで色々バグが見つかる予感(大)

MooseでLispを実装してみる(Readerのデバッグ)

やさしい Lisp の作り方 by Java and by C#を参考に書いてみる。(12日目のあたり)

()がNilになるようにReaderをいじる。makeListメソッドをちょっと変えればいいだけ。

MooseでLispを実装してみる(ラムダリストと変数の局所化)

やさしい Lisp の作り方 by Java and by C#を参考に書いてみる。(11日目のあたり)

lambda1

  1. argumentsをスタックに積む。その際積んだスタート地点を記憶しておく
  2. ラムダリストを順番にスタックの値と交換する(元の値は待避される)
  3. body部を評価する
  4. ラムダリストをスタックの値と交換する(元の値に戻す)
  5. 評価した値を返して終了

例としてa=>5,b=>1であるとき以下の式を評価してみる。

((lambda (a b) (+ a b)) 2 3)

スタックに引数の値を積む

lambda2

スタックの値とラムダリストの値を交換する

lambda3

body部を評価したらスタックに待避した値を戻す

lambda4

これでOK

Moospもこの通りに実装すればよいみたい。あとsexpEvalの

argList = (List)argList.cdr;

は必要ないような気がする。

MooseでLispを実装してみる(システム関数)

やさしい Lisp の作り方 by Java and by C#を参考に書いてみる。(10日目のあたり)

評価系まわりを書き始めるが、ここらへんから妙に省略度が高くなっているので追いかけるのが大変。14日まで頑張れば一通りできるようなのだけどそこまでたどり着けるのが不安になってきた。

さて、関数はS式関数と内部的な関数(システム関数)があって、システム関数は別のクラスとしてつくる。Moosp::Functionってのを用意してその下にMoosp::Function::Car,Moosp::Function::Quoteとかのクラスを用意するようにしてみた。システム関数は初期化の時に一括して環境(Env)のほうに登録するようになっているようだ。

ん?envとかevalはどっから出てきたんだ?と探したら14日目で

// システム関数の登録
Function funs = new Function(env, eval);  // 関数クラスの設定
funs.registSystemFunctions();      // システム関数の生成

初期化時にenvとevalを渡すのね。10日目のFunctionクラスのコードは省略されていたのであとでまた見直す必要があるな。

MooseでLispを実装してみる(RPL)

やさしい Lisp の作り方 by Java and by C#を参考に書いてみる。

READ-PRINT-LOOPまでのものを動かしてみる

$ perl moosp.pl 
Moosp> (1 2 3 4)
(1 2 3 4)
Moosp> (e r t)
(E R T)
Moosp> (2 . 3)
(2 . 3)
Moosp> (defun plus (a b) (+ a b))
(DEFUN PLUS (A B) (+ A B))

コード

package Moosp;

use Moose;
use Moosp::Env;
use Moosp::Reader;
use Term::ReadLine;

our $VERSION = 0.01;

sub run {
  my $self = shift;

  my $term = new Term::ReadLine 'Simple LISP REPL';
  my $prompt = "Moosp> ";
  my $OUT = $term->OUT || \*STDOUT;

  while ( defined ($_ = $term->readline($prompt)) ) {
    my $reader = Moosp::Reader->new();
    my $sexp = $reader->readFromString($_);
    warn $@ if $@;
    print $OUT $sexp->serialize(), "\n" unless $@;
  }

}

__PACKAGE__->meta->make_immutable;

no Moose;

1; # End of Moosp

moosp.pl

#!/usr/bin/env perl    
use lib "./lib";    
use Moosp;    
Moosp->new->run();

MooseでLispを実装してみる(Reader)

やさしい Lisp の作り方 by Java and by C#を参考に書いてみる。

readerは文字列(strings)と今読んでいる位置(index)と現在読んでいる文字の値(char)の属性を用意してあって、一文字読み進めてインデックスを一つ進めるget_charと一文字読み進めるけどインデックス値は進めないという先読み機能のnext_charというメソッドで移動していく。

先読みメソッドを用意するよりはインデックス値を一つ巻き戻すメソッドを用意しておいて、必要に応じてインデックス値を減らすという実装のほうだとcharアトリビュートを用意しておく必要はないかなと思った。

よくわからなくなってきたのでもう一度きちんと見直すかも

package Moosp::Reader;

use Moose;
use Moosp::Nil;
use Moosp::T;
use Moosp::List;
use Moosp::Integer;
use Moosp::Symbol;
use Moosp::Env;

has env => ( is => 'rw', isa => 'Moosp::Env', default => sub { Moosp::Env->new });
has index => (is => 'rw', isa => 'Int', default => 0 );
has strings => (is => 'rw', isa => 'Str');
has char => (is => 'rw', isa => 'Str');

sub get_char {
  my $self = shift;
  my $index = $self->index;
  $self->char(substr($self->strings,$index,1));
  $self->index($index + 1);
  return $self->char;
}

sub next_char {
  my $self = shift;
  $self->char(substr($self->strings,$self->index,1));
  return $self->char;
}

sub getSexpPrepare {
  my $self = shift;
  $self->getSexpPrepareString(@_);
}

sub readFromString {
  my ($self, $input) = @_;
  $self->getSexpPrepareString($input);
  $self->getSexp;
}

sub getSexpPrepareString {
  my ($self, $input) = @_;
  $self->strings($input);
}

sub getSexp {
  my $self = shift;
  while (my $ch = $self->get_char()) {
    if    ($ch eq '(')  { return $self->makeList(); }
    elsif ($ch eq '\\') { return $self->makeQuote(); }
    elsif ($ch eq '-')  { return $self->makeMinusNumber(); }
    else  {
      if ($ch =~ /\s/)  { last; }
      if ($ch =~ /\d/)   { return $self->makeNumber($ch); }
      return $self->makeSymbol($ch);
    }
  }
  return Moosp::Nil->instance;
}

sub makeList {
  my $self = shift;

  my $top = Moosp::List->new();
  my $list = $top;
  my $ch;
  while(1) {
    $list->car($self->getSexp());
    $ch =  $self->char;
    last if ($ch eq ')');
    return Moosp::Nil->instance if ($ch eq '');
    if ($self->next_char eq '.') {
      $ch = $self->get_char();
      $ch = $self->get_char();
      $list->cdr($self->getSexp());
      return $top;
    }
    $list->cdr(Moosp::List->new());
    $list = $list->cdr();
  }
  $ch = $self->get_char();
  return $top;
}

sub makeNumber {
  my ($self, $str) = @_;
  while (my $ch = $self->get_char()) {
    last if $ch eq '(' || $ch eq ')';
    last if $ch =~ /\s/;
    unless ($ch =~ /\d/) {
      $self->index($self->index - 1);
      return $self->makeSymbolInternal($str);
    }
    $str .= $ch;
  }
  return Moosp::Integer->new({value => $str});
}

sub makeSymbol {
  my ($self, $ch) = @_;
  return $self->makeSymbolInternal($ch);
}

sub makeSymbolInternal {
  my ($self, $str) = @_;
  while (my $ch = $self->get_char()) {
    last if $ch eq '(' || $ch eq ')';
    last if $ch =~ /\s/;
    $str .= $ch;
  }
  $str = uc $str;
  return Moosp::T->instance if ($str eq 'T');
  return Moosp::Nil->instance if ($str eq "NIL");

  my $sym = $self->env->symbol->{$str};
  return $self->env->set_symbol(Moosp::Symbol->new(name => $str)) unless defined($sym);
  return $sym;
}

sub makeMinusNumber {
  my $self = shift;
  my $nch = $self->next_char();
  return $self->makeSymbolInternal("-") unless $nch =~ /\d/;
  return $self->makeNumber("-");
}

sub makeQuote {
  my $self = shift;
  my $top = Moosp::List->new();
  my $list = $top;
  $list->car($self->env->symbol->{"QUOTE"});
  $list->cdr(Moosp::List->new());
  $list = $list->cdr();
  $self->get_char();
  $list->car($self->getSexp());
  return $top;
}

1; # End of Moosp::Reader

Readerが出来るとそれっぽくなってきた感じがする。

MooseでLispを実装してみる(List)

やさしい Lisp の作り方 by Java and by C#を参考に書いてみる。リストの構成にはシンボルが出てこない。

list

名前を付けるということがちょっと分かったような気がした。

シンボルは Lisp の最も重要なデータタイプの一つです。「記号」とか単に「名前」または「識別子」とも呼ばれていました。

シンボルは (1) 1 個以上の値を持つことができる、(2) シンボル名により唯一性が保障されます。(2) の唯一性とは同じ名前のシンボルは同じであることを表しています。もっと正確に言えば、同じ名前のシンボルは、ある等価関数によって必ず T を返す、つまり等価であることを保障します。

とりあえず、動くとこを見たかったので、色々端折ってる。

Atom

package Moosp::Atom;

use Moose;

__PACKAGE__->meta->make_immutable;

no Moose;


1; # End of Moosp::Atom

Number

package Moosp::Number;

use Moose;

extends 'Moosp::Atom';

sub str {}

no Moose;

1; # End of Moosp::Number

Integer

package Moosp::Integer;

use Moose;

extends 'Moosp::Number';
with 'Moosp::Sexp';

has 'value' => (is => 'rw',isa => 'Int');

sub add {
  my ($self, $i) = @_;
  my $new_value = $self->value + $i->value;
  __PACKAGE__->new({value => $new_value});
}

sub subt {
  my ($self, $i) = @_;
  my $new_value = $self->value - $i->value;
  __PACKAGE__->new({value => $new_value});
}

sub mul {
  my ($self, $i) = @_;
  my $new_value = $self->value * $i->value;
  __PACKAGE__->new({value => $new_value});
}

sub div {
  my ($self, $i) = @_;
  my $new_value = int($self->value / $i->value);
  __PACKAGE__->new({value => $new_value});
}

sub ge {
  my ($self, $i) = @_;
  if ($self->value >= $i->value){
    return Moosp::T->new();
  }
  else {
    return Moosp::Nil->new();
  }
}

sub str {
  my $self = shift;
  return $self->value;
}

__PACKAGE__->meta->make_immutable;
no Moose;

1; # End of Moosp::Integer

Cell

package Moosp::Cell;

use Moosp::Nil;
use Moose;

has 'car' => (
          is => 'rw',
          default => sub{ Moosp::Nil->instance }
);

has 'cdr' => (
          is => 'rw',
          default => sub { Moosp::Nil->instance }
);

__PACKAGE__->meta->make_immutable;

no Moose;

1;

List

package Moosp::List;

use Moose;
use Moosp::Nil;
extends 'Moosp::Cell';

sub BUILD {
  my ($self, $car, $cdr) = @_;
  $self->car(Moosp::Nil->instance);
  $self->cdr(Moosp::Nil->instance);
  return $self;
}

sub serialize {
  my $self = shift;
  my $str = "(";
  $str .= $self->car->serialize;
  if (ref($self->cdr) eq 'Moosp::Nil') {
    $str .= ")";
  }
  elsif (ref($self->cdr) eq 'Moosp::List') {
    $str .= " " . $self->cdr->serialize . ")";
  }
  else {
    $str .= " . " . $self->cdr->serialize . ")";
  }
  return $str;
}

1; # End of Moosp::List

test

my $int3 = Moosp::Integer->new(value => 3);
my $int5 = Moosp::Integer->new(value => 5);
my $list = Moosp::List->new();
$list->car($int3);
$list->cdr($int5); # (3 . 5)

my $list2 = Moosp::List->new();
$list2->car($int3);
$list2->cdr($list);  # (3 (3 . 5))

print $list2->cdr->car->value # cadr -> 3