Chapter 08

字句解析

takl
takl
2020.12.22に更新
このチャプターの目次

いよいよ中身に入ります。

まずは字句解析です。ソースコードが (defun f () 123) だった場合、 (, defun, f, (, ), 123, ) のように分けるやつです。言語処理系を作るような方にはいつものやつだと思うので詳細は省きますが、注意事項が2点あります。

まずコメントを捨ててはいけません。コメントに色をつけるのに必要になるからです。

次に位置情報をトークンに残しておきましょう。定義へジャンプする機能を作ったりする際に必要になります。位置情報は Language Server Protocol の Location に合わせておくと後で何かと便利です。

というわけで 123 ; hoge を字句解析すると結果は次のようになります。

[
    {
        "kind": "number",
        "text": "123",
        "location": {
            "uri": "/somewhere/test.ore",
            "range": {
                "start": {
                    "line": 0,
                    "character": 0
                },
                "end": {
                    "line": 0,
                    "character": 3
                }
            }
        }
    },
    {
        "kind": "comment",
        "text": "; hoge",
        "location": {
            "uri": "/somewhere/test.ore",
            "range": {
                "start": {
                    "line": 0,
                    "character": 4
                },
                "end": {
                    "line": 0,
                    "character": 10
                }
            }
        }
    }
]

位置情報に関して注意なのですが、行番号(line)と桁番号(character)は共に0はじまりです。また、桁番号は utf-16 での offset です。(なので string が utf-16 でない言語で処理系を書いていると少々面倒なことになります。)

kind はトークンの種類で、今回の言語仕様では ()commentnumbervariable の5種類です。

text はトークンの文字列そのままを保持します。

コード

字句解析は tokenize という関数でやることにします。この字句解析の結果ですが、開いているファイル毎に保存する必要があります。ですので buffers というグローバル変数を用意して、uri の結果は buffers[uri] に保存していく、というようにします。また、診断メッセージは様々なところで発生するので、diagnostics もグローバル変数として持つことにします。sendPublishDiagnostics()compile() 中に行うとバッチコンパイラを実装するときに妨げになるので、これは外に出します。

というわけで関連コードはこんな感じになります。

oreore.js
const buffers = {};
const diagnostics = [];
oreore.js
function compile(uri, src) {
    diagnostics.length = 0;
    const tokens = tokenize(uri, src);
    buffers[uri] = { tokens };
}

notificationTable["textDocument/didOpen"] = (msg) => {
    const uri = msg.params.textDocument.uri;
    const text = msg.params.textDocument.text;
    compile(uri, text);
    sendPublishDiagnostics(uri, diagnostics);
}

notificationTable["textDocument/didChange"] = (msg) => {
    if (msg.params.contentChanges.length !== 0) {
        const uri = msg.params.textDocument.uri;
        const text = msg.params.contentChanges[msg.params.contentChanges.length - 1].text;
        compile(uri, text);
        sendPublishDiagnostics(uri, diagnostics);
    }
}

tokenizeここです。べた書きですが怪しいことはしていないはずです。

* ここまでのソースコード *