/** * @file SCL grammar for tree-sitter * @author Jacob Signorovitch * @license MIT */ /// // @ts-check // grammar.js const PREC = { assign: 0, // right-assoc add: 1, // left-assoc mul: 2, // left-assoc call: 3, funcdef: 4, lambda: 5, }; module.exports = grammar({ name: "scl", // Whitespace is of no consequence. extras: ($) => [/\s/], conflicts: ($) => [[$.params, $._exp]], rules: { // A file is zero or more expressions. source_file: ($) => repeat($._exp), // Everything is expressions. Makes this a lot easier. _exp: ($) => choice( $.num, $.word, $.binexp, $.callexp, $.funcdef, $.lambda, $.vardef, $.parexp, ), // Literals / identifiers. num: (_) => /\d+/, word: (_) => /[a-zA-Z_]\w*/, // Binary expressions with precedence and left associativity. binexp: ($) => choice( prec.left( PREC.add, seq( field("left", $._exp), field("op", choice("+", "-")), field("right", $._exp), ), ), prec.left( PREC.mul, seq( field("left", $._exp), field("op", choice("*", "/")), field("right", $._exp), ), ), ), // Function call: prefer this over plain `word` via precedence. callexp: ($) => prec( PREC.call, seq(field("fn", $.word), "(", optional(commaSep($._exp)), ")"), ), // Convenient function definition (sugar): `f(x, y, ...) body`. // Give it higher precedence than calls to resolve the shared prefix. funcdef: ($) => prec( PREC.funcdef, seq( field("name", $.word), field("params", $.params), field("body", $._exp), ), ), // Lambda: `\(x, y, ...) body`. lambda: ($) => prec( PREC.lambda, seq("\\", field("params", $.params), field("body", $._exp)), ), // Variable definition / assignment: `x = expr`. // Lowest precedence, right-associative: `a = b = c` → `a = (b = c)`. vardef: ($) => prec.right( PREC.assign, seq(field("name", $.word), "=", field("value", $._exp)), ), // Parameter list node used by funcdef and lambda. params: ($) => seq("(", optional(commaSep($.word)), ")"), // Parenthesized expression. parexp: ($) => seq("(", $._exp, ")"), }, }); function commaSep(rule) { return seq(rule, repeat(seq(",", rule))); }