feat: major grammar upgrade for SCL spec conformance (Phase 1)

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
This commit is contained in:
2026-07-18 19:19:03 +02:00
parent df2b218107
commit fb55915147
5 changed files with 105863 additions and 75562 deletions
+423 -237
View File
@@ -1,98 +1,152 @@
// 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],
[$.region_name],
[$.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]
],
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: $ => repeat(
choice(
$.organization_block,
$.function_block,
$.function,
$.type_definition,
$.data_block,
$.statement
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('TYPE', $.keyword),
alias(ci('TYPE'), $.keyword),
field('name', choice($.string, $.identifier)),
optional(seq(alias('TITLE', $.keyword), '=', choice($.string, $.identifier))),
optional($.title_statement),
optional($.attribute_list),
optional(seq(alias('VERSION', $.keyword), ':', $.number)),
alias('STRUCT', $.keyword),
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('END_STRUCT', $.keyword),
alias('END_TYPE', $.keyword)
alias(ci('END_STRUCT'), $.keyword),
optional(';'),
alias(ci('END_TYPE'), $.keyword)
),
data_block: $ => seq(
alias('DATA_BLOCK', $.keyword),
alias(ci('DATA_BLOCK'), $.keyword),
field('name', choice($.string, $.identifier)),
optional(seq(alias('TITLE', $.keyword), '=', choice($.string, $.identifier))),
optional($.title_statement),
optional($.attribute_list),
optional(seq(alias('VERSION', $.keyword), ':', $.number)),
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('NON_RETAIN', $.keyword),
alias('RETAIN', $.keyword)
alias(ci('NON_RETAIN'), $.keyword),
alias(ci('RETAIN'), $.keyword)
)),
optional($.var_declaration),
alias('BEGIN', $.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('END_DATA_BLOCK', $.keyword)
alias(ci('END_DATA_BLOCK'), $.keyword)
),
organization_block: $ => seq(
alias('ORGANIZATION_BLOCK', $.keyword),
alias(ci('ORGANIZATION_BLOCK'), $.keyword),
field('name', $.string),
optional(seq(alias('TITLE', $.keyword), '=', choice($.string, $.identifier))),
optional($.title_statement),
optional($.attribute_list),
optional(seq(alias('VERSION', $.keyword), ':', $.number)),
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('BEGIN', $.keyword),
alias(ci('BEGIN'), $.keyword),
repeat($.statement),
alias('END_ORGANIZATION_BLOCK', $.keyword)
alias(ci('END_ORGANIZATION_BLOCK'), $.keyword)
),
function_block: $ => seq(
alias('FUNCTION_BLOCK', $.keyword),
alias(ci('FUNCTION_BLOCK'), $.keyword),
field('name', $.string),
optional(seq(alias('TITLE', $.keyword), '=', choice($.string, $.identifier))),
optional($.title_statement),
optional($.attribute_list),
optional(seq(alias('VERSION', $.keyword), ':', $.number)),
repeat($.var_declaration),
alias('BEGIN', $.keyword),
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('END_FUNCTION_BLOCK', $.keyword)
alias(ci('END_FUNCTION_BLOCK'), $.keyword)
),
function: $ => seq(
alias('FUNCTION', $.keyword),
alias(ci('FUNCTION'), $.keyword),
field('name', $.string),
optional(seq(':', $.type)),
optional(seq(alias('TITLE', $.keyword), '=', choice($.string, $.identifier))),
optional($.title_statement),
optional($.attribute_list),
optional(seq(alias('VERSION', $.keyword), ':', $.number)),
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,
@@ -101,11 +155,13 @@ module.exports = grammar({
$.var_non_retain_declaration,
$.block_comment
)),
alias('BEGIN', $.keyword),
alias(ci('BEGIN'), $.keyword),
repeat($.statement),
alias('END_FUNCTION', $.keyword)
alias(ci('END_FUNCTION'), $.keyword)
),
// --- Attributes ---
attribute_list: $ => seq(
'{',
$.attribute,
@@ -113,141 +169,172 @@ module.exports = grammar({
optional(choice(',', ';')),
'}'
),
attribute: $ => seq(
$.identifier,
':=',
$.attribute_value
),
attribute_value: $ => choice(
$.string,
alias('TRUE', $.constant),
alias('FALSE', $.constant),
alias(ci('TRUE'), $.constant),
alias(ci('FALSE'), $.constant),
$.identifier
),
// --- VAR section declarations ---
var_declaration: $ => seq(
choice(
alias('VAR_INPUT', $.keyword),
alias('VAR_OUTPUT', $.keyword),
alias('VAR_IN_OUT', $.keyword),
alias('VAR', $.keyword),
alias('VAR_TEMP', $.keyword),
seq(alias('VAR', $.keyword), alias('CONSTANT', $.keyword)),
seq(alias('VAR', $.keyword), alias('RETAIN', $.keyword)),
seq(alias('VAR', $.keyword), alias('NON_RETAIN', $.keyword)),
alias('VAR_CONSTANT', $.keyword),
alias('VAR_RETAIN', $.keyword),
alias('VAR_NON_RETAIN', $.keyword)
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('END_VAR', $.keyword)
alias(ci('END_VAR'), $.keyword)
),
var_temp_declaration: $ => seq(
alias('VAR_TEMP', $.keyword),
alias(ci('VAR_TEMP'), $.keyword),
repeat(choice($.var_item, $.block_comment)),
alias('END_VAR', $.keyword)
alias(ci('END_VAR'), $.keyword)
),
var_constant_declaration: $ => seq(
choice(
seq(alias('VAR', $.keyword), alias('CONSTANT', $.keyword)),
alias('VAR_CONSTANT', $.keyword)
seq(alias(ci('VAR'), $.keyword), alias(ci('CONSTANT'), $.keyword)),
alias(ci('VAR_CONSTANT'), $.keyword)
),
repeat(choice($.var_item, $.block_comment)),
alias('END_VAR', $.keyword)
alias(ci('END_VAR'), $.keyword)
),
var_retain_declaration: $ => seq(
choice(
seq(alias('VAR', $.keyword), alias('RETAIN', $.keyword)),
alias('VAR_RETAIN', $.keyword)
seq(alias(ci('VAR'), $.keyword), alias(ci('RETAIN'), $.keyword)),
alias(ci('VAR_RETAIN'), $.keyword)
),
repeat(choice($.var_item, $.block_comment)),
alias('END_VAR', $.keyword)
alias(ci('END_VAR'), $.keyword)
),
var_non_retain_declaration: $ => seq(
choice(
seq(alias('VAR', $.keyword), alias('NON_RETAIN', $.keyword)),
alias('VAR_NON_RETAIN', $.keyword)
seq(alias(ci('VAR'), $.keyword), alias(ci('NON_RETAIN'), $.keyword)),
alias(ci('VAR_NON_RETAIN'), $.keyword)
),
repeat(choice($.var_item, $.block_comment)),
alias('END_VAR', $.keyword)
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(':=', $.expression)),
optional(seq(':=', choice($.expression, $.struct_initializer))),
';'
),
// --- Types ---
type: $ => choice(
alias('BOOL', $.type_builtin),
alias('Bool', $.type_builtin),
alias('INT', $.type_builtin),
alias('Int', $.type_builtin),
alias('int', $.type_builtin),
alias('DINT', $.type_builtin),
alias('DInt', $.type_builtin),
alias('dint', $.type_builtin),
alias('SINT', $.type_builtin),
alias('SInt', $.type_builtin),
alias('sint', $.type_builtin),
alias('USINT', $.type_builtin),
alias('USInt', $.type_builtin),
alias('usint', $.type_builtin),
alias('UINT', $.type_builtin),
alias('UInt', $.type_builtin),
alias('uint', $.type_builtin),
alias('WORD', $.type_builtin),
alias('Word', $.type_builtin),
alias('word', $.type_builtin),
alias('DWORD', $.type_builtin),
alias('DWord', $.type_builtin),
alias('dword', $.type_builtin),
alias('BYTE', $.type_builtin),
alias('Byte', $.type_builtin),
alias('byte', $.type_builtin),
alias('REAL', $.type_builtin),
alias('Real', $.type_builtin),
alias('real', $.type_builtin),
alias('LREAL', $.type_builtin),
alias('LReal', $.type_builtin),
alias('lreal', $.type_builtin),
alias('TIME', $.type_builtin),
alias('Time', $.type_builtin),
alias('time', $.type_builtin),
alias('TOD', $.type_builtin),
alias('Tod', $.type_builtin),
alias('tod', $.type_builtin),
alias('DT', $.type_builtin),
alias('Dt', $.type_builtin),
alias('dt', $.type_builtin),
alias('String', $.type_builtin),
alias('STRING', $.type_builtin),
alias('string', $.type_builtin),
seq(choice(alias('String', $.type_builtin), alias('STRING', $.type_builtin), alias('string', $.type_builtin)), '[', $.number, ']'),
alias('WString', $.type_builtin),
alias('WSTRING', $.type_builtin),
alias('wstring', $.type_builtin),
seq(choice(alias('WString', $.type_builtin), alias('WSTRING', $.type_builtin), alias('wstring', $.type_builtin)), '[', $.number, ']'),
alias('Char', $.type_builtin),
alias('CHAR', $.type_builtin),
alias('char', $.type_builtin),
alias('WChar', $.type_builtin),
alias('WCHAR', $.type_builtin),
alias('wchar', $.type_builtin),
seq(alias('ARRAY', $.keyword), '[', $.number, '..', $.number, ']', alias('OF', $.keyword), $.type),
$.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,
@@ -258,6 +345,8 @@ module.exports = grammar({
$.repeat_statement,
$.region_statement,
$.function_call,
$.return_statement,
$.goto_statement,
$.exit_statement,
$.continue_statement,
$.empty_statement,
@@ -265,17 +354,18 @@ module.exports = grammar({
$.var_temp_declaration,
$.var_constant_declaration,
$.var_retain_declaration,
$.var_non_retain_declaration
$.var_non_retain_declaration,
$.var_db_specific_declaration
),
empty_statement: $ => ';',
// --- FB calls ---
fb_call: $ => seq(
field('instance', choice($.prefixed_identifier, $.identifier)),
'(',
optional(seq(
$.fb_parameter_list
)),
optional($.fb_parameter_list),
')'
),
@@ -289,186 +379,279 @@ module.exports = grammar({
choice(':=', '=>'),
$.expression
),
// --- Assignments (including compound and chained) ---
assignment: $ => seq(
field('target', $.lvalue),
':=',
field('value', $.expression),
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
$.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('IF', $.keyword),
alias(ci('IF'), $.keyword),
$.expression,
alias('THEN', $.keyword),
alias(ci('THEN'), $.keyword),
repeat($.statement),
repeat($.elsif_clause),
optional($.else_clause),
alias('END_IF', $.keyword)
alias(ci('END_IF'), $.keyword)
),
elsif_clause: $ => seq(
alias('ELSIF', $.keyword),
alias(ci('ELSIF'), $.keyword),
$.expression,
alias('THEN', $.keyword),
alias(ci('THEN'), $.keyword),
repeat($.statement)
),
else_clause: $ => seq(alias('ELSE', $.keyword), repeat($.statement)),
else_clause: $ => seq(alias(ci('ELSE'), $.keyword), repeat($.statement)),
case_statement: $ => seq(
alias('CASE', $.keyword),
alias(ci('CASE'), $.keyword),
$.expression,
alias('OF', $.keyword),
alias(ci('OF'), $.keyword),
repeat($.case_item),
optional($.else_clause),
alias('END_CASE', $.keyword),
optional(';')
alias(ci('END_CASE'), $.keyword)
),
case_item: $ => seq(
choice($.number, $.identifier, $.prefixed_identifier, $.range),
repeat1(seq(
choice($.number, $.string, $.identifier, $.prefixed_identifier, $.range),
optional(',')
)),
':',
repeat($.statement)
),
range: $ => seq($.number, '..', $.number),
for_statement: $ => seq(
alias('FOR', $.keyword),
$.identifier,
alias(ci('FOR'), $.keyword),
choice($.identifier, $.prefixed_identifier),
':=',
$.expression,
alias('TO', $.keyword),
alias(ci('TO'), $.keyword),
$.expression,
optional(seq(alias('BY', $.keyword), $.expression)),
alias('DO', $.keyword),
optional(seq(alias(ci('BY'), $.keyword), $.expression)),
alias(ci('DO'), $.keyword),
repeat($.statement),
alias('END_FOR', $.keyword)
alias(ci('END_FOR'), $.keyword)
),
while_statement: $ => seq(
alias('WHILE', $.keyword),
alias(ci('WHILE'), $.keyword),
$.expression,
alias('DO', $.keyword),
alias(ci('DO'), $.keyword),
repeat($.statement),
alias('END_WHILE', $.keyword)
alias(ci('END_WHILE'), $.keyword)
),
repeat_statement: $ => seq(
alias('REPEAT', $.keyword),
alias(ci('REPEAT'), $.keyword),
repeat($.statement),
alias('UNTIL', $.keyword),
alias(ci('UNTIL'), $.keyword),
$.expression,
alias('END_REPEAT', $.keyword)
alias(ci('END_REPEAT'), $.keyword)
),
region_statement: $ => seq(
alias('REGION', $.keyword),
alias(ci('REGION'), $.keyword),
$.region_name,
repeat($.statement),
alias('END_REGION', $.keyword),
alias(ci('END_REGION'), $.keyword),
optional($.region_name)
),
region_name: $ => repeat1(
choice(
$.identifier,
$.string,
'/',
'-'
)
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), ')')
),
function_call: $ => seq(
choice($.identifier, $.string),
'(',
optional(seq(
$.parameter_list,
optional(seq('=>', $.identifier))
)),
')'
),
method_callee: $ => seq($.lvalue, '.', $.identifier),
parameter_list: $ => seq(
$.parameter,
repeat(seq(',', $.parameter))
),
parameter: $ => seq($.identifier, ':=', $.expression),
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,
seq('(', $.expression, ')')
prec(1, seq('(', $.expression, ')'))
),
unary_expression: $ => prec.right(10, choice(
seq(alias('NOT', $.operator), $.expression),
seq(alias(ci('NOT'), $.operator), $.expression),
seq('+', $.expression),
seq('-', $.expression)
)),
binary_expression: $ => choice(
prec.left(2, seq($.expression, alias('OR', $.operator), $.expression)),
prec.left(3, seq($.expression, alias('XOR', $.operator), $.expression)),
prec.left(4, seq($.expression, alias('AND', $.operator), $.expression)),
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('MOD', $.operator)), $.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_]+/)),
time_value: $ => token(seq('T#', /\d+(_?\d)*(ms|s|min|h)/)),
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]/, '""')), '"')
),
string_type: $ => seq(
choice(alias('STRING', $.type_builtin), alias('String', $.type_builtin), alias('string', $.type_builtin), alias('WSTRING', $.type_builtin), alias('WString', $.type_builtin), alias('wstring', $.type_builtin)),
optional(seq('[', $.number, ']'))
),
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('WCHAR#', "'", /[^']/, "'")
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('*', /[^)]/))),
@@ -481,9 +664,12 @@ module.exports = grammar({
'*/)'
)),
exit_statement: $ => alias('EXIT', $.keyword),
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('')
continue_statement: $ => alias('CONTINUE', $.keyword)
}
});