🐥

Forth(Gforth)で簡単な自作言語のコンパイラを書いた

2023/06/11に公開2

かんたんな自作言語のコンパイラをいろんな言語で書いてみるシリーズ 23番目の言語は Forth です。これで 冬来たる-ready [1] になりました。世界が崩壊して Forth しか使えなくなっても自作言語で書いたプログラムをコンパイルして遊ぶことができますね!

(他所に書いていたものを引っ越してきました。元の公開日は 2023-03-12 です。)

できたもの

https://github.com/sonota88/mini-ruccola-forth

動かし方の例

$ echo '
  func add(a, b) {
    return a + b;
  }

  func main() {
    call add(1, 2);
  }
' | gforth mrcl_lexer.fs | gforth mrcl_parser.fs | gforth mrcl_codegen.fs


# ↓アセンブリが出力される

  call main
  exit
label add
  push bp
  cp sp bp
  cp [bp:2] reg_a
  push reg_a
  cp [bp:3] reg_a
  push reg_a
  pop reg_b
  pop reg_a
  add_ab
  cp bp sp
  pop bp
  ret
label main
  push bp
  cp sp bp
  cp 2 reg_a
  push reg_a
  cp 1 reg_a
  push reg_a
  _cmt call~~add
  call add
  add_sp 2
  cp bp sp
  pop bp
  ret

# ... snip ...

移植元

https://github.com/sonota88/vm2gol-v2

<自作言語処理系(Ruby版)の説明用テンプレ>

自分がコンパイラ実装に入門するために作った素朴なトイ言語 Mini Ruccola とその処理系です。簡単に概要を書くと下記のような感じ。

<説明用テンプレおわり>

メモ

Forth というかスタック指向の言語でまともにプログラミングするのはほぼ今回初めて(逆ポーランド記法自体は知識としては知っていて、BibTeX とか PostScript で昔ちょっと触れたことがある、という程度)。だいぶ手探り感が強かったです。

基本的には以下の2つを見ながら書きました。

一番最初は Starting FORTH を読んで基本を知るのがおすすめ。私は Gforth Manual のチュートリアルセクションに一通り目を通したあと Starting FORTH を読み「こっちを先に読んどけばよかった……」となりました。


こちらの解説動画も参考になりました。英語だと Gforth の解説もいろいろありますね。


https://www.youtube.com/playlist?list=PLGY0au-Sczllm2NcYC0Ypk2CYcFAadYZa


Thinking Forth も参考になりました。文化を知れて興味深いです。翻訳ありがとうございます。


https://thinking-forth-ja.readthedocs.io/ja/latest/


https://qiita.com/kuma35/items/53c9f5dea46c5ba59fe6

困ったところ

日本語情報が少ない

誰でも知っているメジャーな言語ではないとはいえそこそこ歴史のある言語なので日本語でも蓄積があるのでは……という予想に反して、少なくとも Gforth に関してはちょっとググればOKという感じではないですね。

スタックの状態が分からない

読み書きに多少慣れてきたかなと思えてきた段階でも分からない。仕方なく以下の例のように逐一コメントで書くようにしてみました。今回はこのコメントなしでもスラスラ読めるという境地には至れず、そのまま残しています。自分が分かればいいやという程度の適当なもの。

: read-stdin-all ( -- src_ size )
    here
    \ src_
    100000 chars allot

    dup
    \ src_ src_
    \ src_ src_current_

    begin
        read-char
        \ src_ src_cur_ | char num-read
        0 = if
            \ src_ src_cur_ char
            drop
            \ src_ src_cur_

            dup
            \ src_ src_cur_ | src_cur_
            2 pick
            \ src_ src_cur_ | src_cur_ src_
            -
            \ src_ src_cur_ | size

            drop-1
            \ src_ size

            exit
        endif

        \ src_ src_cur_ | char
        1 pick
        \ src_ src_cur_ | char src_cur_
        c!
        \ src_ src_cur_

        1 +
        \ src_ src_next_
    again
;

○○とか△△とかやりたいんだけどスタック操作だけだと無理じゃない? 一体どうやって書くの??

連続したメモリ領域の読み書きができるので、それを使えばOK。


https://gforth.org/manual/Memory-Tutorial.html


https://gforth.org/manual/Memory.html

あと、メインのスタック(data stack)とは別にリターンスタックというスタックがあり、それを補助的に使うこともできる。


https://gforth.org/manual/Return-Stack-Tutorial.html

なるほどと思ったところ

文字列

C のような NUL終端文字列ではなく、先頭アドレスと文字数をセットにして取り回す慣習(?)らしいです(標準的なワードのインターフェイスがそうなっている)。

馴染みがない方式で最初はちょっと戸惑いましたが、文字列とその部分文字列を区別せずに扱えると気付いてなるほどと思いました。


https://gforth.org/manual/Characters-and-Strings-Tutorial.html

多値が返せる

多値を返すしくみがわざわざ用意されているわけではなく、単にスタックに好きな数の値を残せばよい。「関数から値と処理の成否を分けて返す」みたいなことが普通にできる。

たとえば Gforth の組み込みワード s>number? に文字列を渡すと数値(パース結果)とフラグ(成否を表す)の2つをスタックに残す、といった感じ。

https://gforth.org/manual/Line-input-and-conversion.html#index-s_003enumber_003f_0028--addr-u-_002d_002d-d-f--_0029-gforth_002d0_002e5

脚注
  1. Collapse OS ネタ。元はゲーム・オブ・スローンズですね。 ↩︎

Discussion

nfunatonfunato

Forthに関するサイトとしては、Leo Wongさんのサイトも "らしい” 内容で お勧めかもしれません。
http://murphywong.net/hello/forth.htm
(これがhttpsじゃないんだよなぁ...)

murphywong.net/hello/….htm に色々な記事があり(simple,comus,inching,01234th,stacks,...)、たいてい上のforth.htmから辿れると思います。

sonota88sonota88

おすすめありがとうございます。土地勘がないので助かります。
まださらっと眺めた程度ですが、「ちょうどこういうのが欲しかった」というトピックが並んでいて、これを読んでいればもうちょっとスムーズだったかもな〜と思いました。