LLVMで作る日本語プログラミング言語

https://github.com/technohippy/Kaleidoscope.ja

以前から日本語プログラミング言語って作ってみたくて、でもただのトランスレーターならともかく、まっとうにコンパイル出来てネイティブでサクサク動くようなのはどうやって作ったらいいか分からないどころか、どの辺から勉強に手をつければいいのかすら分からなくて放置してたわけです。

ところがまぁ世の中良くしたもので、LLVMというものを使えばフロントエンドを作るだけで、ややこしいところは良しなに処理してくれるそうじゃないですか。しかも最近本が出たばかり。これが日本語の予約語とか関数名・変数名とかを扱えるなら、いろいろ捗りそう。ということで試してみました。

結論から言えば、タイトルのとおり、LLVMは日本語も問題なく使えるみたいです。

サンプル: カレイドスコープ

ということで、さっそく実際に動くものを作ります。

LLVMの本家ページのチュートリアルにKaleidoscopeという言語の実装例があります。KaleidoscopeはC言語に似た簡易言語で以下のようなことができます。

# Compute the x'th fibonacci number.
def fib(x)
  if x < 3 then
    1
  else
    fib(x-1)+fib(x-2)

# This expression will compute the 40th number.
fib(40)

http://llvm.org/docs/tutorial/LangImpl1.html#the-basic-language

これを日本語プログラミング言語にしてみることにしましょう。

まず、フロントエンドがC++だと日本語を扱うのが大変そうなので(というかそもそもC++知らないので)Rubyに移植します。

https://github.com/technohippy/Kaleidoscope.rb

面倒くさいので細かい説明はなし。C++チュートリアルをほぼ行単位でRubyにしてるだけなので、中身の詳細については本家チュートリアルを見てください。

この中のlangimpl6.rbに以下の改造を加えます。

  1. 日本語を識別子とみなすように
  2. 予約語を日本語に変更

ソースコードの関連する部分はこう。日本語識別子はすごく適当なのできっといろいろ漏れてます。

