module.exports = grammar({ name: 'ql', conflicts: $ => [ [$.simpleId, $.className], [$.simpleId, $.literalId], [$.moduleName, $.varName], [$.simpleId, $.moduleInstantiation], [$.className, $.moduleInstantiation], ], extras: $ => [ /[ \t\r\n]/, $.line_comment, $.block_comment, ], word: $ => $._lower_id, rules: { ql: $ => repeat($.moduleMember), module: $ => seq( 'module', field("name", $.moduleName), optional( seq( "<", sep1(field('parameter', $.moduleParam), ","), ">" ) ), optional(seq( "implements", sep1(field('implements', $.signatureExpr), ",") )), choice( seq( "{", repeat($.moduleMember), "}" ), $.moduleAliasBody ) ), moduleMember: $ => choice( seq( repeat($.annotation), choice($.importDirective, $.classlessPredicate, $.dataclass, $.datatype, $.select, $.module) ), $.qldoc ), importDirective: $ => seq( 'import', $.importModuleExpr, optional(seq('as', $.moduleName)) ), moduleAliasBody: $ => seq('=', $.moduleExpr, ";"), predicateAliasBody: $ => seq('=', $.predicateExpr, ";"), typeAliasBody: $ => seq('=', $.typeExpr, ";"), typeUnionBody: $ => seq('=', $.typeExpr, "or", sep($.typeExpr, "or"), ";"), classlessPredicate: $ => seq( field("returnType", choice($.predicate, $.typeExpr)), field("name", $.predicateName), choice( seq("(", sep($.varDecl, ","), ")", $._optbody), $.predicateAliasBody ) ), datatype: $ => seq( 'newtype', field("name", $.className), '=', $.datatypeBranches ), datatypeBranches: $ => sep1($.datatypeBranch, "or"), datatypeBranch: $ => seq( optional($.qldoc), optional($.annotation), field("name", $.className), "(", sep($.varDecl, ","), ")", optional($.body) ), select: $ => seq( optional(seq("from", sep($.varDecl, ","))), optional(seq("where", $._exprOrTerm)), seq('select', $.asExprs, optional($.orderBys)) ), dataclass: $ => seq( 'class', field("name", $.className), choice( seq( optional(field("extends", seq('extends', sep1($.typeExpr, ",")))), optional(field("instanceof", seq('instanceof', sep1($.typeExpr, ",")))), choice( seq( "{", repeat($.classMember), "}" ), ";" ) ), $.typeAliasBody, $.typeUnionBody ) ), classMember: $ => choice( seq( repeat($.annotation), choice($.charpred, $.memberPredicate, $.field) ), $.qldoc ), charpred: $ => seq($.className, "(", ")", "{", field('body', $._exprOrTerm), "}"), memberPredicate: $ => seq( field("returnType", choice($.predicate, $.typeExpr)), field("name", $.predicateName), "(", sep($.varDecl, ","), ")", $._optbody ), field: $ => seq($.varDecl, ";"), _optbody: $ => choice( $.empty, $.body, $.higherOrderTerm ), empty: $ => ";", body: $ => seq("{", $._exprOrTerm, "}"), higherOrderTerm: $ => seq( '=', field("name", $.literalId), "(", sep($.predicateExpr, ","), ")", "(", sep($._call_arg, ","), ")" ), special_call: $ => seq($.specialId, "(", ")"), prefix_cast: $ => prec.dynamic(10, seq("(", $.typeExpr, ")", $._exprOrTerm)), unary_expr: $ => prec.left(9, seq($.unop, $._exprOrTerm)), mul_expr: $ => prec.left(9, seq( field('left', $._exprOrTerm), $.mulop, field('right', $._exprOrTerm) )), add_expr: $ => prec.left(8, seq( field('left', $._exprOrTerm), $.addop, field('right', $._exprOrTerm) )), in_expr: $ => prec.left(7, seq( field('left', $._exprOrTerm), 'in', field('right', $._primary) )), comp_term: $ => prec.left(6, seq( field('left', $._exprOrTerm), $.compop, field('right', $._exprOrTerm) )), instance_of: $ => prec.left(5, seq($._exprOrTerm, 'instanceof', $.typeExpr)), negation: $ => prec.left(4, seq('not', $._exprOrTerm)), if_term: $ => prec.left(3, seq( "if", field('cond', $._exprOrTerm), "then", field('first', $._exprOrTerm), "else", field('second', $._exprOrTerm) )), conjunction: $ => prec.left(3, seq( field('left', $._exprOrTerm), "and", field('right', $._exprOrTerm) )), disjunction: $ => prec.left(2, seq( field('left', $._exprOrTerm), "or", field('right', $._exprOrTerm) )), implication: $ => prec.left(1, seq( field('left', $._exprOrTerm), "implies", field('right', $._exprOrTerm) )), quantified: $ => seq($.quantifier, "(", choice( seq( sep($.varDecl, ","), optional(seq("|", field('range', $._exprOrTerm), optional(seq("|", field('formula', $._exprOrTerm))))) ), field('expr', $._exprOrTerm) ), ")"), specialId: $ => 'none', quantifier: $ => choice('exists', 'forall', 'forex'), _call_arg: $ => choice( $._exprOrTerm, // ExprArg $.underscore // DontCare ), underscore: $ => '_', qualifiedRhs: $ => choice( seq( // QualCall field("name", $.predicateName), optional($.closure), "(", sep($._call_arg, ","), ")" ), seq( // QualCast "(", $.typeExpr, ")" ) ), call_body: $ => seq("(", sep($._call_arg, ","), ")"), unqual_agg_body: $ => seq( "(", sep($.varDecl, ","), "|", field('guard', optional($._exprOrTerm)), field('asExprs', optional(seq("|", $.asExprs))), ")" ), _call_or_unqual_agg_body: $ => choice($.call_body, $.unqual_agg_body), call_or_unqual_agg_expr: $ => prec.dynamic(10, seq($.aritylessPredicateExpr, optional($.closure), $._call_or_unqual_agg_body)), qualified_expr: $ => seq($._primary, ".", $.qualifiedRhs), super_ref: $ => seq(optional(seq($.typeExpr, ".")), $.super), // The split here is to ensure that the node is non-empty full_aggregate_body: $ => choice( seq(sep($.varDecl, ","), seq( "|", field('guard', optional($._exprOrTerm)), optional(seq("|", field('asExprs', $.asExprs), field('orderBys', optional($.orderBys)))) ) ), sep1($.varDecl, ","), ), expr_aggregate_body: $ => seq(field('asExprs', $.asExprs), field('orderBys', optional($.orderBys))), aggregate: $ => seq($.aggId, // Agg optional( seq("[", sep1($._exprOrTerm, ","), "]") ), "(", optional( choice($.full_aggregate_body, $.expr_aggregate_body) ), ")" ), range: $ => seq( // Range "[", field('lower', $._exprOrTerm), "..", field('upper', $._exprOrTerm), "]" ), set_literal: $ => seq( "[", sep($._exprOrTerm, ','), optional(','), "]" ), par_expr: $ => seq("(", $._exprOrTerm, ")"), expr_annotation: $ => seq( field('name', $.annotName), "[", field('annot_arg', $.annotName), "]", "(", $._exprOrTerm, ")", ), _exprOrTerm: $ => choice( $.special_call, $.prefix_cast, $._primary, $.unary_expr, $.mul_expr, $.add_expr, $.in_expr, $.comp_term, $.instance_of, $.negation, $.if_term, $.conjunction, $.disjunction, $.implication, $.quantified, // QuantifiedTerm ), _primary: $ => choice( $.call_or_unqual_agg_expr, // $.qualified_expr, // QualifiedExpr $.literal, // Lit $.variable, // Var $.super_ref, $.aggregate, $.range, $.set_literal, $.par_expr, // ParExpr $.expr_annotation, // ExprAnnotation ), literal: $ => choice( $.integer, // IntLit $.float, // FloatLit $.bool, // BoolLit $.string // StringLit ), bool: $ => choice($.true, $.false), variable: $ => choice($.this, $.result, $.varName), compop: $ => choice('=', '!=', '<', '>', '<=', '>='), unop: $ => choice('+', '-'), mulop: $ => choice('*', '/', '%'), addop: $ => choice('+', '-'), closure: $ => choice('*', '+'), direction: $ => choice('asc', 'desc'), varDecl: $ => seq($.typeExpr, $.varName), moduleParam: $ => seq( field('signature', $.signatureExpr), field('parameter', $.simpleId) ), asExprs: $ => sep1($.asExpr, ","), asExpr: $ => seq($._exprOrTerm, optional(seq('as', $.varName))), orderBys: $ => seq("order", "by", sep1($.orderBy, ",")), orderBy: $ => seq($._exprOrTerm, optional($.direction)), qldoc: $ => /\/\*\*[^*]*\*+([^/*][^*]*\*+)*\//, literalId: $ => choice($._lower_id, $._upper_id), annotation: $ => choice( field('name', $.annotName), // SimpleAnnotation seq( // ArgsAnnotation field('name', $.annotName), "[", field('args', sep1($.annotArg, ",")), "]" ) ), annotName: $ => $._lower_id, annotArg: $ => choice($.simpleId, $.this, $.result), moduleName: $ => $.simpleId, importModuleExpr: $ => seq( repeat(seq(field("qualName", $.simpleId), ".")), $.moduleExpr ), moduleExpr: $ => choice( $.simpleId, $.moduleInstantiation, seq($.moduleExpr, "::", field("name", choice($.simpleId, $.moduleInstantiation))) ), moduleInstantiation: $ => seq( field("name", $.moduleName), "<", sep1($.signatureExpr, ","), ">" ), primitiveType: $ => choice('boolean', 'date', 'float', 'int', 'string'), simpleId: $ => choice($._lower_id, $._upper_id), className: $ => $._upper_id, dbtype: $ => /@[a-z][A-Za-z0-9_]*/, typeExpr: $ => choice( seq(optional(seq(field("qualifier", $.moduleExpr), "::")), field("name", $.className)), $.dbtype, $.primitiveType ), signatureExpr: $ => choice( field("type_expr", $.typeExpr), field("mod_expr", $.moduleExpr), field("predicate", $.predicateExpr) ), predicateName: $ => $._lower_id, aritylessPredicateExpr: $ => seq(optional(seq(field('qualifier', $.moduleExpr), "::")), field("name", $.literalId)), predicateExpr: $ => seq($.aritylessPredicateExpr, '/', $.integer), varName: $ => $.simpleId, aggId: $ => choice('avg', 'concat', 'strictconcat', 'count', 'max', 'min', 'rank', 'strictcount', 'strictsum', 'sum', 'any', 'unique'), _upper_id: $ => /[A-Z][A-Za-z0-9_]*/, _lower_id: $ => /[a-z][A-Za-z0-9_]*/, integer: $ => /[0-9]+/, float: $ => /[0-9]+\.[0-9]+/, string: $ => /"([^"\\\r\n\t]|\\["\\nrt])*"/, line_comment: $ => /\/\/[^\r\n]*/, block_comment: $ => /\/\*([^*]+\*+([^/*][^*]*\*+)*|\*)\//, false: $ => 'false', predicate: $ => 'predicate', result: $ => 'result', super: $ => 'super', this: $ => 'this', true: $ => 'true', } }); function sep(rule, s) { return optional(sep1(rule, s)) } function sep1(rule, s) { return seq(rule, repeat(seq(s, rule))) }