RaccでSchemeパーサを作る
3/2(日)の生駒読書会#2に向けて、Schemeのパーサを準備しておこうと思って作ってみました。なんちゃってSchemeで遊ぶくらいなので、機能はまだまだ足りません。R5RSと見比べると足りないのはだいたいこんな感じかな。
- ペア。
- 文字。
- n進数。小数。複素数。
- quote。
- ベクタ。
- マクロ関係。
- 文字列の中の"。
ファイル: list_parser.y
# Local Variables: # mode: ruby # compile-command: "racc -o list_parser.rb list_parser.y" # End: class ListParser rule atoms : | atom atoms { result = List.new(*val) } atom : IDENT | BOOLEAN | NUMBER | STRING | list list : '(' atoms ')' { result = val[1] } end ---- header require 'strscan' ---- inner INIT_RE_STR = '[A-Za-z!$%&*/:<=>?^_~]' SSEQ_RE_STR = "(?:#{INIT_RE_STR}|\\d|\\+|-|\\.|@)" IDENT_RE = /(?:#{INIT_RE_STR}#{SSEQ_RE_STR}*|\+|-|\.\.\.)/ def parse(str) @tokens = StringScanner.new(str).collect_token do |scanner| case when scanner.skip(/\s+/) # ignore when scanner.skip(/;.*$/) # ignore when scanner.scan(IDENT_RE) [:IDENT, scanner[0].to_sym] when scanner.scan(/#[tf]/) [:BOOLEAN, scanner[0] == '#t'] when scanner.scan(/\d+/) [:NUMBER, scanner[0].to_i] when scanner.scan(/"([^"\\]+|\\.)*"/) [:STRING, scanner[1]] else s = scanner.getch [s, s] end end # p @tokens do_parse end def next_token @tokens.shift end ---- footer class StringScanner def collect_token tokens = [] until eos? token = yield(self) tokens << token if token end tokens end end
ファイル: list.rb
class List include Enumerable def initialize(car, cdr = nil) @car, @cdr = car, cdr end attr_accessor :car, :cdr def each list = self while list.is_a?(List) yield(list.car) list = list.cdr end yield(list) if list self end def inspect inject('(') do |s, v| s << ' ' if s.size > 1 s << v.inspect end + ')' end end
ファイル: main.rb
#!/usr/bin/env ruby require 'list' require 'list_parser' ListParser.new.parse(ARGF.read).each do |expr| p expr end
ファイル: sample1.scm
;; hello with name (define hello (lambda (name) (string-append "Hello " name "!"))) ;; sum of three numbers (define sum3 (lambda (a b c) (+ a b c))) #t
実行例:
> racc -o list_parser.rb list_parser.y > ruby main.rb sample1.scm (:define :hello (:lambda (:name) (:"string-append" "Hello " :name "!"))) (:define :sum3 (:lambda (:a :b :c) (:+ :a :b :c))) true