115 lines
2.6 KiB
JavaScript
115 lines
2.6 KiB
JavaScript
/**
|
|
* @file SCL grammar for tree-sitter
|
|
* @author Jacob Signorovitch <jacob.signorovitch@gmail.com>
|
|
* @license MIT
|
|
*/
|
|
|
|
/// <reference types="tree-sitter-cli/dsl" />
|
|
// @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)));
|
|
}
|