LoginSignup
16
14

More than 5 years have passed since last update.

Raccでプチ言語自作した

Last updated at Posted at 2016-02-07

注意

実用性は皆無で勉強用です。私メモ用の記事になります。

経緯

mruby等をみてて、言語を作るのってどうしてるんだろうってのに興味を持ったので、さわりだけ理解してみたいと想い、字句解析器、構文解析器を作ってみようと思い立ちました。mrubyはbisonを使っていますが、c言語ベースなので私には敷居が高いツールでした。raccはrubyベースのyaccのようなものですので、まだ敷居が低いかなと。

参考資料

mruby (on github): 言わずと知れたrubyの組み込み言語。mrbgemsとしてmruby-compilarというのがあり、その中に.yファイルがあります。
streem (on github): matzが開発している新しい思想のプログラミング言語。
.yファイルが比較的小さいので、基礎的な構文の理解の助けにもなるのではと思って参考にしました。
Raccの使い方
Ruby: Racc

プチ言語仕様

commandという名前の関数を指定できる。指定した引数を表示するのみ。

  • 引数内で指定される数値は整数として生成される。
  • 文字列はダブルクオテーションをつけるつけない関係なく文字列として処理される。
  • 括弧をつけると、バイナリ文字列として生成される。
  • コメントは一行コメントのみ。#を先頭につけることでそれ以降行の終端までコメントとなる。
command.txt
# aaa
command(1,any_val1,3,4,5,
  "aa bb cc",
  (ee ff),
  1
)

# bbb
command(2,any_val2,1,1,1,1,
  "aa bb cc dd",
  (ee ff),
  1
)

出力

command {
  1
  "any_comment1"
  3
  4
  5
  "aa bb cc dd"
  "\xEE\xFF"
  1
}
command {
  1
  "any_comment2"
  1
  1
  1
  1
  "aa bb cc dd"
  "\xEE\xFF"
  1
}

*.y

command.rb に command関数の定義を記述してあり、*.yファイル内のstmt で command.rb をロードして、command関数に引数を渡しているだけです。

lex.rb.y
class MyLexParser
rule
    all_stmt : stmt stmt
             | stmt
    stmt    : cmd left args right
    {
      load("#{val[0]}.rb")
      self.send(val[0].to_sym, val[2])
    }
    cmd     : IDENT
    number  : DEC            { result = val[0].to_i }
    left    : '('
    right   : ')'
    ids     : ids IDENT      { result = val[0]+val[1] }
            | IDENT
    args    : args ',' args  { result = [val[0], val[2]].flatten }
            | left ids right { result = [val[1]].pack("H*") }
            | number         { result = [val[0]] }
            | STRING         { result = [val[0]] }
            | IDENT          { result = [val[0]] }
  end

---- header
require 'pp'
require 'strscan'

---- inner
def parse(str)
  s = StringScanner.new(str)
  @q = []

  until s.eos?
    s.scan(/#[^\n]*\n/) ? nil  :
    s.scan(/\d+/)       ? @q << [:DEC, s.matched]   :
    s.scan(/\s+/)       ? nil  :
    s.scan(/,/)         ? @q << [',', s.matched]    :
    s.scan(/\"([^\\\"]|\\.)*\"/) ? @q << [:STRING, s.matched[1..-2]]    :
    s.scan(/[a-zA-Z0-9\-\_]+/) ? @q << [:IDENT, s.matched] :
    s.scan(/\(/)        ? @q << ['(', s.matched]    :
    s.scan(/\)/)        ? @q << [')', s.matched]    :
                          (raise "scanner error")
  end

  do_parse
end

def next_token
  @q.shift
end

---- footer
if __FILE__ == $0
  parser = MyLexParser.new

  begin
    parser.parse(File.read("command.txt"))
  rescue Racc::ParseError => e
    $stderr.puts e
  end
end
command.rb
def command(vals)
  puts "command {"
  vals.each do |line|
    if line.class == String
      puts "  " + line.inspect
    else
      puts "  #{line}"
    end
  end
  puts "}"
end

実行

$ racc -g -o out.rb lex.rb.y
$ ruby out.rb
16
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
14