こないだつくった構文解析器をちょっと改良、というか修正して、入力データをコマンド形式にしてみた。
コマンド形式、っていうのは、入力データ(の内部表現)を作るための一種の DSL だ。代入も制御構造もないシェルスクリプトみたいなものと考えてくれればいい。コマンド形式にしておけば、あとから機能追加によって入力データの項目が増えたとしても対応するコマンドを追加するだけ済み、パーサ(構文解析器)を修正する必要がない。
入力データの見た目はほとんど変わらなくて、* がなくなっただけ。これは、こないだのはデータの種類を表すラベルだったのに対して、今回のはコマンド名を表すからそれらしくした。
// Example for input data. MODEL RO GAMMA0.5 // % 0.150 HMAX 0.200 PLOT // % 0.0001 0.0002 0.0005 0.001 0.002 0.005 0.01 0.02 0.05 0.1 0.2 0.5 1.0 2.0 5.0 10.0 END
各コマンドは、コマンド名(行の先頭から始まる必要がある)と 0 個以上の引数からなり、改行で終わる。空白文字で始まる行は前の行の続きとみなすのと// から行末までをコメントとするのもこないだと同じ。
文法定義ファイルはこうなった。
?script : statement+
statement : command arg* "\n"
command : /[A-Z0-9.]+/
arg : string
| real
string : UCASE_LETTER+
real : FLOAT
%import common.UCASE_LETTER
%import common.FLOAT
%import common.WS_INLINE
%ignore WS_INLINE
コメントや継続行の処理も文法でできるようだけど、今回はパースする前の前処理でやってしまうことにした。そのほうが文法が楽だったから。
さて、Lark には lark.Interpreter というクラスが用意されている。このクラスの visit() メソッドを使うと、パースした木構造を他のデータ構造に変換する代わりに、ノードをたどりながら処理を実行できる。今回はコマンド名と引数リストを出力してるだけだけど、これでデータを構築する処理を書けばいいわけだ。
from lark import Lark
from lark.visitors import Interpreter
import sys
import re
from pprint import pprint
class MyInterpreter(Interpreter):
def script(self, tree):
print("SCRIPT")
for c in tree.children:
self.visit(c)
def statement(self, tree):
cmd = self.visit(tree.children[0])
args = [ self.visit(a) for a in tree.children[1:] ]
print(" STATEMENT")
print(" COMMAND: " + cmd)
print(" ARGS: " + str(args))
def command(self, tree):
return tree.children[0]
def arg(self, tree):
return self.visit(tree.children[0])
def string(self, tree):
return "".join(tree.children)
def real(self, tree):
return float(tree.children[0].value)
def contract(text):
lines = text.splitlines(keepends=False)
lines_new = []
for line in lines:
line = re.sub(r"//.*$", "", line)
if len(line) == 0:
pass
elif line.startswith(" "):
lines_new[-1] += line
else:
lines_new.append(line)
return "\n".join(lines_new) + "\n"
with open("larksample2.lark", "r", encoding="utf-8") as grammar:
parser = Lark(grammar.read(), start="script")
with open(sys.argv[1], "r") as f:
input_data = contract(f.read())
tree = parser.parse(input_data)
MyInterpreter().visit(tree)
実行するとこうなった。
takatoh@apostrophe:larksample$ python larksample2.py larksample2.dat
SCRIPT
STATEMENT
COMMAND: MODEL
ARGS: ['RO']
STATEMENT
COMMAND: GAMMA0.5
ARGS: [0.15]
STATEMENT
COMMAND: HMAX
ARGS: [0.2]
STATEMENT
COMMAND: PLOT
ARGS: [0.0001, 0.0002, 0.0005, 0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1.0, 2.0, 5.0, 10.0]
STATEMENT
COMMAND: END
ARGS: []