Comprehensive grammar rewrite to conform to SCL specs. Improves parse success rate from 0% to 90% (187 of 208 reference project files). Case-insensitive keywords: - Add ci() helper for case-insensitive token matching - All keywords (TYPE, STRUCT, VAR, IF, etc.) now match any case - All type names (BOOL, Int, Dint, etc.) now match any case New grammar constructs: - RETURN statement - GOTO statement - AUTHOR/FAMILY/NAME block headers - VAR DB_SPECIFIC section - ARRAY[*] (variable-length arrays) - Multidimensional ARRAY[a..b, c..d] - REF_TO <type> - VARIANT type - Anonymous STRUCT...END_STRUCT as a type - Compound assignment operators (+=, -=, *=, /=, &=, |=, ^=) - Chained assignments (lvalue := lvalue := expression) - Bit access (lvalue.%X0) - Method calls (lvalue.field(...)) - Struct/array initializers ((TRUE, FALSE, ...), (name := value)) - END_STRUCT; (semicolon after END_STRUCT in UDTs) - Typed DB associated block name (DATA_BLOCK "Name" : "UDT") - BOM (Byte Order Mark) handling - TITLE as single-token statement (avoids keyword/identifier conflict) New type_builtin aliases: - LWORD, LINT, ULINT, UDINT, DATE, TIME_OF_DAY, DATE_AND_TIME - S5TIME, LTIME, DTL, LTOD, LDT, LTIME_OF_DAY, DATE_AND_LTIME New literal formats: - Binary (2#...), Octal (8#...) - BOOL#TRUE/FALSE - Typed integers (INT#, SINT#, DINT#, etc.) - Date/time literals (DATE#, TOD#, DT#, LTIME#, S5TIME#, DTL#) - WSTRING# and STRING# typed string literals - C# char literal - Time units with case-insensitive matching (T#1S, T#2s) Updated constructs: - CASE labels: comma-separated lists, string labels - Function call parameters: interleaved input (:=) and output (=>) params - FOR loop variable: accepts prefixed_identifier (#tempVar) - Array bounds: accept string/identifier constants (Array[0.."MAX"]) - Author/Family values: accept unquoted text with hyphens/slashes Known remaining issues (21 files, 10%): - Triple-nested parentheses in expressions - Some multi-line function call parameter patterns - END_REGION with trailing region name - Complex chained field access in assignments
676 lines
19 KiB
JavaScript
676 lines
19 KiB
JavaScript
// Case-insensitive token helper: matches any case combination of a word.
|
||
function ci(word) {
|
||
return token(new RegExp(word.split('').map(c =>
|
||
c.toUpperCase() === c.toLowerCase() ? c : '[' + c.toUpperCase() + c.toLowerCase() + ']'
|
||
).join('')));
|
||
}
|
||
|
||
module.exports = grammar({
|
||
name: 'scl',
|
||
|
||
extras: $ => [
|
||
/\s/,
|
||
$.line_comment,
|
||
$.block_comment,
|
||
$.c_style_comment
|
||
],
|
||
|
||
conflicts: $ => [
|
||
[$.case_item],
|
||
[$.region_statement],
|
||
[$.case_statement],
|
||
[$.fb_call, $.function_call],
|
||
[$.fb_parameter, $.parameter],
|
||
[$.var_declaration, $.var_temp_declaration],
|
||
[$.var_declaration, $.var_constant_declaration],
|
||
[$.var_declaration, $.var_retain_declaration],
|
||
[$.var_declaration, $.var_non_retain_declaration],
|
||
[$.var_declaration, $.var_db_specific_declaration],
|
||
[$.type, $.struct_type],
|
||
[$.expression, $.struct_initializer]
|
||
],
|
||
|
||
rules: {
|
||
|
||
source_file: $ => seq(
|
||
optional($.bom),
|
||
repeat(
|
||
choice(
|
||
$.organization_block,
|
||
$.function_block,
|
||
$.function,
|
||
$.type_definition,
|
||
$.data_block,
|
||
$.statement
|
||
)
|
||
)
|
||
),
|
||
|
||
// Title statement: matches the entire "TITLE = <text>" line as a single token
|
||
title_statement: $ => token(seq(
|
||
/[Tt][Ii][Tt][Ll][Ee]/,
|
||
/\s*=\s*/,
|
||
/[^\n\r]+/
|
||
)),
|
||
|
||
// Author/Family value: unquoted text with hyphens, underscores, slashes, etc.
|
||
// E.g. L-TECH, Siemens_Digital_Industries, Manz/Siemens (without quotes)
|
||
author_value: $ => token(repeat1(/[a-zA-Z0-9_\-\/]/)),
|
||
|
||
// --- Block definitions ---
|
||
|
||
type_definition: $ => seq(
|
||
alias(ci('TYPE'), $.keyword),
|
||
field('name', choice($.string, $.identifier)),
|
||
optional($.title_statement),
|
||
optional($.attribute_list),
|
||
optional(seq(alias(ci('NAME'), $.keyword), ':', choice($.string, $.author_value))),
|
||
optional(seq(alias(ci('AUTHOR'), $.keyword), ':', choice($.string, $.author_value))),
|
||
optional(seq(alias(ci('FAMILY'), $.keyword), ':', choice($.string, $.author_value))),
|
||
optional(seq(alias(ci('VERSION'), $.keyword), ':', $.number)),
|
||
alias(ci('STRUCT'), $.keyword),
|
||
repeat(choice($.var_item, $.block_comment)),
|
||
alias(ci('END_STRUCT'), $.keyword),
|
||
optional(';'),
|
||
alias(ci('END_TYPE'), $.keyword)
|
||
),
|
||
|
||
data_block: $ => seq(
|
||
alias(ci('DATA_BLOCK'), $.keyword),
|
||
field('name', choice($.string, $.identifier)),
|
||
optional($.title_statement),
|
||
optional($.attribute_list),
|
||
optional(seq(alias(ci('NAME'), $.keyword), ':', choice($.string, $.author_value))),
|
||
optional(seq(alias(ci('AUTHOR'), $.keyword), ':', choice($.string, $.author_value))),
|
||
optional(seq(alias(ci('FAMILY'), $.keyword), ':', choice($.string, $.author_value))),
|
||
optional(seq(alias(ci('VERSION'), $.keyword), ':', $.number)),
|
||
optional(choice(
|
||
alias(ci('NON_RETAIN'), $.keyword),
|
||
alias(ci('RETAIN'), $.keyword)
|
||
)),
|
||
// Associated FB/UDT name (for typed DBs: appears on its own line)
|
||
optional(field('associated', $.string)),
|
||
optional(choice(
|
||
$.var_declaration,
|
||
$.var_db_specific_declaration
|
||
)),
|
||
alias(ci('BEGIN'), $.keyword),
|
||
repeat($.statement),
|
||
alias(ci('END_DATA_BLOCK'), $.keyword)
|
||
),
|
||
|
||
organization_block: $ => seq(
|
||
alias(ci('ORGANIZATION_BLOCK'), $.keyword),
|
||
field('name', $.string),
|
||
optional($.title_statement),
|
||
optional($.attribute_list),
|
||
optional(seq(alias(ci('NAME'), $.keyword), ':', choice($.string, $.author_value))),
|
||
optional(seq(alias(ci('AUTHOR'), $.keyword), ':', choice($.string, $.author_value))),
|
||
optional(seq(alias(ci('FAMILY'), $.keyword), ':', choice($.string, $.author_value))),
|
||
optional(seq(alias(ci('VERSION'), $.keyword), ':', $.number)),
|
||
repeat($.var_declaration),
|
||
alias(ci('BEGIN'), $.keyword),
|
||
repeat($.statement),
|
||
alias(ci('END_ORGANIZATION_BLOCK'), $.keyword)
|
||
),
|
||
|
||
function_block: $ => seq(
|
||
alias(ci('FUNCTION_BLOCK'), $.keyword),
|
||
field('name', $.string),
|
||
optional($.title_statement),
|
||
optional($.attribute_list),
|
||
optional(seq(alias(ci('NAME'), $.keyword), ':', choice($.string, $.author_value))),
|
||
optional(seq(alias(ci('AUTHOR'), $.keyword), ':', choice($.string, $.author_value))),
|
||
optional(seq(alias(ci('FAMILY'), $.keyword), ':', choice($.string, $.author_value))),
|
||
optional(seq(alias(ci('VERSION'), $.keyword), ':', $.number)),
|
||
repeat(choice(
|
||
$.var_declaration,
|
||
$.var_temp_declaration,
|
||
$.var_constant_declaration,
|
||
$.var_retain_declaration,
|
||
$.var_non_retain_declaration,
|
||
$.var_db_specific_declaration,
|
||
$.block_comment
|
||
)),
|
||
alias(ci('BEGIN'), $.keyword),
|
||
repeat($.statement),
|
||
alias(ci('END_FUNCTION_BLOCK'), $.keyword)
|
||
),
|
||
|
||
function: $ => seq(
|
||
alias(ci('FUNCTION'), $.keyword),
|
||
field('name', $.string),
|
||
optional(seq(':', $.type)),
|
||
optional($.title_statement),
|
||
optional($.attribute_list),
|
||
optional(seq(alias(ci('NAME'), $.keyword), ':', choice($.string, $.author_value))),
|
||
optional(seq(alias(ci('AUTHOR'), $.keyword), ':', choice($.string, $.author_value))),
|
||
optional(seq(alias(ci('FAMILY'), $.keyword), ':', choice($.string, $.author_value))),
|
||
optional(seq(alias(ci('VERSION'), $.keyword), ':', $.number)),
|
||
repeat(choice(
|
||
$.var_declaration,
|
||
$.var_temp_declaration,
|
||
$.var_constant_declaration,
|
||
$.var_retain_declaration,
|
||
$.var_non_retain_declaration,
|
||
$.block_comment
|
||
)),
|
||
alias(ci('BEGIN'), $.keyword),
|
||
repeat($.statement),
|
||
alias(ci('END_FUNCTION'), $.keyword)
|
||
),
|
||
|
||
// --- Attributes ---
|
||
|
||
attribute_list: $ => seq(
|
||
'{',
|
||
$.attribute,
|
||
repeat(seq(choice(',', ';'), $.attribute)),
|
||
optional(choice(',', ';')),
|
||
'}'
|
||
),
|
||
|
||
attribute: $ => seq(
|
||
$.identifier,
|
||
':=',
|
||
$.attribute_value
|
||
),
|
||
|
||
attribute_value: $ => choice(
|
||
$.string,
|
||
alias(ci('TRUE'), $.constant),
|
||
alias(ci('FALSE'), $.constant),
|
||
$.identifier
|
||
),
|
||
|
||
// --- VAR section declarations ---
|
||
|
||
var_declaration: $ => seq(
|
||
choice(
|
||
alias(ci('VAR_INPUT'), $.keyword),
|
||
alias(ci('VAR_OUTPUT'), $.keyword),
|
||
alias(ci('VAR_IN_OUT'), $.keyword),
|
||
alias(ci('VAR'), $.keyword),
|
||
alias(ci('VAR_TEMP'), $.keyword),
|
||
seq(alias(ci('VAR'), $.keyword), alias(ci('CONSTANT'), $.keyword)),
|
||
seq(alias(ci('VAR'), $.keyword), alias(ci('RETAIN'), $.keyword)),
|
||
seq(alias(ci('VAR'), $.keyword), alias(ci('NON_RETAIN'), $.keyword)),
|
||
alias(ci('VAR_CONSTANT'), $.keyword),
|
||
alias(ci('VAR_RETAIN'), $.keyword),
|
||
alias(ci('VAR_NON_RETAIN'), $.keyword)
|
||
),
|
||
repeat(choice($.var_item, $.block_comment)),
|
||
alias(ci('END_VAR'), $.keyword)
|
||
),
|
||
|
||
var_temp_declaration: $ => seq(
|
||
alias(ci('VAR_TEMP'), $.keyword),
|
||
repeat(choice($.var_item, $.block_comment)),
|
||
alias(ci('END_VAR'), $.keyword)
|
||
),
|
||
|
||
var_constant_declaration: $ => seq(
|
||
choice(
|
||
seq(alias(ci('VAR'), $.keyword), alias(ci('CONSTANT'), $.keyword)),
|
||
alias(ci('VAR_CONSTANT'), $.keyword)
|
||
),
|
||
repeat(choice($.var_item, $.block_comment)),
|
||
alias(ci('END_VAR'), $.keyword)
|
||
),
|
||
|
||
var_retain_declaration: $ => seq(
|
||
choice(
|
||
seq(alias(ci('VAR'), $.keyword), alias(ci('RETAIN'), $.keyword)),
|
||
alias(ci('VAR_RETAIN'), $.keyword)
|
||
),
|
||
repeat(choice($.var_item, $.block_comment)),
|
||
alias(ci('END_VAR'), $.keyword)
|
||
),
|
||
|
||
var_non_retain_declaration: $ => seq(
|
||
choice(
|
||
seq(alias(ci('VAR'), $.keyword), alias(ci('NON_RETAIN'), $.keyword)),
|
||
alias(ci('VAR_NON_RETAIN'), $.keyword)
|
||
),
|
||
repeat(choice($.var_item, $.block_comment)),
|
||
alias(ci('END_VAR'), $.keyword)
|
||
),
|
||
|
||
var_db_specific_declaration: $ => seq(
|
||
seq(alias(ci('VAR'), $.keyword), alias(ci('DB_SPECIFIC'), $.keyword)),
|
||
repeat(choice($.var_item, $.block_comment)),
|
||
alias(ci('END_VAR'), $.keyword)
|
||
),
|
||
|
||
// --- Variable items ---
|
||
|
||
var_item: $ => seq(
|
||
field('name', $.identifier),
|
||
optional($.attribute_list),
|
||
':',
|
||
field('type', $.type),
|
||
optional(seq(':=', choice($.expression, $.struct_initializer))),
|
||
';'
|
||
),
|
||
|
||
// --- Types ---
|
||
|
||
type: $ => choice(
|
||
$.type_builtin,
|
||
$.array_type,
|
||
$.ref_type,
|
||
$.struct_type,
|
||
$.string_type,
|
||
$.identifier,
|
||
$.string
|
||
),
|
||
|
||
type_builtin: $ => choice(
|
||
alias(ci('BOOL'), $.type_builtin),
|
||
alias(ci('BYTE'), $.type_builtin),
|
||
alias(ci('WORD'), $.type_builtin),
|
||
alias(ci('DWORD'), $.type_builtin),
|
||
alias(ci('LWORD'), $.type_builtin),
|
||
alias(ci('SINT'), $.type_builtin),
|
||
alias(ci('USINT'), $.type_builtin),
|
||
alias(ci('INT'), $.type_builtin),
|
||
alias(ci('UINT'), $.type_builtin),
|
||
alias(ci('DINT'), $.type_builtin),
|
||
alias(ci('UDINT'), $.type_builtin),
|
||
alias(ci('LINT'), $.type_builtin),
|
||
alias(ci('ULINT'), $.type_builtin),
|
||
alias(ci('REAL'), $.type_builtin),
|
||
alias(ci('LREAL'), $.type_builtin),
|
||
alias(ci('CHAR'), $.type_builtin),
|
||
alias(ci('WCHAR'), $.type_builtin),
|
||
alias(ci('TIME'), $.type_builtin),
|
||
alias(ci('LTIME'), $.type_builtin),
|
||
alias(ci('S5TIME'), $.type_builtin),
|
||
alias(ci('DATE'), $.type_builtin),
|
||
alias(ci('TIME_OF_DAY'), $.type_builtin),
|
||
alias(ci('TOD'), $.type_builtin),
|
||
alias(ci('LTOD'), $.type_builtin),
|
||
alias(ci('LTIME_OF_DAY'), $.type_builtin),
|
||
alias(ci('DATE_AND_TIME'), $.type_builtin),
|
||
alias(ci('DT'), $.type_builtin),
|
||
alias(ci('LDT'), $.type_builtin),
|
||
alias(ci('DATE_AND_LTIME'), $.type_builtin),
|
||
alias(ci('DTL'), $.type_builtin),
|
||
alias(ci('VARIANT'), $.type_builtin)
|
||
),
|
||
|
||
string_type: $ => seq(
|
||
choice(
|
||
alias(ci('STRING'), $.type_builtin),
|
||
alias(ci('WSTRING'), $.type_builtin)
|
||
),
|
||
optional(seq('[', $.number, ']'))
|
||
),
|
||
|
||
array_type: $ => seq(
|
||
alias(ci('ARRAY'), $.keyword),
|
||
'[',
|
||
repeat1(seq(
|
||
choice(
|
||
seq(choice($.number, $.string, $.identifier), '..', choice($.number, $.string, $.identifier)),
|
||
'*'
|
||
),
|
||
optional(',')
|
||
)),
|
||
']',
|
||
alias(ci('OF'), $.keyword),
|
||
$.type
|
||
),
|
||
|
||
ref_type: $ => seq(
|
||
alias(ci('REF_TO'), $.keyword),
|
||
$.type
|
||
),
|
||
|
||
struct_type: $ => seq(
|
||
alias(ci('STRUCT'), $.keyword),
|
||
repeat(choice($.var_item, $.block_comment)),
|
||
alias(ci('END_STRUCT'), $.keyword)
|
||
),
|
||
|
||
// --- Statements ---
|
||
|
||
statement: $ => choice(
|
||
$.assignment,
|
||
$.fb_call,
|
||
$.if_statement,
|
||
$.case_statement,
|
||
$.for_statement,
|
||
$.while_statement,
|
||
$.repeat_statement,
|
||
$.region_statement,
|
||
$.function_call,
|
||
$.return_statement,
|
||
$.goto_statement,
|
||
$.exit_statement,
|
||
$.continue_statement,
|
||
$.empty_statement,
|
||
$.var_declaration,
|
||
$.var_temp_declaration,
|
||
$.var_constant_declaration,
|
||
$.var_retain_declaration,
|
||
$.var_non_retain_declaration,
|
||
$.var_db_specific_declaration
|
||
),
|
||
|
||
empty_statement: $ => ';',
|
||
|
||
// --- FB calls ---
|
||
|
||
fb_call: $ => seq(
|
||
field('instance', choice($.prefixed_identifier, $.identifier)),
|
||
'(',
|
||
optional($.fb_parameter_list),
|
||
')'
|
||
),
|
||
|
||
fb_parameter_list: $ => seq(
|
||
$.fb_parameter,
|
||
repeat(seq(',', $.fb_parameter))
|
||
),
|
||
|
||
fb_parameter: $ => seq(
|
||
field('name', $.identifier),
|
||
choice(':=', '=>'),
|
||
$.expression
|
||
),
|
||
|
||
// --- Assignments (including compound and chained) ---
|
||
|
||
assignment: $ => seq(
|
||
field('target', $.lvalue),
|
||
choice(':=', '+=', '-=', '*=', '/=', '&=', '|=', '^='),
|
||
field('value', choice($.expression, $.chained_assignment)),
|
||
';'
|
||
),
|
||
|
||
chained_assignment: $ => prec.right(seq(
|
||
$.lvalue,
|
||
':=',
|
||
choice($.expression, $.chained_assignment)
|
||
)),
|
||
|
||
lvalue: $ => choice(
|
||
$.identifier,
|
||
$.prefixed_identifier,
|
||
$.field_access,
|
||
$.array_access,
|
||
$.function_name,
|
||
$.bit_access
|
||
),
|
||
|
||
function_name: $ => $.string,
|
||
|
||
prefixed_identifier: $ => seq(alias('#', $.prefix), $.identifier),
|
||
|
||
field_access: $ => seq($.lvalue, '.', choice($.identifier, $.prefixed_identifier)),
|
||
|
||
array_access: $ => seq($.lvalue, '[', $.expression, ']'),
|
||
|
||
// Bit access: lvalue.%X0, lvalue.%X15, etc.
|
||
bit_access: $ => seq($.lvalue, '.', '%', /[A-Za-z]+/, $.number),
|
||
|
||
// --- Control flow ---
|
||
|
||
if_statement: $ => seq(
|
||
alias(ci('IF'), $.keyword),
|
||
$.expression,
|
||
alias(ci('THEN'), $.keyword),
|
||
repeat($.statement),
|
||
repeat($.elsif_clause),
|
||
optional($.else_clause),
|
||
alias(ci('END_IF'), $.keyword)
|
||
),
|
||
|
||
elsif_clause: $ => seq(
|
||
alias(ci('ELSIF'), $.keyword),
|
||
$.expression,
|
||
alias(ci('THEN'), $.keyword),
|
||
repeat($.statement)
|
||
),
|
||
|
||
else_clause: $ => seq(alias(ci('ELSE'), $.keyword), repeat($.statement)),
|
||
|
||
case_statement: $ => seq(
|
||
alias(ci('CASE'), $.keyword),
|
||
$.expression,
|
||
alias(ci('OF'), $.keyword),
|
||
repeat($.case_item),
|
||
optional($.else_clause),
|
||
alias(ci('END_CASE'), $.keyword)
|
||
),
|
||
|
||
case_item: $ => seq(
|
||
repeat1(seq(
|
||
choice($.number, $.string, $.identifier, $.prefixed_identifier, $.range),
|
||
optional(',')
|
||
)),
|
||
':',
|
||
repeat($.statement)
|
||
),
|
||
|
||
range: $ => seq($.number, '..', $.number),
|
||
|
||
for_statement: $ => seq(
|
||
alias(ci('FOR'), $.keyword),
|
||
choice($.identifier, $.prefixed_identifier),
|
||
':=',
|
||
$.expression,
|
||
alias(ci('TO'), $.keyword),
|
||
$.expression,
|
||
optional(seq(alias(ci('BY'), $.keyword), $.expression)),
|
||
alias(ci('DO'), $.keyword),
|
||
repeat($.statement),
|
||
alias(ci('END_FOR'), $.keyword)
|
||
),
|
||
|
||
while_statement: $ => seq(
|
||
alias(ci('WHILE'), $.keyword),
|
||
$.expression,
|
||
alias(ci('DO'), $.keyword),
|
||
repeat($.statement),
|
||
alias(ci('END_WHILE'), $.keyword)
|
||
),
|
||
|
||
repeat_statement: $ => seq(
|
||
alias(ci('REPEAT'), $.keyword),
|
||
repeat($.statement),
|
||
alias(ci('UNTIL'), $.keyword),
|
||
$.expression,
|
||
alias(ci('END_REPEAT'), $.keyword)
|
||
),
|
||
|
||
region_statement: $ => seq(
|
||
alias(ci('REGION'), $.keyword),
|
||
$.region_name,
|
||
repeat($.statement),
|
||
alias(ci('END_REGION'), $.keyword),
|
||
optional($.region_name)
|
||
),
|
||
|
||
region_name: $ => token(repeat1(choice(/[a-zA-Z0-9_\-\/]/, /[ \t]+/))),
|
||
|
||
return_statement: $ => alias(ci('RETURN'), $.keyword),
|
||
|
||
goto_statement: $ => seq(alias(ci('GOTO'), $.keyword), $.identifier),
|
||
|
||
// --- Function calls (with interleaved input/output params) ---
|
||
|
||
function_call: $ => choice(
|
||
seq(choice($.identifier, $.string), '(', optional($.parameter_list), ')'),
|
||
seq($.method_callee, '(', optional($.parameter_list), ')')
|
||
),
|
||
|
||
method_callee: $ => seq($.lvalue, '.', $.identifier),
|
||
|
||
parameter_list: $ => seq(
|
||
$.parameter,
|
||
repeat(seq(',', $.parameter))
|
||
),
|
||
|
||
parameter: $ => choice(
|
||
prec(1, seq(field('name', $.identifier), ':=', $.expression)),
|
||
prec(1, seq(field('name', $.identifier), '=>', $.identifier))
|
||
),
|
||
|
||
// --- Expressions ---
|
||
|
||
expression: $ => choice(
|
||
$.number,
|
||
$.hex_number,
|
||
$.binary_number,
|
||
$.octal_number,
|
||
$.time_value,
|
||
$.date_literal,
|
||
$.bool_literal,
|
||
$.typed_int_literal,
|
||
$.wstring_literal,
|
||
$.char_literal,
|
||
$.identifier,
|
||
$.prefixed_identifier,
|
||
$.field_access,
|
||
$.array_access,
|
||
$.bit_access,
|
||
$.string,
|
||
$.function_call,
|
||
$.unary_expression,
|
||
$.binary_expression,
|
||
prec(1, seq('(', $.expression, ')'))
|
||
),
|
||
|
||
unary_expression: $ => prec.right(10, choice(
|
||
seq(alias(ci('NOT'), $.operator), $.expression),
|
||
seq('+', $.expression),
|
||
seq('-', $.expression)
|
||
)),
|
||
|
||
binary_expression: $ => choice(
|
||
prec.left(2, seq($.expression, alias(ci('OR'), $.operator), $.expression)),
|
||
prec.left(3, seq($.expression, alias(ci('XOR'), $.operator), $.expression)),
|
||
prec.left(4, seq($.expression, alias(ci('AND'), $.operator), $.expression)),
|
||
prec.left(5, seq($.expression, choice('=', '<>'), $.expression)),
|
||
prec.left(6, seq($.expression, choice('<', '>', '<=', '>='), $.expression)),
|
||
prec.left(7, seq($.expression, choice('+', '-'), $.expression)),
|
||
prec.left(8, seq($.expression, choice('*', '/', alias(ci('MOD'), $.operator)), $.expression))
|
||
),
|
||
|
||
// Struct/array initializer: (TRUE, FALSE, 1, 2, ...) or ((1, 2), (3, 4)) or () or (name := value, ...)
|
||
struct_initializer: $ => prec(1, seq(
|
||
'(',
|
||
optional(seq(
|
||
choice(
|
||
$.struct_initializer,
|
||
seq(field('name', $.identifier), ':=', choice($.expression, $.struct_initializer)),
|
||
$.expression
|
||
),
|
||
repeat(seq(',', choice(
|
||
$.struct_initializer,
|
||
seq(field('name', $.identifier), ':=', choice($.expression, $.struct_initializer)),
|
||
$.expression
|
||
))),
|
||
optional(',')
|
||
)),
|
||
')'
|
||
)),
|
||
|
||
// --- Literals ---
|
||
|
||
number: $ => token(/\d+(\.\d+)?/),
|
||
|
||
hex_number: $ => token(seq('16#', /[0-9A-Fa-f_]+/)),
|
||
|
||
binary_number: $ => token(seq('2#', /[01_]+/)),
|
||
|
||
octal_number: $ => token(seq('8#', /[0-7_]+/)),
|
||
|
||
time_value: $ => token(seq(
|
||
/[Tt]#/,
|
||
repeat1(seq(/\d+/, choice(/[Nn][Ss]/, /[Uu][Ss]/, /[Mm][Ss]/, /[Ss]/, /[Mm][Ii][Nn]/, /[Hh]/, /[Dd]/))),
|
||
optional(/_[\d_]+[A-Za-z]+/)
|
||
)),
|
||
|
||
date_literal: $ => token(seq(
|
||
choice(
|
||
/[Dd][Aa][Tt][Ee]#/,
|
||
/[Tt][Oo][Dd]#/,
|
||
/[Dd][Tt]#/,
|
||
/[Ll][Tt][Ii][Mm][Ee]#/,
|
||
/[Ss]5[Tt][Ii][Mm][Ee]#/,
|
||
/[Ll][Dd][Tt]#/,
|
||
/[Ll][Tt][Oo][Dd]#/,
|
||
/[Dd][Tt][Ll]#/
|
||
),
|
||
/[\d:_\-]+/
|
||
)),
|
||
|
||
bool_literal: $ => token(seq(
|
||
/[Bb][Oo][Oo][Ll]#/,
|
||
choice('TRUE', 'FALSE', 'true', 'false', 'True', 'False')
|
||
)),
|
||
|
||
typed_int_literal: $ => token(seq(
|
||
choice(
|
||
/[Uu]?[Ss][Ii][Nn][Tt]#/,
|
||
/[Uu]?[Ii][Nn][Tt]#/,
|
||
/[Uu]?[Dd][Ii][Nn][Tt]#/,
|
||
/[Uu]?[Ll][Ii][Nn][Tt]#/,
|
||
/L#/,
|
||
/C#/,
|
||
/LW#/
|
||
),
|
||
/[\dA-Fa-f_#]+/
|
||
)),
|
||
|
||
string: $ => choice(
|
||
seq("'", repeat(choice(/[^'\n]/, "''")), "'"),
|
||
seq('"', repeat(choice(/[^"\n]/, '""')), '"')
|
||
),
|
||
|
||
wstring_literal: $ => token(seq(
|
||
choice(
|
||
/[Ww][Ss][Tt][Rr][Ii][Nn][Gg]#/,
|
||
/[Ss][Tt][Rr][Ii][Nn][Gg]#/
|
||
),
|
||
choice(
|
||
seq("'", repeat(choice(/[^'\n]/, "''")), "'"),
|
||
seq('"', repeat(choice(/[^"\n]/, '""')), '"')
|
||
)
|
||
)),
|
||
|
||
char_literal: $ => choice(
|
||
seq("'", /[^']/, "'"),
|
||
seq(/[Ww][Cc][Hh][Aa][Rr]#/, "'", /[^']/, "'"),
|
||
seq('C#', "'", /[^']/, "'")
|
||
),
|
||
|
||
identifier: $ => /[a-zA-Z_][a-zA-Z0-9_]*/,
|
||
|
||
line_comment: $ => token(seq('//', /.*/)),
|
||
|
||
block_comment: $ => token(seq(
|
||
'(*',
|
||
repeat(choice(/[^*]/, seq('*', /[^)]/))),
|
||
'*)'
|
||
)),
|
||
|
||
c_style_comment: $ => token(seq(
|
||
'(/*',
|
||
repeat(choice(/[^*]/, seq('*', /[^)]/))),
|
||
'*/)'
|
||
)),
|
||
|
||
exit_statement: $ => alias(ci('EXIT'), $.keyword),
|
||
|
||
continue_statement: $ => alias(ci('CONTINUE'), $.keyword),
|
||
|
||
// UTF-8 BOM (Byte Order Mark) - consumed at start of file
|
||
bom: $ => token('')
|
||
|
||
}
|
||
});
|