Another one.

This commit is contained in:
2025-08-27 01:29:54 -04:00
parent 7cad33405e
commit 542d36f266
4 changed files with 4254 additions and 1008 deletions

View File

@@ -7,78 +7,64 @@
/// <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,
$.bool,
$.word,
$.binexp,
$.callexp,
$.funcdef,
$.lambda,
$.vardef,
$.parexp,
$.block,
$.vardef,
$.lambda,
$.funcdef,
$.ifexp,
),
// Literals / identifiers.
num: (_) => /\d+/,
bool: (_) => choice("TRUE", "T", "FALSE", "F"),
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),
),
prec.left(
1,
seq(
field("left", $._exp),
field("op", choice("+", "-", "*", "/", "eq")),
field("right", $._exp),
),
),
// Function call: prefer this over plain `word` via precedence.
callexp: ($) =>
prec(
PREC.call,
prec.left(
1, // lower than funcdef
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.
parexp: ($) => seq("(", $._exp, ")"),
block: ($) => seq("{", repeat($._exp), "}"),
vardef: ($) =>
prec.right(1, seq(field("name", $.word), "=", field("value", $._exp))),
lambda: ($) =>
prec.right(
2,
seq("\\", field("params", $.params), field("body", $._exp)),
),
funcdef: ($) =>
prec(
PREC.funcdef,
prec.right(
3, // higher precedence than call
seq(
field("name", $.word),
field("params", $.params),
@@ -86,26 +72,33 @@ module.exports = grammar({
),
),
// 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, ")"),
ifexp: ($) =>
choice(
seq(
"_if",
"(",
field("cond", $._exp),
",",
field("then", $._exp),
",",
field("else", $._exp),
")",
),
seq(
"if",
field("cond", $._exp),
field("then", $._exp),
choice(seq("else", field("else", $._exp)), field("else", $._exp)),
),
seq(
"?",
field("cond", $._exp),
field("then", $._exp),
field("else", $._exp),
),
),
},
});