lavats

lavats是一个由typescript写成的支持umd的DSL式词法分析器、语法分析器库。

Usage no npm install needed!

<script type="module">
  import lavats from 'https://cdn.skypack.dev/lavats';
</script>

README

lavats

lavats是一个由typescript写成的支持umdDSL式的词法分析器、语法分析器库。

特色

  • 支持typescript*.d.ts文件
  • 支持umd,可运行在浏览器nodejs环境下
  • 使用DSL式,可与js语言无缝衔接
  • 支持左递归循环

安装

在命令行中输入:

npm install lavats

引入

cmd

var lavats = require("lavats");

amd

require(["lavats"], function(lavats) {

})
define(["lavats"], function(lavats) {

})

es6

import * as lavats from "lavats";

<script>

<script src="./node_modules/lavats/dist/index.js"></script>
<script>

lavats

</script>

使用

此处介绍了一些常用的类型,其他的具体类型信息,可以查看包内的*.d.ts文件。

TerminalRule

用于生成终止符的规则

import { TerminalRule } from "lavats";
// new TerminalRule(options: TerminalOptions | RegExp | string);

// 可以接受正则表达式(不支持flags)
new TerminalRule(/[0-9]+/);

// 可以接受字符串
new TerminalRule("+");

// 可以接受TerminalOptions
// interface TerminalOptions {
//     reg: RegExp | string;        // 正则表达式或字符串
//     ignore?: boolean;            // 是否忽略,默认为false
//     ast?: typeof TerminalAst;    // 生成的ast类型,默认为TerminalAst
// }
new TerminalRule({ reg: /\s+/, ignore: true });

Lex

用于生成进行词法分析的Lex对象

  • 一般用例:
import { Lex, TerminalRule } from "lavats";

// new Lex(terminalRules: TerminalRule[])

let lex = new Lex([
    new TerminalRule(/[0-9]+/),
    new TerminalRule("+"),
    new TerminalRule("-"),
    new TerminalRule("*"),
    new TerminalRule("/"),
    new TerminalRule({ reg: /\s+/, ignore: true })
]);

let result = lex.match("100 * 100");

// interface LexResult {
//     ast: TerminalAst[];          // ast数组
//     errors: Token[];             // 未能识别的token
//     ignore: TerminalAst[];       // 被忽略的ast
// }

JSON.stringify(result); 
// =>
// '{
//     "ast": [
//         { "name": "TerminalAst", "reg": "/[0-9]+/", "token": { "content": "100", "start": { "row": 0, "col": 0 }, "end": { "row": 0, "col": 3 } } },
//         { "name": "TerminalAst", "reg": "/\\*/", "token": { "content": "*", "start": { "row": 0, "col": 4 }, "end": { "row": 0, "col": 5 } } },
//         { "name": "TerminalAst", "reg": "/[0-9]+/", "token": { "content": "100", "start": { "row": 0, "col": 6 }, "end": { "row": 0, "col": 9 } } }
//     ],
//     "errors": [],
//     "ignore": [
//         { "name": "TerminalAst", "reg": "/\\s+/", "token": { "content": " ", "start": { "row": 0, "col": 3 }, "end": { "row": 0, "col": 4 } } },
//         { "name": "TerminalAst", "reg": "/\\s+/", "token": { "content": " ", "start": { "row": 0, "col": 5 }, "end": { "row": 0, "col": 6 } } }
//     ]
// }'
  • 含有无法识别的字符时:
import { Lex, TerminalRule } from "lavats";

let lex = new Lex([
    new TerminalRule(/[0-9]+/),
    new TerminalRule("+"),
    new TerminalRule("-"),
    new TerminalRule("*"),
    new TerminalRule("/"),
    new TerminalRule({ reg: /\s+/, ignore: true })
]);

let result = lex.match("100.00 * 100");

