理系学生日記

おまえはいつまで学生気分なのか

Scheme の字句解析2

前に Scheme の字句 をクソ楽に区切りたいなーとか思って,一行で済ませてみようとしたのでしたが,結果としてうまくいきませんでした.

my @tokens = map { split /(?<=\()|(?=\))/, $_ } split /\s+/, <<SOURCE;

最初に空白を区切り文字として split しているもんだから," abc sdd" みたいな空白を含むクオート文字列がムダに分割されてしまう.

あーもうムカつくなー,他の人はどうやってんのかなーと,Text::CSV の PurePerl 実装の方を見てみたところ,正規表現使って区切ってるっぽい.

そういうわけですから,こんな感じで区切ったりするなどしました.

  • 括弧( '(' or ')' )が来たら,それは一文字で区切る
  • クオートが来たらその次のクオートまでで区切る
  • その他の場合は,括弧かクオートまでで区切る

とても単純ですね.これはあんましぼくが考えてないからで,クオート文字がエスケープとかされたら死ぬでしょう.

sub parse {
    my ($self) = @_;

    my $source = $self->{source};
    my @result;

    while( $source ) {
        $source =~ s/^\s+//;

        if ( $source =~ /( ^[()] | ^(["']).*?\2 | ^[^"'()\s]+ )/x ) {
            push @result, $1;
            my $len = length $1;
            $source =~ s/^.{$len}//;
        }
    }
    \@result;
}

こんな感じで使う.

use strict;
use warnings;
use Test::More qw(no_plan);

BEGIN {
    use_ok( 'Language::Scheme::Lexer' );
}

my $lexer = Language::Scheme::Lexer->new;
isa_ok( $lexer, 'Language::Scheme::Lexer' );
can_ok( $lexer, qw(load parse) );

my $source = do { local $/; <DATA> };
my $a = $lexer->load( $source )->parse();
ok( eq_array( 
    [ 
        '(', 'define', '(', 'list->set-name', 'size', 'lst', ')',
        '(', 'cond',
        '(', '(', 'null?', 'lst', ')', '"e"', ')',
        '(', '(', '=', 'size', '(', 'list-length', 'lst', ')', ')', '"f"', ')',
        '(', '#t', '(', 'apply', 'join-fields-with', '" "', '(', 'map', '->string', 'lst', ')', ')', ')', ')', ')' 
    ],
    $a ),
    'lexical analyse check'
);

__DATA__
(define (list->set-name size lst)
  (cond
   ((null? lst)  "e")
   ((= size (list-length lst)) "f")
   (#t (apply join-fields-with " " (map ->string lst)))))