UNPKG

tree-sitter-bos

Version:

DSL scripting language for controlling unit animations and state. Used by the Recoil engine.

412 lines (338 loc) 12.6 kB
/** * @file DSL scripting language for controlling unit animations and state in the Recoil engine * @author ChrisFloofyKitsune <chrisfloofykitsune@gmail.com> * @license GPL-2.0 */ const {PREC, c_rules, commaSep1, preprocIf, commaSep, preprocessor} = require("./c_grammar"); /// <reference types="tree-sitter-cli/dsl" /> // @ts-check function laxCasing(str) { return alias(new RegExp(str.replaceAll('-', '-?'), 'i'), str); } function kw(keyword, ...rules) { let argRules = rules.map(rule => { // if regex or string, pass through if (typeof rule === 'string') { return prec(2, laxCasing(rule)); } else if (rule instanceof RegExp) { return prec(2, rule); } // pass already labeled fields through if (rule.type === 'FIELD') { return prec(1, rule); } // otherwise, mark as argument return field(`argument`, rule); }); return prec(PREC.CALL + 1, seq( field('keyword', laxCasing(keyword)), ...argRules, ';' )); } const commonPreprocNodes = ($) => [ $.macro_name_statement, $.macro_call_statement, $.preproc_include, $.preproc_def, $.preproc_function_def, $.preproc_undef, $.preproc_line, $.preproc_directive, ] module.exports = grammar({ name: "bos", word: $ => $.identifier, conflicts: $ => [ [$._block_item, $.statement], [$.get_call], [$.varying, $.preproc_call_expression] ], extras: $ => [ /\s|\\\r?\n/, $.comment, ], inline: $ => [ $._expression_not_binary, $.speed_or_now, $._var_name, $._piece_name, $._func_name, $._arg_name, $._define_name, ], supertypes: $ => [ $.expression, $.preproc_expression, $.statement, $.keyword_statement, $.declaration, $.constant, $.varying, ], rules: { source_file: $ => repeat($._top_level_item), _top_level_item: $ => choice( $.declaration, alias($.preproc_if_top_level, $.preproc_if), alias($.preproc_ifdef_top_level, $.preproc_ifdef), ...commonPreprocNodes($), ';', ), _block_item: $ => choice( $.statement, $.preproc_if, $.preproc_ifdef, ...commonPreprocNodes($), ';', ), macro_name_statement: $ => seq( $._define_name, optional(';'), token.immediate(/\r?\n/), ), macro_call_statement: $ => prec(PREC.CALL, seq( $._macro_call_expression, optional(';'), token.immediate(/\r?\n/), )), declaration: $ => prec(10, choice( $.piece_declaration, $.static_var_declaration, $.function_declaration )), piece_declaration: $ => prec(10, seq( laxCasing('piece'), commaSep1(field("name", $._piece_name)), ';' )), static_var_declaration: $ => prec(10, seq( laxCasing('static-var'), commaSep1(field('name', $._var_name)), ';' )), function_declaration: $ => prec(10, seq( field("name", $._func_name), choice('()', seq('(', commaSep1(field("arg", $._arg_name)), ')')), field("body", $.compound_statement), )), compound_statement: $ => seq( '{', repeat($._block_item), '}', ), statement: $ => choice( $.compound_statement, $.keyword_statement, $.var_statement, $.if_statement, $.while_statement, $.assign_statement, $.return_statement, ';', ), var_statement: $ => seq( laxCasing('var'), commaSep1($._var_name), ';' ), if_statement: $ => prec.left(seq( laxCasing('if'), '(', field("condition", $.expression), ')', field("then", $.statement), optional(seq(laxCasing('else'), field("else", $.statement))), )), while_statement: $ => seq( laxCasing('while'), '(', field("condition", $.expression), ')', field("body", $.statement), ), assign_statement: $ => choice( seq(field("name", $._var_name), '=', field("value", $.expression), ';'), $.increment_statement, $.decrement_statement, ), increment_statement: $ => seq( '++', field("name", $._var_name), ';' ), decrement_statement: $ => seq( '--', field("name", $._var_name), ';' ), return_statement: $ => seq( laxCasing('return'), optional($.expression), ';' ), keyword_statement: $ => prec.left(11, choice( $.call_script_statement, $.start_script_statement, $.signal_statement, $.set_signal_mask_statement, $.sleep_statement, $.set_statement, $.get_statement, $.spin_statement, $.stop_spin_statement, $.turn_statement, $.move_statement, $.wait_for_turn_statement, $.wait_for_move_statement, $.hide_statement, $.show_statement, // $.play_sound_statement, $.emit_sfx_statement, $.explode_statement, $.attach_unit_statement, $.drop_unit_statement, $.cache_statement, $.dont_cache_statement, $.dont_shadow_statement, $.dont_shade_statement, )), axis: _ => choice( /x-?axis/i, /y-?axis/i, /z-?axis/i ), argument_list: $ => seq( '(', commaSep($.expression), ')', ), call_script_statement: $ => choice( kw('call-script', field('function', $._func_name), field('arguments', $.argument_list)) ), start_script_statement: $ => choice( kw('start-script', field('function', $._func_name), field('arguments', $.argument_list)), ), signal_statement: $ => kw('signal', $.expression), set_signal_mask_statement: $ => kw('set-signal-mask', $.expression), sleep_statement: $ => kw('sleep', $.expression), set_statement: $ => kw('set', $.expression, 'to', $.expression), get_statement: $ => kw('get', field('call', $.get_call)), spin_statement: $ => kw( 'spin', field('piece', $._piece_name), 'around', field('axis', $.axis), 'speed', $.expression, optional(seq('accelerate', $.expression)) ), stop_spin_statement: $ => kw( 'stop-spin', field('piece', $._piece_name), 'around', field('axis', $.axis), optional(seq('decelerate', $.expression)) ), speed_or_now: $ => choice('now', seq('speed', $.expression)), turn_statement: $ => kw('turn', field('piece', $._piece_name), 'to', field('axis', $.axis), $.expression, $.speed_or_now), move_statement: $ => kw('move', field('piece', $._piece_name), 'to', field('axis', $.axis), $.expression, $.speed_or_now), wait_for_turn_statement: $ => kw('wait-for-turn', field('piece', $._piece_name), 'around', field('axis', $.axis),), wait_for_move_statement: $ => kw('wait-for-move', field('piece', $._piece_name), 'along', field('axis', $.axis),), hide_statement: $ => kw('hide', field('piece', $._piece_name)), show_statement: $ => kw('show', field('piece', $._piece_name)), emit_sfx_statement: $ => kw('emit-sfx', $.expression, 'from', field('piece', $._piece_name)), // play_sound_statement: $ => undefined, explode_statement: $ => kw('explode', field('piece', $._piece_name), 'type', $.expression), attach_unit_statement: $ => kw('attach-unit', $.expression, 'to', $.expression), drop_unit_statement: $ => kw('drop-unit', $.expression), cache_statement: $ => kw('cache', field('piece', $._piece_name)), dont_cache_statement: $ => kw('dont-cache', field('piece', $._piece_name)), shade_statement: $ => kw('shade', field('piece', $._piece_name)), dont_shade_statement: $ => kw('dont-shade', field('piece', $._piece_name)), dont_shadow_statement: $ => kw('dont-shadow', field('piece', $._piece_name)), expression: $ => choice( $._expression_not_binary, $.binary_expression, ), _expression_not_binary: $ => choice( $.parenthesized_expression, $._macro_call_expression, $.constant, $.varying, $.unary_expression, $.true, $.false, ), parenthesized_expression: $ => seq('(', $.expression, ')',), _macro_call_expression: $ => alias($.preproc_call_expression, $.macro_call_expression), get_term: $ => seq(laxCasing('get'), $.get_call), unary_expression: $ => prec.left(PREC.UNARY, seq( field('operator', alias(choice('!', /not/i), '!')), field('argument', $.expression), )), binary_expression: $ => { const table = [ ['+', PREC.ADD], ['-', PREC.ADD], ['*', PREC.MULTIPLY], ['/', PREC.MULTIPLY], ['%', PREC.MULTIPLY], [alias(choice('^^', /xor/i), '^^'), PREC.LOGICAL_XOR], [alias(choice('||', /or/i), '||'), PREC.LOGICAL_OR], [alias(choice('&&', /and/i), '&&'), PREC.LOGICAL_AND], ['^', PREC.EXCLUSIVE_OR], ['|', PREC.INCLUSIVE_OR], ['&', PREC.BITWISE_AND], ['==', PREC.EQUAL], ['!=', PREC.EQUAL], ['>', PREC.RELATIONAL], ['>=', PREC.RELATIONAL], ['<=', PREC.RELATIONAL], ['<', PREC.RELATIONAL], // ['<<', PREC.SHIFT], // ['>>', PREC.SHIFT], ]; return choice(...table.map(([operator, precedence]) => { return prec.left(precedence, seq( field('left', $.expression), // @ts-ignore field('operator', operator), field('right', $.expression), )); })); }, rand_call: $ => seq( laxCasing('rand'), '(', field('lower_bound', $.expression), ',', field('upper_bound', $.expression), ')' ), get_call: $ => choice( field('value_index', $.expression), seq(field('value_index', $.expression), '(', field('arg', $.expression), optional(seq(',', field('arg', $.expression))), optional(seq(',', field('arg', $.expression))), optional(seq(',', field('arg', $.expression))), ')' ), ), varying: $ => choice( $.rand_call, $.get_term, prec.dynamic(1, alias($._var_name, $.var_name_term)), ), constant: $ => choice( $.linear_constant, $.degree_constant, $.number_literal, ), linear_constant: $ => seq('[', choice($.number_literal, $.identifier), ']'), degree_constant: $ => seq('<', choice($.number_literal, $.identifier), '>'), identifier: _ => /[a-z_][a-z_0-9]*/i, _var_name: $ => alias($.identifier, $.var_name), _piece_name: $ => alias($.identifier, $.piece_name), _func_name: $ => alias($.identifier, $.func_name), _arg_name: $ => alias($.identifier, $.arg_name), _define_name: $ => alias($.identifier, $.define_name), ...c_rules, ...preprocIf("", $ => $._block_item), ...preprocIf("_top_level", $ => $._top_level_item), preproc_line: $ => seq( preprocessor('line'), field('lineno', $.number_literal), optional(field('filename', $.string_literal)), token.immediate(/\r?\n/), ) } });