JSON.stringify(result); 
// =>
// '{
//     "ast": [
//         { "name": "TerminalAst", "reg": "/[0-9]+/", "token": { "content": "100", "start": { "row": 0, "col": 0 }, "end": { "row": 0, "col": 3 } } },
//         // 此处多出了'00'
//         { "name": "TerminalAst", "reg": "/[0-9]+/", "token": { "content": "00", "start": { "row": 0, "col": 4 }, "end": { "row": 0, "col": 6 } } },
//         { "name": "TerminalAst", "reg": "/\\*/", "token": { "content": "*", "start": { "row": 0, "col": 7 }, "end": { "row": 0, "col": 8 } } },
//         { "name": "TerminalAst", "reg": "/[0-9]+/", "token": { "content": "100", "start": { "row": 0, "col": 9 }, "end": { "row": 0, "col": 12 } } }
//     ],
//     "errors": [
//         // 此处多出了'.'
//         { "content": ".", "start": { "row": 0, "col": 3 }, "end": { "row": 0, "col": 4 } }
//     ], "ignore": [
//         { "name": "TerminalAst", "reg": "/\\s+/", "token": { "content": " ", "start": { "row": 0, "col": 6 }, "end": { "row": 0, "col": 7 } } },
//         { "name": "TerminalAst", "reg": "/\\s+/", "token": { "content": " ", "start": { "row": 0, "col": 8 }, "end": { "row": 0, "col": 9 } } }
//     ]
// }'

StreamLex

用于生成进行词法分析的Lex对象,接受流式数据,以事件形式返回结果

import { StreamLex, TerminalRule } from "lavats";

// new StreamLex(rules: TerminalRule[])

let lex = new StreamLex([
    new TerminalRule(/[0-9]+/),
    new TerminalRule("+"),
    new TerminalRule("-"),
    new TerminalRule("*"),
    new TerminalRule("/"),
    new TerminalRule({ reg: /\s+/, ignore: true })
]);

lex.on("ast", ast => console.log("ast:\n" + JSON.stringify(ast, null, 4)));
lex.on("error", token => console.log("error:\n" + JSON.stringify(token, null, 4)));
lex.on("ignore", ast => console.log("ignore:\n" + JSON.stringify(ast, null, 4)));
lex.on("end", () => console.log("end"));

lex.match("100.00");
// ast:
// { "name": "TerminalAst", "reg": "/[0-9]+/", "token": { "content": "100", "start": { "row": 0, "col": 0 }, "end": { "row": 0, "col": 3 } } }

// error:
// { "content": ".", "start": { "row": 0, "col": 3 }, "end": { "row": 0, "col": 4 } }

// warning:此处并没有返回'00',因为并不知道'00'后面是否有其他的数字

lex.match(" *");
// ast:
// { "name": "TerminalAst", "reg": "/[0-9]+/", "token": { "content": "00", "start": { "row": 0, "col": 4 }, "end": { "row": 0, "col": 6 } } }

// ignore:
// { "name": "TerminalAst", "reg": "/\\s+/", "token": { "content": " ", "start": { "row": 0, "col": 6 }, "end": { "row": 0, "col": 7 } } }

lex.match(" 100");
// ast:
// { "name": "TerminalAst", "reg": "/\\*/", "token": { "content": "*", "start": { "row": 0, "col": 7 }, "end": { "row": 0, "col": 8 } } }

// ignore:
// { "name": "TerminalAst", "reg": "/\\s+/", "token": { "content": " ", "start": { "row": 0, "col": 8 }, "end": { "row": 0, "col": 9 } } }

// warning:此处也并没有返回'100',理由同上

lex.end();
// ast:
// { "name": "TerminalAst", "reg": "/[0-9]+/", "token": { "content": "100", "start": { "row": 0, "col": 9 }, "end": { "row": 0, "col": 12 } } }

// end
  • 另有reset方法,用于重置StreamLex对象
lex.reset();

RuleCollection

用于生成语法分析器的对象

import { RuleCollection, TerminalAst, DelayAst } from "lavats";

class Num extends TerminalAst {
    exec() {
        return +this.token.content;
    }
}

class Add extends TerminalAst {
    exec(left, right) {
        return left + right;
    }
}

