tree-sitter-cfengine 1.1.12

CFEngine grammar for tree-sitter
Documentation
/**
 * @file CFEngine grammar for tree-sitter
 * @author Ole Herman Schumacher Elgesem <oleherman93@gmail.com>
 * @license MIT
 */

// Regexes defined here so we can concatenate them without creating rules in the grammar;
const IDENTIFIER = /[a-zA-Z0-9_]+/;
const QUALIFIED_IDENTIFIER =
  /([a-zA-Z0-9_]+\:)?([a-zA-Z0-9_]+\.)?[a-zA-Z0-9_]+/;
const PROMISE_GUARD = /[a-zA-Z_]+:/;
const CLASS_EXPRESSION = /[.|&!()a-zA-Z0-9_:][\t .|&!()a-zA-Z0-9_:]*/;
const SQUOTE = /\'((\\(.|\n))|[^'\\])*\'/;
const DQUOTE = /\"((\\(.|\n))|[^"\\])*\"/;
const BQUOTE = /`[^`]*`/;
const QUOTED_STRING = RegExp(
  '(' + SQUOTE.source + ')|(' + DQUOTE.source + ')|(' + BQUOTE.source + ')',
);
const CLASS_GUARD = RegExp(
  '((' + QUOTED_STRING.source + ')|(' + CLASS_EXPRESSION.source + '))::',
);

/// <reference types="tree-sitter-cli/dsl" />
// @ts-check

module.exports = grammar({
  name: 'cfengine',

  extras: ($) => [$.comment, $.macro, /[\s]+/],
  word: ($) => $.identifier,

  rules: {
    source_file: ($) => repeat($._block),

    _block: ($) => choice($.bundle_block, $.body_block, $.promise_block),

    body_block: ($) =>
      seq(
        $.body_block_keyword,
        alias($.identifier, $.body_block_type),
        alias($.identifier, $.body_block_name),
        optional($.parameter_list),
        $.body_block_body,
      ),
    body_block_keyword: (_) => 'body',
    body_block_body: ($) =>
      seq(
        '{',
        // 0 or more promises without a class guard:
        repeat($._body_attribute),
        // 0 or more class guards with 0 or more promises inside:
        repeat($.class_guarded_body_attributes),
        '}',
      ),

    _body_attribute: ($) => seq($.attribute, ';'),

    promise_block: ($) =>
      seq(
        $.promise_block_keyword,
        alias($.identifier, $.promise_block_type),
        alias($.identifier, $.promise_block_name),
        $.promise_block_body,
      ),
    promise_block_keyword: (_) => 'promise',
    promise_block_body: ($) =>
      seq(
        '{',
        // 0 or more promises without a class guard:
        repeat($._body_attribute),
        // 0 or more class guards with 0 or more promises inside:
        repeat(
          alias(
            $.class_guarded_body_attributes,
            $.class_guarded_promise_block_attributes,
          ),
        ),
        '}',
      ),

    class_guarded_body_attributes: ($) =>
      seq($.class_guard, repeat($._body_attribute)),

    bundle_block: ($) =>
      seq(
        $.bundle_block_keyword,
        alias($.identifier, $.bundle_block_type),
        alias($.identifier, $.bundle_block_name),
        optional($.parameter_list),
        $.bundle_block_body,
      ),
    bundle_block_keyword: (_) => 'bundle',

    parameter_list: ($) =>
      seq(
        '(',
        optional(
          seq(
            repeat(seq(alias($.identifier, $.parameter), ',')),
            alias($.identifier, $.parameter),
            optional(','),
          ),
        ),
        ')',
      ),

    bundle_block_body: ($) => seq('{', repeat($.bundle_section), '}'),

    bundle_section: ($) =>
      seq(
        $.promise_guard,
        // 0 or more promises without a class guard:
        optional($._promises),
        // 0 or more class guards with 0 or more promises inside:
        repeat($.class_guarded_promises),
      ),

    _bare_value: ($) =>
      choice(
        $.quoted_string,
        $.qualified_identifier,
        $.list,
        $.call,
        $.dollar_expression,
        $.at_expression,
      ),

    // An rval is normally a bare value,
    // but because of macros it can also end up being 2 bare values
    // without a comma between them. (One in each branch of the macro).
    _right_value: ($) =>
      choice($._bare_value, seq($._bare_value, $._bare_value)),

    _variable_reference: ($) =>
      choice($.qualified_identifier, $.indexed_identifier),

    dollar_expression: ($) =>
      choice(
        seq(
          alias('$', $.dollar_expression_operator),
          alias('(', $.dollar_expression_start),
          choice(
            alias($._variable_reference, $.dollar_expression_reference),
            $.dollar_expression,
          ),
          alias(')', $.dollar_expression_end),
        ),
        seq(
          alias('$', $.dollar_expression_operator),
          alias('{', $.dollar_expression_start),
          choice(
            alias($._variable_reference, $.dollar_expression_reference),
            $.dollar_expression,
          ),
          alias('}', $.dollar_expression_end),
        ),
      ),
    at_expression: ($) =>
      choice(
        seq(
          alias('@', $.at_expression_operator),
          alias('(', $.at_expression_start),
          alias($._variable_reference, $.at_expression_reference),
          alias(')', $.at_expression_end),
        ),
        seq(
          alias('@', $.at_expression_operator),
          alias('{', $.at_expression_start),
          alias($._variable_reference, $.at_expression_reference),
          alias('}', $.at_expression_end),
        ),
      ),

    calling_identifier: ($) =>
      choice(
        $.qualified_identifier,
        $.dollar_expression,
      ),
    call: ($) => seq($.calling_identifier, '(', optional($._value_list), ')'),

    list: ($) => seq('{', optional($._value_list), '}'),

    // inner part of a non-empty list of values
    // such as the arguments of a function call
    // or the strings in an slist
    _value_list: ($) =>
      seq($._right_value, repeat(seq(',', $._right_value)), optional(',')),

    //
    _double_value: ($) => seq($._right_value, $._right_value),

    class_guarded_promises: ($) => seq($.class_guard, optional($._promises)),

    _promises: ($) => seq($.promise, repeat(choice($.promise, $.half_promise))),

    promise: ($) =>
      seq(
        alias($.quoted_string, $.promiser),
        optional(seq('->', alias($._right_value, $.stakeholder))),
        optional($.attribute),
        repeat(seq(',', $.attribute)),
        optional(','),
        ';',
      ),

    // "Half promises" are not real, and shouldn't normally be allowed
    // in policy. However they can arise from using macros.
    // So this is sort of a hack to make the parser work better
    // when faced with macros.
    // TODO: resarch if there are better ways to handle macros
    half_promise: ($) =>
      seq($.attribute, repeat(seq(',', $.attribute)), optional(','), ';'),

    attribute: ($) =>
      seq(alias($.identifier, $.attribute_name), '=>', $._right_value),
    index: ($) =>
      seq(
        '[',
        choice(
          $.identifier,
          $.dollar_expression
        ),
        ']'
      ),
    indexed_identifier: ($) =>
      seq(
        $.qualified_identifier,
        $.index,
        repeat($.index)
      ),

    quoted_string: ($) => QUOTED_STRING,
    identifier: ($) => IDENTIFIER,
    qualified_identifier: ($) => QUALIFIED_IDENTIFIER,
    promise_guard: ($) => PROMISE_GUARD,
    class_guard: ($) => CLASS_GUARD,

    comment: ($) => token(seq('#', /.*/)),

    macro: ($) => token(/@(if |else|endif)[^\n]*/),
  },
});