184 lines
5.1 KiB
JavaScript
184 lines
5.1 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]*/),
|
|
|
|
string_literal: $ => seq(
|
|
'"',
|
|
repeat(choice($.string_content, $.escape_sequence)),
|
|
'"',
|
|
),
|
|
|
|
string_content: _ => token.immediate(prec(1, /[^\\"]+/)),
|
|
|
|
escape_sequence: _ => token.immediate(/\\[bfnrtv\"\\]/),
|
|
|
|
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),
|
|
field("string", $.string_literal),
|
|
|
|
// 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("&&", "||")),
|
|
),
|
|
}
|
|
});
|