on master help sign in
785 lines 23 kB history raw
1/// <reference types="tree-sitter-cli/dsl" />
2// Tree-sitter grammar for the Knot programming language.
3//
4// Knot is layout-sensitive. An external scanner emits NEWLINE, INDENT, and
5// DEDENT tokens based on indentation. Newlines are NOT in extras — the scanner
6// handles all \n characters.
8const PREC = {
9 PIPE: 1,
10 OR: 3,
11 AND: 5,
12 EQUALITY: 7,
13 COMPARISON: 9,
14 CONCAT: 11,
15 ADD: 13,
16 MUL: 15,
17 UNARY: 17,
18 APP: 18,
19 FIELD: 19,
20};
22module.exports = grammar({
23 name: "knot",
25 // Spaces, tabs, CR, comments, and line continuations. NO raw newlines.
26 extras: ($) => [/[ \t\r]/, $.comment, $._line_cont],
28 word: ($) => $.lower_identifier,
30 externals: ($) => [$._newline, $._indent, $._dedent, $._line_cont],
32 conflicts: ($) => [
33 [$.constraint, $.named_type],
34 // do-block: pattern vs expression (resolved by presence of `<-`)
35 [$.variable_pattern, $.variable_expression],
36 [$.record_pattern, $.record_expression],
37 [$.list_pattern, $.list_expression],
38 [$.literal_pattern, $._atom_expression],
39 [$.field_pattern, $.variable_expression],
40 // trait param vs type variable in trait declaration
41 [$._trait_param, $.type_variable],
42 // trait method with default body vs separate default body
43 [$.trait_method, $.trait_default_body],
44 ],
46 rules: {
47 // ── Module ──────────────────────────────────────────────────────
48 source_file: ($) =>
49 seq(
50 optional(seq($.module_declaration, $._newline)),
51 sep($._declaration, $._newline),
52 ),
54 module_declaration: ($) =>
55 seq("module", field("name", $.upper_identifier)),
57 // ── Declarations ────────────────────────────────────────────────
58 _declaration: ($) =>
59 choice(
60 $.import_declaration,
61 $.export_declaration,
62 $._declaration_body,
63 ),
65 _declaration_body: ($) =>
66 choice(
67 $.data_declaration,
68 $.type_alias_declaration,
69 $.source_declaration,
70 $.view_declaration,
71 $.derived_declaration,
72 $.fun_declaration,
73 $.trait_declaration,
74 $.impl_declaration,
75 $.migrate_declaration,
76 $.route_declaration,
77 $.route_composite_declaration,
78 $.subset_constraint,
79 ),
81 // ── Import ──────────────────────────────────────────────────────
82 import_declaration: ($) =>
83 seq(
84 "import",
85 field("path", $.import_path),
86 optional(seq("(", sep1($.import_item, ","), ")")),
87 ),
89 import_path: ($) =>
90 seq(".", "/", sep1($.lower_identifier, "/")),
92 import_item: ($) =>
93 choice($.lower_identifier, $.upper_identifier),
95 // ── Export ───────────────────────────────────────────────────────
96 export_declaration: ($) =>
97 seq("export", $._declaration_body),
99 // ── Data ────────────────────────────────────────────────────────
100 data_declaration: ($) =>
101 seq(
102 "data",
103 field("name", $.upper_identifier),
104 field("params", repeat($.lower_identifier)),
105 choice(
106 // Multi-line: indented constructors on separate lines
107 seq(
108 $._indent,
109 sep1($.constructor_def, $._newline),
110 optional(seq($._newline, $.deriving_clause)),
111 $._dedent,
112 ),
113 // Single-line: constructors on same line as `data`
114 seq(
115 repeat1($.constructor_def),
116 optional($.deriving_clause),
117 ),
118 ),
119 ),
121 constructor_def: ($) =>
122 seq(
123 choice("=", "|"),
124 field("name", $.upper_identifier),
125 field("fields", $.record_type_body),
126 ),
128 deriving_clause: ($) =>
129 seq("deriving", "(", sep1($.upper_identifier, ","), ")"),
131 // ── Type alias ──────────────────────────────────────────────────
132 type_alias_declaration: ($) =>
133 seq(
134 "type",
135 field("name", $.upper_identifier),
136 field("params", repeat($.lower_identifier)),
137 "=",
138 field("type", $._type),
139 ),
141 // ── Source ───────────────────────────────────────────────────────
142 source_declaration: ($) =>
143 seq(
144 $.source_ref,
145 ":",
146 field("type", $._type),
147 ),
149 // ── View ────────────────────────────────────────────────────────
150 view_declaration: ($) =>
151 seq($.source_ref, "=", field("body", $._block_body)),
153 // ── Derived ─────────────────────────────────────────────────────
154 derived_declaration: ($) =>
155 seq($.derived_ref, "=", field("body", $._block_body)),
157 // ── Fun ─────────────────────────────────────────────────────────
158 fun_declaration: ($) =>
159 seq(
160 field("name", $.lower_identifier),
161 choice(
162 seq(
163 optional(seq(":", field("type", $.type_scheme))),
164 "=",
165 field("body", $._block_body),
166 ),
167 seq(":", field("type", $.type_scheme)),
168 ),
169 ),
171 // ── Trait ────────────────────────────────────────────────────────
172 trait_declaration: ($) =>
173 seq(
174 "trait",
175 repeat($.supertrait),
176 field("name", $.upper_identifier),
177 field("params", repeat1($._trait_param)),
178 "where",
179 $._indent,
180 sep1($._trait_item, $._newline),
181 $._dedent,
182 ),
184 supertrait: ($) =>
185 prec.dynamic(1, seq($.upper_identifier, repeat1($._type_atom), "=>")),
187 _trait_param: ($) =>
188 choice(
189 $.lower_identifier,
190 seq("(", $.lower_identifier, ":", $._type, ")"),
191 ),
193 _trait_item: ($) =>
194 choice($.trait_method, $.trait_associated_type, $.trait_default_body),
196 trait_method: ($) =>
197 prec.right(
198 seq(
199 field("name", $.lower_identifier),
200 choice(
201 seq(":", field("type", $.type_scheme)),
202 seq(repeat($._pattern_atom), "=", $._expression),
203 ),
204 ),
205 ),
207 trait_associated_type: ($) =>
208 seq("type", $.upper_identifier, repeat($.lower_identifier)),
210 trait_default_body: ($) =>
211 seq(
212 $.lower_identifier,
213 repeat($._pattern_atom),
214 "=",
215 $._expression,
216 ),
218 // ── Impl ────────────────────────────────────────────────────────
219 impl_declaration: ($) =>
220 seq(
221 "impl",
222 repeat(seq($.constraint, "=>")),
223 field("trait", $.upper_identifier),
224 field("args", repeat1($._type_atom)),
225 "where",
226 $._indent,
227 sep1($._impl_item, $._newline),
228 $._dedent,
229 ),
231 _impl_item: ($) =>
232 choice($.impl_method, $.impl_associated_type),
234 impl_method: ($) =>
235 seq(
236 field("name", $.lower_identifier),
237 field("params", repeat($._pattern_atom)),
238 "=",
239 field("body", $._block_body),
240 ),
242 impl_associated_type: ($) =>
243 seq("type", $.upper_identifier, repeat($._type_atom), "=", $._type),
245 // ── Migrate ─────────────────────────────────────────────────────
246 migrate_declaration: ($) =>
247 seq(
248 "migrate",
249 field("relation", $.source_ref),
250 "from",
251 field("from", $._type),
252 "to",
253 field("to", $._type),
254 "using",
255 field("using", $._expression),
256 ),
258 // ── Route ───────────────────────────────────────────────────────
259 route_declaration: ($) =>
260 seq(
261 "route",
262 field("name", $.upper_identifier),
263 "where",
264 $._indent,
265 sep1($._route_item, $._newline),
266 $._dedent,
267 ),
269 route_composite_declaration: ($) =>
270 seq(
271 "route",
272 field("name", $.upper_identifier),
273 "=",
274 sep1($.upper_identifier, "|"),
275 ),
277 _route_item: ($) =>
278 choice($.route_entry, $.route_path_group),
280 route_path_group: ($) =>
281 seq(
282 repeat1($._route_path_segment),
283 $._indent,
284 sep1($._route_item, $._newline),
285 $._dedent,
286 ),
288 route_entry: ($) =>
289 prec.right(
290 seq(
291 field("method", $.http_method),
292 repeat(choice(
293 $._route_path_segment,
294 field("body", $.record_type_body),
295 seq("?", field("query", $.record_type_body)),
296 seq("headers", $.record_type_body),
297 )),
298 "->",
299 field("response", $._type),
300 optional(seq("headers", $.record_type_body)),
301 "=",
302 field("constructor", $.upper_identifier),
303 ),
304 ),
306 _route_path_segment: ($) =>
307 choice(
308 seq("/", field("name", $.lower_identifier)),
309 seq("/", token.immediate("{"), field("name", $.lower_identifier), ":", field("type", $._type), "}"),
310 "/",
311 ),
313 http_method: ($) => choice("GET", "POST", "PUT", "DELETE", "PATCH"),
315 // ── Subset constraints ──────────────────────────────────────────
316 subset_constraint: ($) =>
317 seq(
318 $._constraint_path,
319 "<=",
320 $._constraint_path,
321 ),
323 _constraint_path: ($) =>
324 choice(
325 seq($.source_ref, ".", $.lower_identifier),
326 $.source_ref,
327 ),
329 // ── Types ───────────────────────────────────────────────────────
331 type_scheme: ($) => seq(repeat(seq($.constraint, "=>")), $._type),
333 constraint: ($) => seq($.upper_identifier, repeat1($._type_atom)),
335 _type: ($) => choice($.function_type, $.refined_type, $._type_app),
337 function_type: ($) =>
338 prec.right(
339 seq(field("param", $.refined_type), "->", field("result", $._type)),
340 ),
342 refined_type: ($) =>
343 choice(
344 seq(field("base", $._type_app), "where", field("predicate", $._expression)),
345 $._type_app,
346 ),
348 _type_app: ($) => choice($.type_application, $._type_atom),
350 type_application: ($) =>
351 prec.left(
352 PREC.APP,
353 seq(choice($.named_type, $.type_application), $._type_atom),
354 ),
356 _type_atom: ($) =>
357 choice(
358 $.named_type,
359 $.type_variable,
360 $.record_type,
361 $.relation_type,
362 $.variant_type,
363 $.type_hole,
364 $.parenthesized_type,
365 ),
367 named_type: ($) => $.upper_identifier,
368 type_variable: ($) => $.lower_identifier,
370 record_type: ($) =>
371 seq(
372 "{",
373 optional(
374 choice(
375 // Record fields: {name: Type, age: Int}
376 seq(
377 sep1($.type_field, ","),
378 optional(seq("|", $.lower_identifier)),
379 ),
380 // Bare identifiers for effect sets: {console, network}
381 // Also covers: reads *rel, writes *rel
382 sep1($.effect, ","),
383 ),
384 ),
385 "}",
386 ),
388 effect: ($) =>
389 choice(
390 seq($.lower_identifier, $.source_ref),
391 $.lower_identifier,
392 ),
394 record_type_body: ($) =>
395 seq("{", optional(sep1($.type_field, ",")), "}"),
397 type_field: ($) =>
398 seq(field("name", $.lower_identifier), ":", field("type", $._type)),
400 relation_type: ($) => seq("[", optional($._type), "]"),
402 variant_type: ($) =>
403 seq(
404 "<",
405 sep1($.variant_constructor, "|"),
406 optional(seq("|", $.lower_identifier)),
407 ">",
408 ),
410 variant_constructor: ($) =>
411 seq($.upper_identifier, optional($.record_type_body)),
413 type_hole: ($) => "_",
415 parenthesized_type: ($) => seq("(", $._type, ")"),
417 // ── Expressions ─────────────────────────────────────────────────
419 // Expression body that may span multiple indented lines.
420 // Same-line: just an expression. Multi-line: wrapped in INDENT/DEDENT.
421 _block_body: ($) =>
422 choice(
423 $._expression,
424 seq($._indent, $._expression, $._dedent),
425 ),
427 _expression: ($) =>
428 choice(
429 $.lambda_expression,
430 $.if_expression,
431 $.case_expression,
432 $.do_expression,
433 $.set_expression,
434 $.replace_set_expression,
435 $.yield_expression,
436 $.atomic_expression,
437 $.refine_expression,
438 $.let_in_expression,
439 $._binary_expression,
440 ),
442 lambda_expression: ($) =>
443 prec.right(
444 seq(
445 "\\",
446 repeat1($._pattern_atom),
447 "->",
448 field("body", $._expression),
449 ),
450 ),
452 if_expression: ($) =>
453 prec.right(
454 seq(
455 "if",
456 field("condition", $._expression),
457 "then",
458 field("then", $._expression),
459 "else",
460 field("else", $._expression),
461 ),
462 ),
464 case_expression: ($) =>
465 prec.right(
466 seq(
467 "case",
468 field("scrutinee", $._expression),
469 "of",
470 $._indent,
471 sep1($.case_arm, $._newline),
472 $._dedent,
473 ),
474 ),
476 case_arm: ($) =>
477 prec.right(
478 seq(
479 field("pattern", $._pattern),
480 "->",
481 field("body", $._expression),
482 ),
483 ),
485 do_expression: ($) =>
486 seq("do", $._indent, sep1($._statement, $._newline), $._dedent),
488 _statement: ($) =>
489 choice(
490 $.bind_statement,
491 $.let_statement,
492 $.where_statement,
493 $._expression,
494 ),
496 bind_statement: ($) =>
497 prec.dynamic(
498 1,
499 seq(
500 field("pattern", $._bind_pattern),
501 "<-",
502 field("value", $._expression),
503 ),
504 ),
506 _bind_pattern: ($) =>
507 choice(
508 $.constructor_pattern,
509 $.variable_pattern,
510 $.wildcard_pattern,
511 $.record_pattern,
512 $.list_pattern,
513 $.parenthesized_pattern,
514 ),
516 let_statement: ($) =>
517 seq(
518 "let",
519 field("pattern", $._pattern),
520 "=",
521 field("value", $._expression),
522 ),
524 where_statement: ($) =>
525 prec.right(seq("where", field("condition", $._expression))),
527 set_expression: ($) =>
528 prec.right(
529 seq(
530 "set",
531 field("target", $.source_ref),
532 "=",
533 field("value", $._expression),
534 ),
535 ),
537 replace_set_expression: ($) =>
538 prec.right(
539 seq(
540 "replace",
541 "set",
542 field("target", $.source_ref),
543 "=",
544 field("value", $._expression),
545 ),
546 ),
548 yield_expression: ($) =>
549 prec.right(seq("yield", field("value", $._expression))),
551 atomic_expression: ($) =>
552 prec.right(seq("atomic", field("body", $._expression))),
554 refine_expression: ($) =>
555 prec.right(seq("refine", field("value", $._expression))),
557 let_in_expression: ($) =>
558 prec.right(
559 seq("let", $._pattern, "=", $._expression, "in", $._expression),
560 ),
562 // ── Binary / unary / application / postfix ──────────────────────
564 _binary_expression: ($) =>
565 choice(
566 $.pipe_expression,
567 $.or_expression,
568 $.and_expression,
569 $.equality_expression,
570 $.comparison_expression,
571 $.concat_expression,
572 $.add_expression,
573 $.mul_expression,
574 $._unary_expression,
575 ),
577 pipe_expression: ($) =>
578 prec.left(
579 PREC.PIPE,
580 seq($._binary_expression, "|>", $._binary_expression),
581 ),
583 or_expression: ($) =>
584 prec.left(
585 PREC.OR,
586 seq($._binary_expression, "||", $._binary_expression),
587 ),
589 and_expression: ($) =>
590 prec.left(
591 PREC.AND,
592 seq($._binary_expression, "&&", $._binary_expression),
593 ),
595 equality_expression: ($) =>
596 prec.left(
597 PREC.EQUALITY,
598 seq($._binary_expression, choice("==", "!="), $._binary_expression),
599 ),
601 comparison_expression: ($) =>
602 prec.left(
603 PREC.COMPARISON,
604 seq(
605 $._binary_expression,
606 choice("<", ">", "<=", ">="),
607 $._binary_expression,
608 ),
609 ),
611 concat_expression: ($) =>
612 prec.right(
613 PREC.CONCAT,
614 seq($._binary_expression, "++", $._binary_expression),
615 ),
617 add_expression: ($) =>
618 prec.left(
619 PREC.ADD,
620 seq($._binary_expression, choice("+", "-"), $._binary_expression),
621 ),
623 mul_expression: ($) =>
624 prec.left(
625 PREC.MUL,
626 seq($._binary_expression, choice("*", "/"), $._binary_expression),
627 ),
629 _unary_expression: ($) =>
630 choice(
631 $.negation_expression,
632 $.not_expression,
633 $._application_expression,
634 ),
636 negation_expression: ($) =>
637 prec(PREC.UNARY, seq("-", $._application_expression)),
639 not_expression: ($) =>
640 prec(PREC.UNARY, seq("not", $._application_expression)),
642 _application_expression: ($) =>
643 choice($.application_expression, $._postfix_expression),
645 application_expression: ($) =>
646 prec.left(
647 PREC.APP,
648 seq($._application_expression, $._postfix_expression),
649 ),
651 _postfix_expression: ($) =>
652 choice($.field_access_expression, $._atom_expression),
654 field_access_expression: ($) =>
655 prec.left(
656 PREC.FIELD,
657 seq($._postfix_expression, ".", $.lower_identifier),
658 ),
660 _atom_expression: ($) =>
661 choice(
662 $._literal,
663 $.variable_expression,
664 $.constructor_expression,
665 $.source_ref,
666 $.derived_ref,
667 $.record_expression,
668 $.list_expression,
669 $.parenthesized_expression,
670 ),
672 variable_expression: ($) => $.lower_identifier,
673 constructor_expression: ($) => $.upper_identifier,
675 source_ref: ($) => token(seq("*", /[a-z_][a-zA-Z0-9_']*/)),
676 derived_ref: ($) => token(seq("&", /[a-z_][a-zA-Z0-9_']*/)),
678 record_expression: ($) =>
679 seq("{", optional(sep1($.record_field, ",")), "}"),
681 record_field: ($) =>
682 choice(
683 seq(
684 field("name", $.lower_identifier),
685 ":",
686 field("value", $._expression),
687 ),
688 field("value", $._expression),
689 ),
691 list_expression: ($) =>
692 seq("[", optional(sep1($._expression, ",")), "]"),
694 parenthesized_expression: ($) => seq("(", $._expression, ")"),
696 // ── Patterns ────────────────────────────────────────────────────
698 _pattern: ($) => choice($.constructor_pattern, $._pattern_atom),
700 constructor_pattern: ($) =>
701 prec(
702 PREC.APP,
703 seq(
704 field("constructor", $.upper_identifier),
705 field("payload", $._pattern_atom),
706 ),
707 ),
709 _pattern_atom: ($) =>
710 choice(
711 $.variable_pattern,
712 $.wildcard_pattern,
713 $.literal_pattern,
714 $.record_pattern,
715 $.list_pattern,
716 $.parenthesized_pattern,
717 ),
719 variable_pattern: ($) => $.lower_identifier,
720 wildcard_pattern: ($) => "_",
721 literal_pattern: ($) => $._literal,
723 record_pattern: ($) =>
724 seq("{", optional(sep1($.field_pattern, ",")), "}"),
726 field_pattern: ($) =>
727 choice(
728 seq(
729 field("name", $.lower_identifier),
730 ":",
731 field("pattern", $._pattern),
732 ),
733 $.lower_identifier,
734 ),
736 list_pattern: ($) => seq("[", optional(sep1($._pattern, ",")), "]"),
738 parenthesized_pattern: ($) => seq("(", $._pattern, ")"),
740 // ── Literals ────────────────────────────────────────────────────
742 _literal: ($) =>
743 choice(
744 $.integer_literal,
745 $.float_literal,
746 $.string_literal,
747 $.boolean_literal,
748 $.bytes_literal,
749 ),
751 integer_literal: ($) => /[0-9][0-9_]*/,
753 float_literal: ($) => /[0-9][0-9_]*\.[0-9][0-9_]*/,
755 string_literal: ($) =>
756 seq('"', repeat(choice($.escape_sequence, $.string_content)), '"'),
758 boolean_literal: ($) => choice("true", "false"),
760 bytes_literal: ($) =>
761 seq('b"', repeat(choice($.escape_sequence, $.string_content)), '"'),
763 escape_sequence: ($) => token.immediate(prec(1, /\\([\\\"ntr]|x[0-9a-fA-F]{2})/)),
765 string_content: ($) => token.immediate(prec(0, /[^"\\]+/)),
767 // ── Identifiers ─────────────────────────────────────────────────
769 lower_identifier: ($) => /[a-z_][a-zA-Z0-9_']*/,
771 upper_identifier: ($) => /[A-Z][a-zA-Z0-9_']*/,
773 // ── Misc ────────────────────────────────────────────────────────
775 comment: ($) => token(seq("--", /.*/)),
776 },
777});
779function sep1(rule, separator) {
780 return seq(rule, repeat(seq(separator, rule)));
783function sep(rule, separator) {
784 return optional(sep1(rule, separator));