dsl-parser

A simple lexer and bottom-up parser for creating domain specific languages

Usage no npm install needed!

<script type="module">
  import dslParser from 'https://cdn.skypack.dev/dsl-parser';
</script>

README

The example below shows a simple domain specific language for arithmetic.

import { Lexer, LexTree, Terminal } from "./Lexer";
import { Parser, ParseTree, RuleSet } from "./Parser";
import { skipOneChild, Visitor } from "./Visitor";

const terminals: Terminal[] = [
    // Terminals are matched by their pattern (in order)
    { type: "number", pattern: /[0-9]+/ },
    { type: "add", pattern: /[+-]/ },
    { type: "mul", pattern: /[*\/]/ },
    // Terminals with type 'whitespace' are ignored
    { type: "whitespace", pattern: /\s+/ }
];

const rules: RuleSet = {
    root: [
        // The root can expand to an addExpr
        ["addExpr"]
    ],
    addExpr: [
        // An addExpr can expand to an addExpr + a mulExpr
        ["addExpr", "add", "mulExpr"],
        // Or just a mulExpr
        ["mulExpr"]
    ],
    mulExpr: [
        // An addExpr can expand to a mulExpr * a number
        ["mulExpr", "mul", "number"],
        // Or just a number
        ["number"]
    ]
};

class ArithVisitor extends Visitor<number, void> {
    @skipOneChild
    public visit_addExpr(state: void, tree: ParseTree): number {
        // Compute the lhs and rhs
        const lhs = this.visit(state, tree.children[0]);
        const rhs = this.visit(state, tree.children[2]);

        switch ((tree.children[1] as LexTree).match[0]) {
            case "+":
                return lhs + rhs;

            case "-":
                return lhs - rhs;
        }

        throw new Error("Unreachable");
    }

    @skipOneChild
    public visit_mulExpr(state: void, tree: ParseTree): number {
        // Compute the lhs and rhs
        const lhs = this.visit(state, tree.children[0]);
        const rhs = this.visit(state, tree.children[tree.children.length - 1]);

        switch ((tree.children[1] as LexTree).match[0]) {
            case "*":
                return lhs * rhs;

            case "/":
                return lhs / rhs;
        }

        throw new Error("Unreachable");
    }

    public visit_number(state: void, tree: LexTree): number {
        return parseFloat(tree.match[0]);
    }
}

const lexer = new Lexer(terminals);
const parser = new Parser(rules, "root");
const visitor = new ArithVisitor();

const parseTree = parser.parse(lexer, Lexer.split("3 + 2 * 1"));
console.log(visitor.visit(undefined, parseTree));