Off-side rule と言うのは,
{
とか }
とかを使わずに,
インデント(空白)のレベルでどこまでがブロックかを決めるやつ.
Python とか Haskell とかが採用している.
Python だと, こんな感じで書くよね.
def hello():
print("Hello world")
hello()
こう言う書き方はしない.
def hello(): {
print("Hello world")
}
hello()
けど,Parsing するときは事前に, Lexing の段階(文字列をトークンの列に分解する)で, 下の方の構文に変換する感じになる.
より具体的には,
インデントのレベルに合わせて,
{
に対応するトークン (INDENT
) と,
}
に対応するトークン (DEDENT
)
を挟む(まぁ何でも良いけど).
今回の実装だと ;
に対応するトークン (DELIMITER
) も挟んでいる.
上記の例だと, Lexing 後にこんな感じになるようにする.
DEF
VAR("hello")
(
)
:
INDENT
VAR("print")
(
STRING("Hello world")
)
DELIMITER
DEDENT
DELIMITER
VAR("print")
(
STRING("Hello world")
)
そうすれば,普通の parsing ができる.
問題は,どうやって INDENT
, DEDENT
を挟むか
ちょっとややこしい.
「indent が空白何個分か」 を indent level と言うことにする.
今までの indent level を stack にして持っておく.
最初は 0 を入れておく.
つまり,初期状態で,stack の top の indent level は [0]
.
現在の indent level が,この stack の一番上に入っている indent level と同じなら
前の行と同じブロック内だと言うことなので, 特に何もしない.
例えば,上記のコードを解析しているとする.
def hello(): # [0]
この 1 行目の indent level は 0 で, stack の top の indent level (= 0) と同じなので, 1 行目では,特に何もしない.
現在の indent level が,この stack の一番上に入っている indent level よりも大きいなら
新しいブロックが始まったと言うことなので,
INDENT
token を挟み,stack に indent level を push する.
例えば, 上記のコードを解析しているとする.
def hello(): # [0]
print("Hello world") # [2, 0]
この 2 行目の indent level は 2 なので,
print("Hello...
の Lexing に入る前に,
INDENT
トークンを挟む.
また,stack に indent level = 2 を push して,
stack は [2, 0]
になる.
現在の indent level が,この stack の一番上に入っている indent level よりも小さいなら
ブロック終わったと言うことなので,
DENDENT
token を挟む.stack から n > 1 回 pop して, stack の top の indent level が, 今の indent level と等しくなるようにする. うまく等しくなるようにできたら,
DEDENT
token を n 個挟む.等しくなるようにできないなら, エラーを吐く.
例えば, 上記のコードを解析しているとする.
def hello(): # [0]
print("Hello world") # [2, 0]
hello() # [0]
この 4 行目の indent level は 0 なので,
stack の top の index level (= 2) とは異なる.
従って,
print()
の Lexing に入る前に,
stack を pop する必要がある.
幸いなことに,一回 pop すれば, stack の top の indent level を 0 にすることができる.
よって,
DEDENT
トークンを一個挟めば良い.
また,stack に indent level = 2 を push して,
stack は [2, 0]
になる.
DEDENT
token を挟むのに失敗する例
例えば, 上記のコードを解析しているとする.
def hello(): # [0]
print("Hello world") # [2, 0]
hello() # <-- error
ちょっとわかりづらいが, この 4 行目の indent level は 1 なので, stack の top の indent level (= 2) とは異なる.
従って, stack を pop するわけだが, pop した後の top の indent level は 0 なので, どう足掻いても等しくできない.
なので,これはエラーを吐く.
で,実際に OCaml でどうやって実装するか.
OCamlLex で,今まで述べたアルゴリズムを実装してみる.
ただ,ちょっとした問題があって,
OCamlLex は(何故か)一度に複数の token を返せない.
しかし,DEDENT
token は n 個挟まなきゃいけなかったので,
このままでは困る.
なので,工夫する.