def isalpha c; isalnum(c) && !isdigit(c) end 
def isalnum c; c =~ /^[^\s();:,=#!<>+\/\*\-\|\&\.]$/ end
return TOK_DEF if $IdentifierStr == '次の関数を定義'
return TOK_EXTERN if $IdentifierStr == '次の関数を利用'
return TOK_IF if $IdentifierStr == 'もし' 
return TOK_THEN if $IdentifierStr == 'が真なら' 
return TOK_ELSE if $IdentifierStr == 'そうではなければ' 
return TOK_FOR if $IdentifierStr == '次の条件で' 
return TOK_IN if $IdentifierStr == '以下を繰り返す' 
return TOK_BINARY if $IdentifierStr == '二項演算子' 
return TOK_UNARY if $IdentifierStr == '単項演算子'

これだけでだいたい日本語プログラミング言語として動きました。簡単ですね。

サンプルのサンプル: マンデルブロ集合

先の日本語プログラミング言語が動作することを示すために、サンプルとしてマンデルブロ集合を表示してみようと思います。

ただ、いまのところ画面に表示するための命令がなにもありません。これだけは如何ともしがたいので、まずはprintdとputchardという組み込み関数的なものを使えるようにします。

まず、C++で件の関数を定義して

#include <cstdio>
  
//===----------------------------------------------------------------------===//
// "Library" functions that can be "extern'd" from user code.
//===----------------------------------------------------------------------===//
  
/// putchard - putchar that takes a double and returns 0.
extern "C"
double putchard(double X) {
  putchar((char)X);
  return 0;
} 
  
/// printd - printf that takes a double prints it as "%f\n", returning 0.
extern "C" 
double printd(double X) {
  printf("%f\n", X);
  return 0;
} 


ビットコードに変換した後で

$ clang++ -emit-llvm -S print.cpp -o print.ll
$ llvm-as print.ll


langimpl6.rb(改)内でデフォルトでそれらが使えるようにLLVM::Module.newではなく、LLVM::Module.parse_bitcodeを使うように変更します。

#$TheModule = LLVM::Module.new "my cool jit"
$TheModule = LLVM::Module.parse_bitcode 'print.bc'

Kaleidoscopeの改造はこれで終わりです。この上で動作するマンデルブロ集合を表示する日本語プログラム(抜粋)は次のような感じになります。(一番最後に全文を載せています)

# ..前略..

次の関数を定義 密度を表示する(密度)
  もし 密度 > 8 が真なら
    次の文字コードを持つ文字を表示する(32)  # ' '
  そうではなければ もし 密度 > 4 が真なら
    次の文字コードを持つ文字を表示する(46)  # '.'
  そうではなければ もし 密度 > 2 が真なら
    次の文字コードを持つ文字を表示する(43)  # '+'
  そうではなければ
    次の文字コードを持つ文字を表示する(42); # '*'

# ある位置が発散するかどうかを決める
次の関数を定義 発散するか?(実数 虚数 繰り返し数 実数初期値 虚数初期値)
  もし 繰り返し数 > 255 | (実数*実数 + 虚数*虚数 > 4) が真なら
    繰り返し数
  そうではなければ
    発散するか?(実数*実数 - 虚数*虚数 + 実数初期値,
                    2*実数*虚数 + 虚数初期値,
                    繰り返し数+1, 実数初期値, 虚数初期値);

# ..後略..

早速実行してみましょう。

ちょっとわかりにくいですが、マンデルブロ集合です。

ということで、LLVMを使った日本語プログラミング言語はたぶん実現可能ですよ、というお話でした。



# 論理否定 単項演算子
次の関数を定義 単項演算子!(値)
  もし 値 が真なら
    0
  そうではなければ
    1;

# 負 単項演算子
次の関数を定義 単項演算子-(値)
  0-値;

# > を < と同じ優先順位で定義
次の関数を定義 二項演算子> 10 (左辺値 右辺値)
  右辺値 < 左辺値;

# 論理和 二項演算子
次の関数を定義 二項演算子| 5 (左辺値 右辺値)
  もし 左辺値 が真なら
    1
  そうではなければ もし 右辺値 が真なら
    1
  そうではなければ
    0;

# 論理積 二項演算子
次の関数を定義 二項演算子& 6 (左辺値 右辺値)
  もし !左辺値 が真なら
    0
  そうではなければ
    !!右辺値;

# 同値比較
次の関数を定義 二項演算子 = 9 (左辺値 右辺値)
  !(左辺値 < 右辺値 | 左辺値 > 右辺値);

# 順次実行。右の値を返す
次の関数を定義 二項演算子 : 1 (式12) 式2;


次の関数を利用 putchard(char);
次の関数を定義 次の文字コードを持つ文字を表示する(文字コード)
  putchard(文字コード);

次の関数を定義 密度を表示する(密度)
  もし 密度 > 8 が真なら
    次の文字コードを持つ文字を表示する(32)  # ' '
  そうではなければ もし 密度 > 4 が真なら
    次の文字コードを持つ文字を表示する(46)  # '.'
  そうではなければ もし 密度 > 2 が真なら
    次の文字コードを持つ文字を表示する(43)  # '+'
  そうではなければ
    次の文字コードを持つ文字を表示する(42); # '*'

# ある位置が発散するかどうかを決める
次の関数を定義 発散するか?(実数 虚数 繰り返し数 実数初期値 虚数初期値)
  もし 繰り返し数 > 255 | (実数*実数 + 虚数*虚数 > 4) が真なら
    繰り返し数
  そうではなければ
    発散するか?(実数*実数 - 虚数*虚数 + 実数初期値,
                    2*実数*虚数 + 虚数初期値,
                    繰り返し数+1, 実数初期値, 虚数初期値);

# イテレーションを抜けるのに必要だった繰り返しの回数を返す
次の関数を定義 発散するかの確認を開始する(実数 虚数)
  発散するか?(実数, 虚数, 0, 実数, 虚数);

# 指定された2次元の範囲内でマンデルブロ集合を計算してプロットする
次の関数を定義 マンデルブロ(xの最小値 xの最大値 xのステップ   yの最小値 yの最大値 yのステップ)
  次の条件で y = yの最小値, y < yの最大値, yのステップ 以下を繰り返す (
    (次の条件で x = xの最小値, x < xの最大値, xのステップ 以下を繰り返す
       密度を表示する(発散するかの確認を開始する(x,y)))
    : 次の文字コードを持つ文字を表示する(10)
  )

# 指定した倍率で指定された位置のマンデルブロ集合をプロットする
次の関数を定義 マンデルブロ集合を表示する(開始実数値 開始虚数値 実数値の倍率 虚数値の倍率)
  マンデルブロ(開始実数値, 開始実数値+実数値の倍率*78, 実数値の倍率,
             開始虚数値, 開始虚数値+虚数値の倍率*40, 虚数値の倍率);

マンデルブロ集合を表示する(-2.3, -1.3, 0.05, 0.07);