/** * Based in part on: * * https://github.com/tree-sitter/tree-sitter-c * MIT license, Copyright (c) 2014 Max Brunsfeld * * https://github.com/zephyrproject-rtos/python-devicetree/tree/main * BSD 3-Clause license, Copyright (c) 2019, Nordic Semiconductor */ /// // @ts-check const PREC = { PAREN_DECLARATOR: -10, ASSIGNMENT: -1, CONDITIONAL: -2, DEFAULT: 0, LOGICAL_OR: 1, LOGICAL_AND: 2, INCLUSIVE_OR: 3, EXCLUSIVE_OR: 4, BITWISE_AND: 5, EQUAL: 6, RELATIONAL: 7, SIZEOF: 8, SHIFT: 9, ADD: 10, MULTIPLY: 11, CAST: 12, UNARY: 13, CALL: 14, FIELD: 15, SUBSCRIPT: 16, }; module.exports = grammar({ name: 'devicetree', extras: ($) => [/\s|\\\r?\n/, $.comment], inline: ($) => [ $.node_identifier, $.label_identifier, $.property_identifier, $.parenthesized_expression, ], rules: { document: ($) => repeat($._top_level_item), _top_level_item: ($) => choice( $.file_version, $.plugin, $.memory_reservation, $.omit_if_no_ref, $.node, $.dtsi_include, $.delete_node, $.preproc_include, $.preproc_def, $.preproc_function_def, $.preproc_if, $.preproc_ifdef, $.preproc_undef ), _label: ($) => seq(field('label', $.label_identifier), ':'), file_version: ($) => seq('/dts-v1/', ';'), plugin: ($) => seq('/plugin/', ';'), memory_reservation: ($) => seq( repeat($._label), '/memreserve/', field('address', $.integer_literal), field('length', $.integer_literal), ';' ), // http://stackoverflow.com/questions/13014947/regex-to-match-a-c-style-multiline-comment/36328890#36328890 comment: ($) => token( choice( seq('//', /(\\(.|\r?\n)|[^\\\n])*/), seq('/*', /[^*]*\*+([^/*][^*]*\*+)*/, '/') ) ), _label_name: ($) => /[a-zA-Z_][0-9a-zA-Z_]*/, _node_path: ($) => /\/[0-9a-zA-Z/,._+-]*/, _node_or_property: ($) => /[a-zA-Z][0-9a-zA-Z,._+-]*/, _property_with_hash: ($) => /[#0-9a-zA-Z,._+-]*#[#0-9a-zA-Z,._+-]*/, _property_starts_with_number: ($) => /[0-9][#0-9a-zA-Z,._+-]*/, unit_address: ($) => /[0-9a-zA-Z,._+-]+/, label_identifier: ($) => alias($._label_name, $.identifier), node_identifier: ($) => alias( choice($._node_or_property, $._node_path, $._label_name), $.identifier ), property_identifier: ($) => alias( choice( $._node_or_property, $._property_with_hash, $._property_starts_with_number, $._label_name ), $.identifier ), reference: ($) => choice($._label_reference, $._node_reference), _label_reference: ($) => seq('&', field('label', $.label_identifier)), _node_reference: ($) => seq( '&{', field('path', $.node_identifier), field('address', optional(seq('@', $.unit_address))), '}' ), omit_if_no_ref: ($) => seq('/omit-if-no-ref/', choice($.node, seq($.reference, ';'))), node: ($) => seq( repeat($._label), choice( field('name', $.reference), seq( field('name', $.node_identifier), field('address', optional(seq('@', $.unit_address))) ) ), '{', repeat($._node_members), '}', ';' ), _bits: ($) => seq('/bits/', $.integer_literal), property: ($) => seq( repeat($._label), field('name', $.property_identifier), optional( seq( '=', field('bits', optional($._bits)), field('value', commaSep($._property_value)) ) ), ';' ), _node_members: ($) => choice( $.delete_property, $.delete_node, $.omit_if_no_ref, $.node, $.property, $.preproc_include, $.preproc_def, $.preproc_function_def, alias($.preproc_if_in_node, $.preproc_if), alias($.preproc_ifdef_in_node, $.preproc_ifdef), $.preproc_undef ), delete_node: ($) => seq( token(prec(2, '/delete-node/')), choice( field('name', $.reference), seq( field('name', $.node_identifier), field('address', optional(seq('@', $.unit_address))) ) ), ';' ), delete_property: ($) => seq( token(prec(2, '/delete-property/')), field('name', $.property_identifier), ';' ), incbin: ($) => seq( '/incbin/', '(', field('filename', $.string_literal), optional( seq( ',', field('offset', $._integer_cell_items), ',', field('size', $._integer_cell_items) ) ), ')' ), // TODO: labels can appear before or after any component of a property value, // or between cells of a cell array, or between bytes of a bytestring. _property_value: ($) => choice( $.integer_cells, $.string_literal, $.byte_string_literal, $.reference, $.incbin ), integer_cells: ($) => seq('<', repeat($._integer_cell_items), '>'), _integer_cell_items: ($) => choice( $.integer_literal, $.reference, $.parenthesized_expression, $.identifier, $.call_expression ), string_literal: ($) => seq( '"', repeat( choice( token.immediate(prec(1, /[^\\"\n]+/)), $.escape_sequence ) ), '"' ), escape_sequence: ($) => token( prec( 1, seq( '\\', choice( /[^xuU]/, /\d{2,3}/, /x[0-9a-fA-F]{2,}/, /u[0-9a-fA-F]{4}/, /U[0-9a-fA-F]{8}/ ) ) ) ), system_lib_string: ($) => token(seq('<', repeat(choice(/[^>\n]/, '\\>')), '>')), byte_string_literal: ($) => seq('[', repeat($._byte_string_item), ']'), _byte_string_item: ($) => /[0-9a-fA-F]+/, integer_literal: ($) => { const separator = "'"; const hex = /[0-9a-fA-F]/; const decimal = /[0-9]/; const hexDigits = seq( repeat1(hex), repeat(seq(separator, repeat1(hex))) ); const decimalDigits = seq( repeat1(decimal), repeat(seq(separator, repeat1(decimal))) ); return token( choice( decimalDigits, seq('0b', decimalDigits), seq('0x', hexDigits) ) ); }, identifier: ($) => /[a-zA-Z_]\w*/, _expression: ($) => choice( $.conditional_expression, $.binary_expression, $.unary_expression, $.call_expression, $.identifier, $.integer_literal, $.parenthesized_expression ), parenthesized_expression: ($) => seq('(', $._expression, ')'), call_expression: ($) => prec( PREC.CALL, seq( field('function', $.identifier), field('arguments', $.argument_list) ) ), argument_list: ($) => seq('(', commaSep($._expression), ')'), conditional_expression: ($) => prec.right( PREC.CONDITIONAL, seq( field('condition', $._expression), '?', field('consequence', $._expression), ':', field('alternative', $._expression) ) ), unary_expression: ($) => prec.left( PREC.UNARY, seq( field('operator', choice('!', '~', '-', '+')), field('argument', $._expression) ) ), binary_expression: ($) => { /** @type {[string, number][]} */ const table = [ ['+', PREC.ADD], ['-', PREC.ADD], ['*', PREC.MULTIPLY], ['/', PREC.MULTIPLY], ['%', PREC.MULTIPLY], ['||', PREC.LOGICAL_OR], ['&&', PREC.LOGICAL_AND], ['|', PREC.INCLUSIVE_OR], ['^', PREC.EXCLUSIVE_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), field('operator', operator), field('right', $._expression) ) ); }) ); }, dtsi_include: ($) => seq('/include/', field('path', $.string_literal)), preproc_include: ($) => seq( preprocessor('include'), field( 'path', choice($.string_literal, $.system_lib_string, $.identifier) ), token.immediate(/\r?\n/) ), preproc_def: ($) => seq( preprocessor('define'), field('name', $.identifier), field('value', optional($.preproc_arg)), token.immediate(/\r?\n/) ), preproc_function_def: ($) => seq( preprocessor('define'), field('name', $.identifier), field('parameters', $.preproc_params), field('value', optional($.preproc_arg)), token.immediate(/\r?\n/) ), preproc_params: ($) => seq( token.immediate('('), commaSep(choice($.identifier, '...')), ')' ), preproc_undef: ($) => seq( preprocessor('undef'), field('name', $.identifier), token.immediate(/\r?\n/) ), preproc_arg: ($) => token(prec(-1, repeat1(/.|\\\r?\n/))), ...preprocIf('', ($) => $._top_level_item), ...preprocIf('_in_node', ($) => $._node_members), _preproc_expression: ($) => choice( $.identifier, alias($.preproc_call_expression, $.call_expression), $.integer_literal, $.preproc_defined, alias($.preproc_unary_expression, $.unary_expression), alias($.preproc_binary_expression, $.binary_expression), alias( $.preproc_parenthesized_expression, $.parenthesized_expression ) ), preproc_parenthesized_expression: ($) => seq('(', $._preproc_expression, ')'), preproc_defined: ($) => choice( prec(PREC.CALL, seq('defined', '(', $.identifier, ')')), seq('defined', $.identifier) ), preproc_unary_expression: ($) => prec.left( PREC.UNARY, seq( field('operator', choice('!', '~', '-', '+')), field('argument', $._preproc_expression) ) ), preproc_call_expression: ($) => prec( PREC.CALL, seq( field('function', $.identifier), field( 'arguments', alias($.preproc_argument_list, $.argument_list) ) ) ), preproc_argument_list: ($) => seq('(', commaSep($._preproc_expression), ')'), preproc_binary_expression: ($) => { const table = [ ['+', PREC.ADD], ['-', PREC.ADD], ['*', PREC.MULTIPLY], ['/', PREC.MULTIPLY], ['%', PREC.MULTIPLY], ['||', PREC.LOGICAL_OR], ['&&', PREC.LOGICAL_AND], ['|', PREC.INCLUSIVE_OR], ['^', PREC.EXCLUSIVE_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', $._preproc_expression), // @ts-ignore field('operator', operator), field('right', $._preproc_expression) ) ); }) ); }, }, }); /** * @param {string} command */ function preprocessor(command) { return alias( token(prec(2, new RegExp('#[ \t]*' + command))), '#' + command ); } /** * @param {RuleOrLiteral} rule */ function commaSep(rule) { return optional(commaSep1(rule)); } /** * @param {RuleOrLiteral} rule */ function commaSep1(rule) { return seq(rule, repeat(seq(',', rule))); } /** * @param {string} suffix * @param {RuleBuilder} content * @returns {RuleBuilders} */ function preprocIf(suffix, content) { /** * @param {GrammarSymbols} $ * @returns {ChoiceRule} */ function elseBlock($) { return choice( suffix ? alias($['preproc_else' + suffix], $.preproc_else) : $.preproc_else, suffix ? alias($['preproc_elif' + suffix], $.preproc_elif) : $.preproc_elif ); } return { ['preproc_if' + suffix]: ($) => seq( preprocessor('if'), field('condition', $._preproc_expression), '\n', repeat(content($)), field('alternative', optional(elseBlock($))), preprocessor('endif') ), ['preproc_ifdef' + suffix]: ($) => seq( choice(preprocessor('ifdef'), preprocessor('ifndef')), field('name', $.identifier), repeat(content($)), field( 'alternative', optional(choice(elseBlock($), $.preproc_elifdef)) ), preprocessor('endif') ), ['preproc_else' + suffix]: ($) => seq(preprocessor('else'), repeat(content($))), ['preproc_elif' + suffix]: ($) => seq( preprocessor('elif'), field('condition', $._preproc_expression), '\n', repeat(content($)), field('alternative', optional(elseBlock($))) ), ['preproc_elifdef' + suffix]: ($) => seq( choice(preprocessor('elifdef'), preprocessor('elifndef')), field('name', $.identifier), repeat(content($)), field('alternative', optional(elseBlock($))) ), }; }