Files
wehub-resource-sync 41cb1c0170
OpenSSF Scorecard / scorecard (push) Failing after 0s
DCO / dco (push) Failing after 0s
CodeQL SAST / analyze (push) Failing after 1s
Deploy Pages / deploy (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:28:05 +08:00

277 lines
6.6 KiB
JavaScript

/// <reference types="tree-sitter-cli/dsl" />
// tree-sitter grammar for FORM (symbolic manipulation language)
// https://www.nikhef.nl/~form/
module.exports = grammar({
name: 'form',
extras: $ => [
/\s/,
$.line_comment,
$.block_comment,
],
word: $ => $.identifier,
// Tell tree-sitter to allow GLR parsing for these specific ambiguities
conflicts: $ => [
[$.procedure_definition],
[$.call_statement],
],
rules: {
source_file: $ => repeat($._statement),
_statement: $ => choice(
$.declaration_statement,
$.substitution_statement,
$.print_statement,
$.procedure_definition,
$.call_statement,
$.if_statement,
$.repeat_statement,
$.do_loop,
$.argument_block,
$.label_statement,
$.module_directive,
$.include_directive,
$.define_directive,
$.preproc_directive_with_arg,
$.preproc_directive_bare,
$.preproc_line_directive,
$.preproc_do,
$.expression_statement,
),
// Declarations: Symbols, Functions, CFunctions, Vectors, Indices, etc.
declaration_statement: $ => seq(
field('kind', choice(
'Symbols', 'Symbol',
'Functions', 'Function',
'CFunctions', 'CFunction',
'Tensors', 'Tensor',
'Vectors', 'Vector',
'Indices', 'Index',
'Sets', 'Set',
'Tables', 'Table',
'NTensors', 'NTensor',
'AutoDeclare',
'Dimension',
'FixIndex',
'Off', 'On',
)),
commaSep1($.identifier),
';',
),
// Substitution: id pattern = replacement;
substitution_statement: $ => seq(
optional(choice('Local', 'Global')),
'id',
$._expression,
'=',
$._expression,
';',
),
// Print/format statements
print_statement: $ => seq(
choice('Print', 'NoPrint', 'Format'),
optional($._expression),
';',
),
// Procedure definition: #procedure name(args) ... #endprocedure
// Parameters use a dedicated list to avoid expression ambiguity.
procedure_definition: $ => prec(10, seq(
'#procedure',
field('name', $.identifier),
optional($.parameter_list),
repeat($._statement),
'#endprocedure',
)),
parameter_list: $ => seq(
token.immediate('('),
optional(commaSep1(alias($.identifier, $.parameter))),
')',
),
// #call procedure(args)
call_statement: $ => prec(10, seq(
'#call',
field('name', $.identifier),
optional(seq(
token.immediate('('),
optional(commaSep1($._expression)),
')',
)),
)),
// #include filename
include_directive: $ => seq(
'#include',
choice($.string_literal, $.identifier),
),
// #define name "value"
define_directive: $ => seq(
choice('#define', '#redefine'),
$.identifier,
$.string_literal,
),
// Preprocessor directives that REQUIRE an argument
preproc_directive_with_arg: $ => seq(
choice('#ifdef', '#ifndef', '#if', '#case', '#switch', '#undefine'),
$._expression,
),
// Preprocessor directives with NO argument
preproc_directive_bare: _ => choice(
'#else', '#endif', '#break', '#endswitch',
),
// Preprocessor directives that consume rest of line
preproc_line_directive: $ => seq(
choice('#message', '#write', '#close', '#system',
'#optimize', '#Optimize', '#toexternal', '#fromexternal',
'#setexternal', '#rmexternal', '#external',
'#factdollar', '#pipe'),
/[^\n]*/,
),
// Preprocessor loop: #do var = start, end ... #enddo
preproc_do: $ => seq(
'#do',
$.identifier,
'=',
$._expression,
optional(seq(',', $._expression)),
repeat($._statement),
'#enddo',
),
// Module directives: .sort, .end, .clear, .store, .global
module_directive: _ => seq(
'.',
choice('sort', 'end', 'clear', 'store', 'global'),
),
// Label statement
label_statement: $ => seq(
choice('label', 'goto'),
$.identifier,
';',
),
// if ... endif;
if_statement: $ => seq(
'if',
'(',
$._expression,
')',
repeat($._statement),
optional(seq('else', repeat($._statement))),
'endif',
';',
),
// repeat; ... endrepeat;
repeat_statement: $ => seq(
'repeat',
';',
repeat($._statement),
'endrepeat',
';',
),
// argument ... endargument;
argument_block: $ => seq(
'argument',
';',
repeat($._statement),
'endargument',
';',
),
// do var = start, end; ... enddo;
do_loop: $ => seq(
'do',
$.identifier,
'=',
$._expression,
',',
$._expression,
';',
repeat($._statement),
'enddo',
';',
),
// Generic expression statement (catch-all)
expression_statement: $ => seq(
$._expression,
';',
),
// Expressions
_expression: $ => choice(
$.identifier,
$.number,
$.string_literal,
$.dollar_variable,
$.wildcard,
$.function_application,
$.binary_expression,
$.unary_expression,
$.parenthesized_expression,
$.index_expression,
),
function_application: $ => prec(2, seq(
field('name', $.identifier),
'(',
optional(commaSep1($._expression)),
')',
)),
binary_expression: $ => choice(
prec.left(1, seq($._expression, choice('+', '-'), $._expression)),
prec.left(2, seq($._expression, choice('*', '/'), $._expression)),
prec.left(0, seq($._expression, choice('==', '!=', '<=', '>=', '<', '>'), $._expression)),
prec.left(-1, seq($._expression, choice('&&', '||'), $._expression)),
prec.right(3, seq($._expression, '^', $._expression)),
),
unary_expression: $ => prec(4, seq(choice('-', '+', '!'), $._expression)),
parenthesized_expression: $ => seq('(', $._expression, ')'),
index_expression: $ => prec(1, seq(
$._expression,
'[',
commaSep1($._expression),
']',
)),
// Terminals
identifier: _ => /[a-zA-Z_][a-zA-Z0-9_]*/,
dollar_variable: _ => token(seq('$', /[a-zA-Z_][a-zA-Z0-9_]*/)),
wildcard: _ => token(choice(
seq(/[a-zA-Z_][a-zA-Z0-9_]*/, '?'),
seq('?', /[a-zA-Z_][a-zA-Z0-9_]*/),
)),
number: _ => /\d+(\.\d+)?/,
string_literal: _ => seq('"', /[^"]*/, '"'),
// Comments
line_comment: _ => token(seq('*', /[^\n]*/)),
block_comment: _ => token(seq('/*', /[^*]*\*+([^/*][^*]*\*+)*/, '/')),
},
});
function commaSep1(rule) {
return seq(rule, repeat(seq(',', rule)));
}