class Sub extends TerminalAst {
    exec(left, right) {
        return left - right;
    }
}

class Mul extends TerminalAst {
    exec(left, right) {
        return left * right;
    }
}

class Div extends TerminalAst {
    exec(left, right) {
        return left / right;
    }
}

class Expr extends DelayAst {
    get left() {
        return this.children[0];
    }
    get operator() {
        return this.children[1];
    }
    get right() {
        return this.children[2];
    }
    exec() {
        return this.operator.exec(this.left.exec(), this.right.exec());
    }
}

let collection = new RuleCollection();

// 省略空格
collection.terminal({ reg: /\s+/, ignore: true });

// num => [0-9]+
let num = collection.terminal({
    reg: /[0-9]+/,
    ast: Num
});

// add => "+"
let add = collection.terminal({
    reg: "+",
    ast: Add
})

// sub => "-"
let sub = collection.terminal({
    reg: "-",
    ast: Sub
})

// mul => "*"
let mul = collection.terminal({
    reg: "*",
    ast: Mul
})

// div => "/"
let div = collection.terminal({
    reg: "/",
    ast: Div
})

// 无特殊Ast对象的规则可以直接定义中间变量使用
// operator => add | sub | mul | div
let operator = add.or(sub).or(mul).or(div);

// 非终结符规则的定义需要分开两步(先声明,后定义)
let expr = collection.delay(Expr);
// expr => num operator num
expr.define(num.and(operator).and(num));

// 获取语法分析器Parser
let parser = collection.getParser(expr);
let result = parser.match("100 * 100");

// interface ParserResult<T> {
//     ast?: T;                             // 生成的ast树,可通过判断是否为undefined,来判断是否成功
//     ignoreAst: TerminalAst[];            // 被跳过的ast
//     errorAst: Ast[];                     // 不符合语法规则的ast
//     errorToken: Token[];                 // 不符合词法规则的token
// }

console.log(JSON.stringify(result));
// {
//     "ast": {
//         "name": "Expr",
//         "children": [
//             { "name": "Num", "reg": "/[0-9]+/", "token": { "content": "100", "start": { "row": 0, "col": 0 }, "end": { "row": 0, "col": 3 } } },
//             { "name": "Mul", "reg": "/\\*/", "token": { "content": "*", "start": { "row": 0, "col": 4 }, "end": { "row": 0, "col": 5 } } },
//             { "name": "Num", "reg": "/[0-9]+/", "token": { "content": "100", "start": { "row": 0, "col": 6 }, "end": { "row": 0, "col": 9 } } }
//         ]
//     },
//     "ignoreAst": [
//         { "name": "TerminalAst", "reg": "/\\s+/", "token": { "content": " ", "start": { "row": 0, "col": 3 }, "end": { "row": 0, "col": 4 } } },
//         { "name": "TerminalAst", "reg": "/\\s+/", "token": { "content": " ", "start": { "row": 0, "col": 5 }, "end": { "row": 0, "col": 6 } } }
//     ],
//     "errorAst": [],
//     "errorToken": []
// }

let execResult = result.ast.exec();
console.log(execResult);        // 10000

另外有StreamParser,接受流式数据,以事件形式返回结果

let parser = collection.getStreamParser(expr);
parser.on("error", result => result);
parser.on("ignore", result => result);
parser.on("success", result => result);
parser.on("fail", result => result);
parser.on("end", result => {
    let execResult = result.ast.exec();
    console.log(execResult);
});

parser.match("100 + 100");
parser.end();
// 200

提供便捷方法,可使用插值字符串的方式,来定义规则。

// operator => add | sub | mul | div
// let operator = add.or(sub).or(mul).or(div);
// 可换写成:
let operator = Rule.bnf`${add} | ${sub} | ${mul} | ${div}`;

// expr => num operator num
// expr.define(num.and(operator).and(num));
// 可换写成:
expr.defineBnf`${num} ${operator} ${num}`;

StreamParser可通过reset方法重置

parser.reset();