const listSeq = (rule, separator, trailing_separator = false) =>
trailing_separator ?
seq(rule, repeat(seq(separator, rule)), optional(separator)) :
seq(rule, repeat(seq(separator, rule)));
function commaSep1(rule, trailing_separator = false) {
return listSeq(rule, ',', trailing_separator);
}
function commaSep(rule, trailing_separator = false) {
return optional(commaSep1(rule, trailing_separator));
}
function opt_grouping(grouping, rule) {
return choice(grouping(rule), rule);
}
function parens(rule) {
return seq('(', rule, ')');
}
function brackets(rule) {
return seq('[', rule, ']');
}
function curlies(rule) {
return seq('{', rule, '}');
}
function bodied_block(keyword, effect, block) {
return seq(keyword, effect, field('body', block));
}
function binary_operator(operator, rule) {
return seq(field('left', rule), operator, field('right', rule));
}
module.exports = grammar({
name: 'openscad',
extras: $ => [
$.comment,
/\s/,
],
supertypes: $ => [
$.literal,
$.expression,
$.number,
],
word: $ => $.identifier,
rules: {
source_file: $ => repeat(choice($.use_statement, $._item)),
_item: $ => choice(
seq($.assignment, ';'),
$._statement,
$.module_declaration,
$.function_declaration,
),
module_declaration: $ => seq(
'module',
field('name', $.identifier),
field('parameters', $.parameters_declaration),
field('body', $._statement),
),
parameters_declaration: $ => parens(seq(commaSep($._parameter_declaration), optional(','))),
_parameter_declaration: $ => choice(alias($._variable_name, $.parameter), $.assignment),
function_declaration: $ => seq(
'function',
field('name', $.identifier),
field('parameters', $.parameters_declaration),
'=', $.expression,
),
_statement: $ => choice(
$.for_block,
$.intersection_for_block,
$.if_block,
$.let_block,
$.assign_block,
$.union_block,
$.modifier_chain,
$.transform_chain,
$.include_statement,
$.assert_statement,
';',
),
include_statement: $ => seq('include', $.include_path),
use_statement: $ => seq('use', $.include_path),
include_path: _ => /<[^>]*>/,
assignment: $ => seq(
field('left', $._variable_name),
'=',
field('right', $.expression),
),
union_block: $ => curlies(repeat($._item)),
for_block: $ => bodied_block(
'for',
$.parenthesized_assignments,
$._statement,
),
intersection_for_block: $ => bodied_block(
'intersection_for',
$.parenthesized_assignments,
$._statement,
),
let_block: $ => bodied_block(
'let',
$.parenthesized_assignments,
$._statement,
),
assign_block: $ => bodied_block(
'assign',
$.parenthesized_assignments,
$._statement,
),
if_block: $ => prec.right(seq(
'if',
field('condition', $.parenthesized_expression),
field('consequence', $._statement),
optional(field('alternative', seq('else', $._statement))),
)),
modifier_chain: $ => seq($.modifier, $._statement),
modifier: _ => choice('*', '!', '#', '%'),
transform_chain: $ => seq($.module_call, $._statement),
module_call: $ => seq(
field('name', $.identifier),
field('arguments', $.arguments),
),
arguments: $ => parens(commaSep(choice($.expression, $.assignment), true)),
parenthesized_assignments: $ => parens(commaSep($.assignment)),
parenthesized_expression: $ => parens($.expression),
condition_update_clause: $ => parens(seq(
field('initializer', commaSep($.assignment)), ';',
field('condition', $.expression), ';',
field('update', commaSep($.assignment)),
)),
expression: $ => choice(
$.parenthesized_expression,
$.unary_expression,
$.binary_expression,
$.ternary_expression,
$.let_expression,
$.function_call,
$.index_expression,
$.dot_index_expression,
$.assert_expression,
$.literal,
$._variable_name,
),
let_expression: $ => bodied_block('let', $.parenthesized_assignments, $.expression),
literal: $ => choice(
$.string,
$.number,
$.boolean,
$.undef,
$.function,
$.range,
$.list,
),
function: $ => seq(
'function',
field('parameters', $.parameters_declaration),
field('body', $.expression),
),
range: $ => brackets(seq(
field('start', $.expression),
optional(seq(':', field('increment', $.expression))),
':', field('end', $.expression),
)),
list: $ => brackets(seq(commaSep($._list_cell, true))),
_list_cell: $ => choice($.expression, $.each, $.list_comprehension),
_comprehension_cell: $ => choice(
$.expression,
opt_grouping(parens, $.each),
opt_grouping(parens, $.list_comprehension),
),
each: $ => seq('each', choice($.expression, $.list_comprehension)),
list_comprehension: $ => seq(
choice($.for_clause, $.if_clause),
),
for_clause: $ => seq('for',
choice($.parenthesized_assignments, $.condition_update_clause),
$._comprehension_cell,
),
if_clause: $ => prec.right(seq(
'if',
field('condition', $.parenthesized_expression),
field('consequence', $._comprehension_cell),
optional(
seq('else', field('alternative', $._comprehension_cell)),
),
)),
function_call: $ => prec(10,
seq(
field('function', $.expression),
field('arguments', $.arguments),
)),
index_expression: $ => prec(10, seq(
field('value', $.expression),
brackets(field('index', $.expression)),
)),
dot_index_expression: $ => prec(10, seq(
field('value', $.expression), '.',
field('index', $.identifier),
)),
unary_expression: $ => choice(
prec(9, seq('!', $.expression)),
prec.left(6, seq('-', $.expression)),
prec.left(6, seq('+', $.expression)),
),
binary_expression: $ => choice(
prec.left(2, binary_operator('||', $.expression)),
prec.left(3, binary_operator('&&', $.expression)),
prec.left(4, binary_operator('==', $.expression)),
prec.left(4, binary_operator('!=', $.expression)),
prec.left(5, binary_operator('<', $.expression)),
prec.left(5, binary_operator('>', $.expression)),
prec.left(5, binary_operator('<=', $.expression)),
prec.left(5, binary_operator('>=', $.expression)),
prec.left(6, binary_operator('+', $.expression)),
prec.left(6, binary_operator('-', $.expression)),
prec.left(7, binary_operator('*', $.expression)),
prec.left(7, binary_operator('/', $.expression)),
prec.left(7, binary_operator('%', $.expression)),
prec.left(8, binary_operator('^', $.expression)),
),
ternary_expression: $ => prec.right(1, seq(
field('condition', $.expression), '?',
field('consequence', $.expression), ':',
field('alternative', $.expression),
)),
_assert_clause: $ => seq('assert', parens(
optional(seq(field('condition', $.expression),
optional(seq(',', field('message', $.expression),
optional(seq(',', field('trailing_args', commaSep1($.expression)))),
)),
)),
)),
assert_statement: $ => seq($._assert_clause, $._statement),
assert_expression: $ => seq($._assert_clause, $.expression),
identifier: _ => /[a-zA-Z_]\w*/,
special_variable: $ => seq('$', $.identifier),
_variable_name: $ => choice($.identifier, $.special_variable),
string: _ => token(seq('"', repeat(choice(/[^"]/, '\\"')), '"')),
number: $ => choice($.decimal, $.float),
decimal: _ => token(/-?\d+/),
float: _ => token(/-?(\d+(\.\d+)?|\.\d+)(e-?\d+)?/),
boolean: _ => choice('true', 'false'),
undef: _ => 'undef',
comment: _ => token(choice(
seq('//', /(\\(.|\r?\n)|[^\\\n])*/),
seq(
'/*',
/[^*]*\*+([^/*][^*]*\*+)*/,
'/',
),
)),
},
});