MiniZinc grammar for tree-sitter
1const PREC = {
2 call: 15,
3 annotation: 14,
4 unary: 13,
5 exponent: 12,
6 multiplicative: 11,
7 additive: 10,
8 intersect: 9,
9 dotdot: 8,
10 symdiff: 7,
11 diff: 6,
12 union: 5,
13 comparative: 4,
14 and: 3,
15 xor: 2,
16 or: 1,
17 implication: 2,
18 equivalence: 1,
19};
20
21const primitive_types = ["ann", "bool", "float", "int", "string"];
22
23module.exports = grammar({
24 name: "minizinc",
25
26 extras: ($) => [/\s/, $.line_comment, $.block_comment],
27
28 word: ($) => $.identifier,
29
30 conflicts: ($) => [
31 [$._expression, $.generator],
32 [$._expression, $.assignment],
33 ],
34
35 supertypes: ($) => [$._expression, $._item, $._type],
36
37 rules: {
38 source_file: ($) => seq(sepBy(";", $._item)),
39
40 _item: ($) =>
41 choice(
42 $.annotation,
43 $.assignment,
44 $.constraint,
45 $.declaration,
46 $.enumeration,
47 $.function_item,
48 $.goal,
49 $.include,
50 $.output,
51 $.predicate
52 ),
53
54 annotation: ($) =>
55 seq(
56 "annotation",
57 field("name", $.identifier),
58 optional(field("parameters", $._parameters)),
59 optional(seq("=", field("expr", $._expression)))
60 ),
61
62 assignment: ($) =>
63 seq(field("name", $.identifier), "=", field("expr", $._expression)),
64
65 constraint: ($) => seq("constraint", $._expression),
66
67 declaration: ($) =>
68 seq(
69 field("type", $._type),
70 ":",
71 field("name", $.identifier),
72 optional(field("annotations", $._annotations)),
73 optional(seq("=", field("expr", $._expression)))
74 ),
75
76 enumeration: ($) =>
77 seq(
78 "enum",
79 field("name", $.identifier),
80 optional(field("annotations", $._annotations)),
81 optional(seq("=", "{", field("members", sepBy(",", $.identifier)), "}"))
82 ),
83
84 function_item: ($) =>
85 seq(
86 "function",
87 field("type", $._type),
88 ":",
89 field("name", $.identifier),
90 field("parameters", $._parameters),
91 optional(field("annotations", $._annotations)),
92 optional(seq("=", field("expr", $._expression)))
93 ),
94
95 goal: ($) =>
96 seq(
97 "solve",
98 field(
99 "strategy",
100 choice(
101 "satisfy",
102 seq("maximize", $._expression),
103 seq("minimize", $._expression)
104 )
105 )
106 ),
107
108 include: ($) => seq("include", $.string_literal),
109
110 output: ($) => seq("output", $._expression),
111
112 predicate: ($) =>
113 seq(
114 field("type", choice("predicate", "test")),
115 field("name", $.identifier),
116 field("parameters", $._parameters),
117 optional(field("annotations", $._annotations)),
118 optional(seq("=", field("expr", $._expression)))
119 ),
120
121 _annotations: ($) => repeat1(seq("::", $._expression)),
122
123 _parameters: ($) =>
124 seq("(", sepBy(",", seq($._type, optional(seq(":", $.identifier)))), ")"),
125 _expression: ($) =>
126 choice(
127 $.identifier,
128 $._literal,
129
130 $.array_comprehension,
131 $.call,
132 $.generator_call,
133 $.if_then_else,
134 $.indexed_access,
135 $.infix_operator,
136 $.let_expression,
137 $.prefix_operator,
138 $.set_comprehension,
139 $.string_interpolation,
140 $.parenthesised_expression
141 ),
142
143 parenthesised_expression: ($) => seq("(", $._expression, ")"),
144
145 array_comprehension: ($) =>
146 seq("[", $._expression, "|", sepBy1(",", $.generator), "]"),
147
148 call: ($) =>
149 prec(
150 PREC.call,
151 seq(
152 field("name", $.identifier),
153 "(",
154 field("arguments", sepBy(",", $._expression)),
155 ")"
156 )
157 ),
158
159 generator_call: ($) =>
160 prec(
161 PREC.call,
162 seq(
163 field("name", $.identifier),
164 "(",
165 field("generators", sepBy1(",", $.generator)),
166 ")",
167 "(",
168 field("template", $._expression),
169 ")"
170 )
171 ),
172
173 generator: ($) =>
174 seq(
175 $.identifier,
176 "in",
177 $._expression,
178 optional(seq("where", $._expression))
179 ),
180
181 if_then_else: ($) =>
182 seq(
183 "if",
184 $._expression,
185 "then",
186 $._expression,
187 repeat(seq("elseif", $._expression, "then", $._expression)),
188 optional(seq("else", $._expression)),
189 "endif"
190 ),
191
192 indexed_access: ($) =>
193 prec(
194 PREC.call,
195 seq(
196 field("collection", $._expression),
197 "[",
198 field("indices", seq($._expression, repeat(seq(",", $._expression)))),
199 "]"
200 )
201 ),
202
203 infix_operator: ($) => {
204 const table = [
205 [prec.left, PREC.equivalence, "<->"],
206 [prec.left, PREC.implication, choice("->", "<-")],
207 [prec.left, PREC.or, "\\/"],
208 [prec.left, PREC.xor, "xor"],
209 [prec.left, PREC.and, "/\\"],
210 // TODO: Should really be nonassoc
211 // prettier-ignore
212 [prec.left, PREC.comparative, choice(
213 "=", "==", "!=", "<", "<=", ">", ">=", "in", "subset", "superset"
214 )],
215 [prec.left, PREC.union, "union"],
216 [prec.left, PREC.diff, "diff"],
217 [prec.left, PREC.symdiff, "symdiff"],
218 [prec.left, PREC.intersect, "intersect"],
219 // TODO: Could be nonassoc, will always give type error
220 [prec.left, PREC.dotdot, ".."],
221 [prec.left, PREC.additive, choice("+", "-", "++")],
222 [prec.left, PREC.multiplicative, choice("*", "/", "div", "mod")],
223 [prec.left, PREC.exponent, "^"],
224 [prec.left, PREC.annotation, "::"],
225 ];
226
227 return choice(
228 ...table.map(([assoc, precedence, operator]) =>
229 assoc(
230 precedence,
231 seq(
232 field("left", $._expression),
233 field("operator", operator),
234 field("right", $._expression)
235 )
236 )
237 )
238 );
239 },
240
241 let_expression: ($) =>
242 seq(
243 "let",
244 "{",
245 field(
246 "let",
247 sepBy(choice(",", ";"), choice($.declaration, $.constraint))
248 ),
249 "}",
250 "in",
251 field("in", $._expression)
252 ),
253
254 prefix_operator: ($) =>
255 prec(
256 PREC.unary,
257 seq(field("operator", choice("-", "not", "¬")), $._expression)
258 ),
259
260 set_comprehension: ($) =>
261 seq("{", $._expression, "|", sepBy1(",", $.generator), "}"),
262
263 // TODO: Decide if string_literal and string_interpolation should be combined
264 string_interpolation: ($) =>
265 seq(
266 '"',
267 optional($.string_content),
268 repeat1(seq("\\(", $._expression, ")", optional($.string_content))),
269 '"'
270 ),
271
272 _type: ($) => choice($.array_type, $.type_base),
273 array_type: ($) =>
274 seq("array", "[", sepBy1(",", $.type_base), "]", "of", $._type),
275 type_base: ($) =>
276 seq(
277 optional(field("var_par", choice("var", "par"))),
278 optional(field("opt", "opt")),
279 optional(field("set", seq("set", "of"))),
280 choice($.primitive_type, $._expression)
281 ),
282 primitive_type: ($) => choice(...primitive_types),
283
284 _literal: ($) =>
285 choice(
286 $.absent,
287 $.array_literal,
288 $.boolean_literal,
289 $.float_literal,
290 $.integer_literal,
291 $.set_literal,
292 $.string_literal
293 ),
294
295 absent: ($) => "<>",
296 array_literal: ($) => seq("[", sepBy(",", $._expression), "]"),
297 boolean_literal: ($) => choice("true", "false"),
298 float_literal: ($) =>
299 token(
300 choice(
301 /\d+\.\d+/,
302 /\d+(\.\d+)?[Ee][+-]?\d+/
303 // TODO: Hexadecimal floating point numbers
304 )
305 ),
306 integer_literal: ($) =>
307 token(choice(/[0-9]+/, /0x[0-9a-fA-F]+/, /0b[01]+/, /0o[0-7]+/)),
308 set_literal: ($) => seq("{", sepBy(",", $._expression), "}"),
309
310 string_literal: ($) =>
311 seq('"', alias(optional($.string_content), "content"), '"'),
312 string_content: ($) =>
313 repeat1(choice(token.immediate(prec(1, /[^"\n\\]+/)), $.escape_sequence)),
314 escape_sequence: ($) =>
315 token.immediate(
316 seq(
317 "\\",
318 choice(
319 /[^xuU]/,
320 /\d{2,3}/,
321 /x[0-9a-fA-F]{2,}/,
322 /u[0-9a-fA-F]{4}/,
323 /U[0-9a-fA-F]{8}/
324 )
325 )
326 ),
327
328 identifier: ($) => /[A-Za-z][A-Za-z0-9_]*/,
329
330 line_comment: ($) => token(seq("%", /.*/)),
331 block_comment: ($) => token(seq("/*", /([^*]|\*[^\/]|\n)*?\*?/, "*/")),
332 },
333});
334
335function sepBy(sep, rule) {
336 return seq(repeat(seq(rule, sep)), optional(rule));
337}
338
339function sepBy1(sep, rule) {
340 return seq(rule, repeat(seq(sep, rule)), optional(sep));
341}