Skip to content

Instantly share code, notes, and snippets.

@kmizu
Last active April 30, 2023 13:13
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kmizu/dbb12b7be5c7b0339480595dbba1196a to your computer and use it in GitHub Desktop.
Save kmizu/dbb12b7be5c7b0339480595dbba1196a to your computer and use it in GitHub Desktop.
Blawnの文法について

ソースコード読んだり、実際に処理系を動かしてみたメモを残しておきます。内容は当然ながら無保証です。 基本的に難しいことはやっていないので、ソースを読めばだいたいわかります。

処理系

C++で書かれている。

字句解析(flex) → 構文解析(bison)→ コード生成(LLVM IRを生成)

って感じで特に変なことはしていない。LLVM IR使っているというのが今どきぽいけど。

基本

インデントは意味を持たない。ただ単に、 return がメソッド定義/関数定義の終端子になっているだけ。

function add1(x)
  x = x + 1 // 字下げしなくても同じ意味
  return x // returnが来たら定義の終わり

class Person(name)
  @name = name
  @function speak()
    print("Hello, ")
    print(self.name)
    return // returnが来たら定義の終わり

変数宣言と代入

最初に現れた代入が宣言を兼ねる。

x = 1 // これが宣言を兼ねる
x = x + 1 // インクリメント
x = "Foo" // コンパイルエラー

この仕様のため、

if 1 < 2
(
  i = 1
)
else
(
  i = 2
)
print(int_to_str(i))

というコードは、コンパイルを通らない。

i = 0
if 1 < 2
(
  i = 1
)
else
(
  i = 2
)
print(int_to_str(i))

のように、ifの外のスコープで宣言してあげる必要がある。この辺、明示的な宣言もないと不便。この、最初の代入が宣言を兼ねて、型もそれになるという方式は、大昔作ったOnionもそうだったけど、それだと困るケースがあったので、明示的な宣言も可能にしてあった。

if

if 条件式 <改行>
(<改行>
  ...
)<改行>
else<改行>
(<改行>
  ...
)<改行>

のような形。ここで、<改行>と書いてある全ての箇所に必ず改行を入れなければいけない(elseの場合も同様)点に注意。

while

トークンはあるけど、そもそも実装されてない。試しに実装してみると、こんな感じ

i = 0
while i < 10
(
  i = i + 1
)
print(int_to_str(i)) // 10

for

Cの for と見た目が違うだけで同じ。ifと同様に、区切りに全部改行を入れるのを忘れずに。

for 初期化式 "," 条件式 "," インクリメント <改行>
( <改行>
  ...
)  <改行>

関数定義

functionキーワードで始まる(メソッドは @function)。最初に書いたように、return文で、構文的に終端するようになっている。

function id(x)
  return x

クラス定義

class キーワードに続けて、クラス名、コンストラクタ引数、インスタンス変数の定義、メソッド定義の順番で書く。

class クラス名 "(" 引数 ... ")" <改行>
  @インスタンス変数名 "=" 式
  ...
  @function メソッド名 "(" 引数 .. ")" <改行>
    ...
    return (式)?

コンストラクタ引数が空だったり、インスタンス変数の定義が空だったりすると構文エラー。無理やり、空を許すように構文をいじくってみたら プログラムが落ちたので、LLVM IRの生成方法に問題があるのかも。

class Person(name, age)
  @name = name
  @age = age
  @function print_age()
    print(int_to_str(self.age))
    return
  @function print_name()
    print(self.name)
    return

インスタンス生成は、クラス名に () をつけるだけ。この部分と、selfが使われているところだけはPythonぽいけど、それ以外は ほとんど似ていない(似せる意図があったとしても)。

p = Person("Hoge", 15) // インスタンス生成
p.print_age()
p.print_name()

LLVMのAPIから型を取ってきている(より正確には、LLVMのノードから型を取得している。自前で型推論とかはしていない。 動的型ではなくて、ちゃんと静的な型がある。間違うと、LLVMレベルの型エラーが出る(型チェック自体はほとんど自前でやっていないため)。

その他

落とし穴とか。

再帰関数

そもそも定義できない。たとえば、以下の階乗を計算する関数は、 n が見つからないというコンパイルエラー。

function fact(n)
  return if n < 1
  (
    1
  )
  else
  (
    n * fact(n - 1)
  )

ちなみに、returnif式の値を返そうとすると、実行時にSEGVで落ちる。

returnの妙な挙動

以下のようなコードを実行すると、SEGVになる。たぶん、if が returnの位置に来たときに正しいコードが吐かれていない。

function lt(m, n)
  return if m < n
  (
    1
  )
  else
  (
    0
  )

というか、以下の例もエラーになるので、ifを式として使うと、つまり、値を返すような使い方をするときは一般に正しい動作をしない 可能性が高い。文法上は式として(値を返すものとして)扱えるのだけど、たぶん、それ以降の部分がそう扱うと不具合が出る状態な気がする。

k = if 2 < 3
(
  1
)
else
(
  2
)
print(int_to_str(k))

制御構造のネスト

forの中にifをネストすると変な挙動をする(パーザのバグではない)。

for i = 0, i < 10, i = i + 1
(
  if i < 5
  (
    print("i < 5")
  )
  else
  (
    print("i >= 5")
  )
)

は、"i < 5" を一回だけ出力して終了してしまう。forの中のifが外側にジャンプするようなコードが吐かれてしまってる気がする。

標準関数

builtins.cpp で登録している。

  • print()
  • int_to_str()
  • float_to_str()
  • to_char_ptr()
  • append_string()

などがある。実態はこの辺: https://github.com/Naotonosato/Blawn/blob/master/src/compiler/builtins/builtins.c

演算子

  • 四則演算: +, -, *, /
  • 論理和、論理積: and, or
  • 比較: <, <=, >, >=
  • 等しい: ==, !=
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment