module.exports = grammar({ name: 'scl', extras: $ => [ /\s/, $.line_comment, $.block_comment, $.c_style_comment ], conflicts: $ => [ [$.case_item], [$.region_statement], [$.region_name], [$.case_statement], [$.var_declaration, $.var_temp_declaration], [$.var_declaration, $.var_constant_declaration], [$.var_declaration, $.var_retain_declaration], [$.var_declaration, $.var_non_retain_declaration] ], rules: { source_file: $ => repeat( choice( $.organization_block, $.function_block, $.function, $.statement ) ), organization_block: $ => seq( 'ORGANIZATION_BLOCK', field('name', $.string), optional(seq('TITLE', '=', choice($.string, $.identifier))), optional($.attribute_list), optional(seq('VERSION', ':', $.number)), repeat($.var_declaration), 'BEGIN', repeat($.statement), 'END_ORGANIZATION_BLOCK' ), function_block: $ => seq( 'FUNCTION_BLOCK', field('name', $.string), optional(seq('TITLE', '=', choice($.string, $.identifier))), optional($.attribute_list), optional(seq('VERSION', ':', $.number)), repeat($.var_declaration), 'BEGIN', repeat($.statement), 'END_FUNCTION_BLOCK' ), function: $ => seq( 'FUNCTION', field('name', $.string), optional(seq(':', $.type)), optional(seq('TITLE', '=', choice($.string, $.identifier))), optional($.attribute_list), optional(seq('VERSION', ':', $.number)), repeat(choice( $.var_declaration, $.var_temp_declaration, $.var_constant_declaration, $.var_retain_declaration, $.var_non_retain_declaration, $.block_comment )), 'BEGIN', repeat($.statement), 'END_FUNCTION' ), attribute_list: $ => seq( '{', $.attribute, repeat(seq(choice(',', ';'), $.attribute)), optional(choice(',', ';')), '}' ), attribute: $ => seq( $.identifier, ':=', $.attribute_value ), attribute_value: $ => choice( $.string, 'TRUE', 'FALSE', $.identifier ), var_declaration: $ => seq( choice( 'VAR_INPUT', 'VAR_OUTPUT', 'VAR_IN_OUT', 'VAR', 'VAR_TEMP', seq('VAR', 'CONSTANT'), seq('VAR', 'RETAIN'), seq('VAR', 'NON_RETAIN'), 'VAR_CONSTANT', 'VAR_RETAIN', 'VAR_NON_RETAIN' ), repeat(choice($.var_item, $.block_comment)), 'END_VAR' ), var_temp_declaration: $ => seq( 'VAR_TEMP', repeat(choice($.var_item, $.block_comment)), 'END_VAR' ), var_constant_declaration: $ => seq( choice( seq('VAR', 'CONSTANT'), 'VAR_CONSTANT' ), repeat(choice($.var_item, $.block_comment)), 'END_VAR' ), var_retain_declaration: $ => seq( choice( seq('VAR', 'RETAIN'), 'VAR_RETAIN' ), repeat(choice($.var_item, $.block_comment)), 'END_VAR' ), var_non_retain_declaration: $ => seq( choice( seq('VAR', 'NON_RETAIN'), 'VAR_NON_RETAIN' ), repeat(choice($.var_item, $.block_comment)), 'END_VAR' ), var_item: $ => seq( field('name', $.identifier), optional($.attribute_list), ':', field('type', $.type), optional(seq(':=', $.expression)), ';' ), type: $ => choice( 'BOOL', 'Bool', 'INT', 'Int', 'int', 'DINT', 'DInt', 'dint', 'SINT', 'SInt', 'sint', 'USINT', 'USInt', 'usint', 'UINT', 'UInt', 'uint', 'WORD', 'Word', 'word', 'DWORD', 'DWord', 'dword', 'BYTE', 'Byte', 'byte', 'REAL', 'Real', 'real', 'LREAL', 'LReal', 'lreal', 'TIME', 'Time', 'time', 'TOD', 'Tod', 'tod', 'DT', 'Dt', 'dt', 'String', 'STRING', 'string', seq(choice('String', 'STRING', 'string'), '[', $.number, ']'), 'WString', 'WSTRING', 'wstring', seq(choice('WString', 'WSTRING', 'wstring'), '[', $.number, ']'), 'Char', 'CHAR', 'char', 'WChar', 'WCHAR', 'wchar', seq('ARRAY', '[', $.number, '..', $.number, ']', 'OF', $.type), $.identifier, $.string ), statement: $ => choice( $.assignment, $.if_statement, $.case_statement, $.for_statement, $.while_statement, $.repeat_statement, $.region_statement, $.function_call, $.exit_statement, $.continue_statement, $.empty_statement, $.var_declaration, $.var_temp_declaration, $.var_constant_declaration, $.var_retain_declaration, $.var_non_retain_declaration ), empty_statement: $ => ';', assignment: $ => seq( field('target', $.lvalue), ':=', field('value', $.expression), ';' ), lvalue: $ => choice( $.identifier, $.prefixed_identifier, $.field_access, $.array_access, $.function_name ), function_name: $ => $.string, prefixed_identifier: $ => seq('#', $.identifier), field_access: $ => seq($.lvalue, '.', choice($.identifier, $.prefixed_identifier)), array_access: $ => seq($.lvalue, '[', $.expression, ']'), if_statement: $ => seq( 'IF', $.expression, 'THEN', repeat($.statement), repeat($.elsif_clause), optional($.else_clause), 'END_IF' ), elsif_clause: $ => seq( 'ELSIF', $.expression, 'THEN', repeat($.statement) ), else_clause: $ => seq('ELSE', repeat($.statement)), case_statement: $ => seq( 'CASE', $.expression, 'OF', repeat($.case_item), optional($.else_clause), 'END_CASE', optional(';') ), case_item: $ => seq( choice($.number, $.identifier, $.prefixed_identifier, $.range), ':', repeat($.statement) ), range: $ => seq($.number, '..', $.number), for_statement: $ => seq( 'FOR', $.identifier, ':=', $.expression, 'TO', $.expression, optional(seq('BY', $.expression)), 'DO', repeat($.statement), 'END_FOR' ), while_statement: $ => seq( 'WHILE', $.expression, 'DO', repeat($.statement), 'END_WHILE' ), repeat_statement: $ => seq( 'REPEAT', repeat($.statement), 'UNTIL', $.expression, 'END_REPEAT' ), region_statement: $ => seq( 'REGION', $.region_name, repeat($.statement), 'END_REGION', optional($.region_name) ), region_name: $ => repeat1( choice( $.identifier, $.string, '/', '-' ) ), function_call: $ => seq( choice($.identifier, $.string), '(', optional(seq( $.parameter_list, optional(seq('=>', $.identifier)) )), ')' ), parameter_list: $ => seq( $.parameter, repeat(seq(',', $.parameter)) ), parameter: $ => seq($.identifier, ':=', $.expression), expression: $ => choice( $.number, $.hex_number, $.time_value, $.identifier, $.prefixed_identifier, $.field_access, $.array_access, $.string, $.function_call, $.unary_expression, $.binary_expression, seq('(', $.expression, ')') ), unary_expression: $ => prec.right(10, choice( seq('NOT', $.expression), seq('+', $.expression), seq('-', $.expression) )), binary_expression: $ => choice( prec.left(2, seq($.expression, 'OR', $.expression)), prec.left(3, seq($.expression, 'XOR', $.expression)), prec.left(4, seq($.expression, 'AND', $.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('*', '/', 'MOD'), $.expression)) ), number: $ => token(/\d+(\.\d+)?/), hex_number: $ => token(seq('16#', /[0-9A-Fa-f_]+/)), time_value: $ => token(seq('T#', /\d+(_?\d)*(ms|s|min|h)/)), string: $ => choice( seq("'", repeat(choice(/[^'\n]/, "''")), "'"), seq('"', repeat(choice(/[^"\n]/, '""')), '"') ), // Support for STRING type with length: STRING[80] string_type: $ => seq( choice('STRING', 'String', 'string', 'WSTRING', 'WString', 'wstring'), optional(seq('[', $.number, ']')) ), // Support for SCL character literals: 'A', WCHAR#'B' char_literal: $ => choice( seq("'", /[^']/, "'"), seq('WCHAR#', "'", /[^']/, "'") ), 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: $ => 'EXIT', continue_statement: $ => 'CONTINUE' } });