Files
kerolox/tree-sitter-kerolox/grammar.js

173 lines
4.8 KiB
JavaScript

/**
* @file Kerolox grammar for tree-sitter
* @license AGPL-3.0-or-later
*
* Copyright (C) 2025-2026 Marceline Cramer
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This file is part of Kerolox.
*
* Kerolox is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* Kerolox is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Kerolox. If not, see <https://www.gnu.org/licenses/>.
*/
/// <reference types="tree-sitter-cli/dsl" />
// @ts-check
/// A comma-separated list.
const list = el => optional(seq(el, repeat(seq(",", el)), optional(",")))
/// A paren-surrounded, comma-separated list.
const parenList = el => seq("(", list(el), ")");
/// A list that requires a comma for unit length.
const listComma = el => optional(choice(seq(el, ","), seq(el, repeat1(seq(",", el)), optional(","))))
/// A paren-surrounded list that requires a comma for unit length.
const parenListComma = el => seq("(", listComma(el), ")")
/// Shorthand to write a binary expression with left precedence of the given priority.
const expr_prec = (expr, precedence, op) => prec.left(precedence,
seq(field("lhs", expr), field("op", op), field("rhs", expr)))
export default grammar({
name: "kerolox",
extras: $ => [$._whitespace, $.comment],
rules: {
file: $ => repeat(choice(
// explicitly parse newlines and comments separately
// this helps determine which comments are doc comments
$.newline, $.comment,
// the actual items
$.type_alias, $.import, $.relation, $.rule, $.assumption
)),
comment: $ => seq(";", $.commentInner),
commentInner: _ => /[^\n]*\n/,
newline: _ => /[\n\r]/,
_whitespace: _ => /[ \n\r\t]/,
docs: _ => /(?:;.*\n)*/,
variable: _ => /[a-z][a-zA-Z0-9_]*/,
symbol: _ => /[A-Z][a-zA-Z0-9]*/,
_ident: $ => choice($.variable, $.symbol),
integer: _ => choice("0", /-?[1-9][0-9]*/),
type_alias: $ => seq(
"type",
field("name", $.symbol),
"=",
field("type", $.type),
),
type: $ => choice(
field("named", $.name),
parenListComma(field("tuple", $.type)),
),
import: $ => seq(
"import",
field("path", $.symbol),
repeat(seq(".", field("path", $.symbol))),
".",
choice(
field("item", $.symbol),
parenList(field("item", $.symbol))
),
),
relation: $ => seq(
"define",
field("input", optional("input")),
field("output", optional("output")),
field("decision", optional("decision")),
field("name", $.symbol),
field("type", $.type),
),
rule: $ => seq(
optional(field("negate", "-")),
field("relation", $.name),
field("head", $.expr),
choice(".", seq(":-", field("body", $.rule_body))),
),
assumption: $ => seq(
optional(seq("soft", "(", field("soft", $.integer), ")")),
":-",
field("body", $.rule_body),
),
name: $ => prec.left(seq(repeat(seq(field("path", $.symbol), ".")), field("path", $.symbol))),
rule_body: $ => seq(list(field("clause", $.expr)), "."),
expr: $ => choice(
// variables
field("hole", "_"),
field("variable", $.variable),
// composites
field("apply", $.apply),
field("tuple", $.tuple),
field("aggregate", $.aggregate),
// value literals
field("true", "True"),
field("false", "False"),
field("symbol", $.name),
field("integer", $.integer),
// operations
field("unary", $.unary_expr),
field("binary", $.binary_expr),
// nested
seq('(', field("parens", $.expr), ')'),
),
apply: $ => prec.right(2, seq(field("head", $.name), field("body", $.expr))),
tuple: $ => parenListComma(field("el", $.expr)),
aggregate: $ => seq(
field("op", $.aggregate_op),
optional(seq(list(field("witness", $.variable)), ":")),
choice(
field("apply", $.apply),
seq("{", list(field("clause", $.expr) ), "}"),
),
),
aggregate_op: $ => /@[a-z]+/,
unary_expr: $ => prec.left(5, seq(
field("op", $.unary_op),
field("term", $.expr)
)),
unary_op: _ => choice("!", "-"),
binary_expr: $ => choice(
expr_prec($.expr, 4, choice("*", "/")),
expr_prec($.expr, 3, choice("+", "-")),
expr_prec($.expr, 2, choice("..", "++")),
expr_prec($.expr, 1, choice("=", "!=", ">=", "<=", "<", ">")),
expr_prec($.expr, 0, choice("&&", "||")),
),
}
});