読者です 読者をやめる 読者になる 読者になる

レガシーコード生産ガイド

私に教えられることなら

JavaScriptで字句解析

JavaScript

2週間でできる!スクリプト言語の作り方 という本を読みながら遊んでいる。 しばらくは使うプログラミング言語JavaScriptともう一つに絞りたいので、JavaScriptでやってみる。

正規表現を用いた簡単な字句解析器は次のようになった

var fs = require('fs');
var contents = fs.readFileSync(process.argv[2], 'utf-8');

var TOKENS = [
    "\\s+",                 // 空白
    "//.*$",                // コメント
    "\(-?[0-9]+\)",         // 1.数字リテラル
    "\(\\w[\\w|-]*\)",      // 2.識別子
    "\(\\\(\)",             // 3.開きカッコ
    "\(\\\)\)",             // 4.閉じカッコ
    "\(\\\[\)",             // 5.開きブラケット
    "\(\\\]\)",             // 6.閉じブラケット
    "\(\{\)",               // 7.開きブレース
    "\(\}\)",               // 8.閉じブレース
    "\(\\+|-|\\*|\\/|%\)",  // 9.二項演算子
    ""].join('|'),

    TOKEN_TYPES = {
        1: "NUMBER",
        2: "SYMBOL",
        3: "L-PAREN",
        4: "R-PAREN",
        5: "L-BRACKET",
        6: "R-BRAKCET",
        7: "L-BRACE",
        8: "R-BRACE",
        9: "BINOP"
    },
    tokens = []
;

// マッチ部分をトークンとして判定し追加する
function addToken (matched) {
    var i, type;
    if (!matched) { return; }
    
    for (i in TOKEN_TYPES) {
        if (!matched[i]) { continue; }
        
        tokens.push({
            token: matched[i],
            type:  TOKEN_TYPES[i]
        });
        break;
    }
}

function badToken (line_no, at, line) {
    throw "error: bad token at " + line_no + ":" + at + " -> " + line.slice(at);
}

contents.split('\n').forEach(function (line, line_no) {
    var TOKENS_re = new RegExp(TOKENS, "g"),
        start = 0, end = line.length, at,
        matched, error_at_start
    ;
    line_no += 1;

    while ( start < end ) {
        matched = TOKENS_re.exec(line);

        // 行のはじめにエラー
        error_at_start = !matched && start === 0;
        if (error_at_start) { badToken(line_no, 0); }

        // 行の途中にエラー
        if (!matched || TOKENS_re.lastIndex === start) {
            badToken(line_no, start, line);
        }

        addToken(matched);

        // 次のトークンの開始位置
        start = TOKENS_re.lastIndex;
    }
    
});

console.log(tokens);

以下は実行結果

phaendal $ cat test.script 
// comment
4
-5
     69 //comment2

5 + 8 * 4 - 1 / 7

(read-file-sync 4)

this-minuses-4-not-bin-op

[ ]
{ }

phaendal $ node lexer.js test.script 
[ { token: '4', type: 'NUMBER' },
  { token: '-5', type: 'NUMBER' },
  { token: '69', type: 'NUMBER' },
  { token: '5', type: 'NUMBER' },
  { token: '+', type: 'BINOP' },
  { token: '8', type: 'NUMBER' },
  { token: '*', type: 'BINOP' },
  { token: '4', type: 'NUMBER' },
  { token: '-', type: 'BINOP' },
  { token: '1', type: 'NUMBER' },
  { token: '/', type: 'BINOP' },
  { token: '7', type: 'NUMBER' },
  { token: '(', type: 'L-PAREN' },
  { token: 'read-file-sync', type: 'SYMBOL' },
  { token: '4', type: 'NUMBER' },
  { token: ')', type: 'R-PAREN' },
  { token: 'this-minuses-4-not-bin-op', type: 'SYMBOL' },
  { token: '[', type: 'L-BRACKET' },
  { token: ']', type: 'R-BRAKCET' },
  { token: '{', type: 'L-BRACE' },
  { token: '}', type: 'R-BRACE' } ]

Lisp風?のfunction-nameが好きなので、シンボルはその形式にしてみた。 でも演算子の処理でバグが出そうだからテストを書いておきたい。 それとトークンの種類と番号の対応はプログラムにやらせたほうが良さそうだ。 次は構文解析器なんだけど、ParserじゃなくてParserGeneratorを作りたくなったので挑戦中。結構難しそうだ。

広告を非表示にする