add generics

This commit is contained in:
paul committed 2026-10-23 02:13:30 -04:00
1 parent 79533e9537
commit 2204d58102
22 files changed
+47358 -37579

No files matched your search

+5 -4
View File
@@ -10,7 +10,9 @@ The public named-node and field contract is documented in
## Development
Tree-sitter 0.27 and Node.js are required.
Tree-sitter 0.27 is required. `tree-sitter generate` evaluates `grammar.js`
with Node.js by default; without Node.js, pass `--js-runtime native` to use the
CLI's bundled QuickJS runtime.
```sh
npm install
@@ -28,9 +30,8 @@ script: the adjacency-only `=-` and `!=` lexer errors, and recovery after a
repeated result operator. These need lexical or recovery handling rather than
a looser grammar rule.
The future-reserved word `context` is not yet emitted as a Tree-sitter keyword
because it has no grammar production. It should move into lexical handling with
the two adjacency checks above.
The future-reserved word `context` is a `reserved_word` token with no grammar
production, so it is rejected wherever an identifier is expected.
Generated parser sources under `src/` are committed so consumers do not need
Node.js or the Tree-sitter CLI.
+270 -13
View File
@@ -196,10 +196,10 @@ mod tests {
}
#[test]
fn exposes_wrappers_and_untyped_inputs() {
let source = "present: i32?: some 1\n\
fn exposes_sum_spelled_optionals_and_untyped_inputs() {
let source = "present: i32?: 1->|.some\n\
double :: @ value { value * 2 }\n\
unwrap :: @ result { match result { ok inner => inner; err _ => 0 } }\n";
unwrap :: @ result { match result { |.ok inner => inner; |.err _ => 0 } }\n";
let tree = parse(source);
assert!(
!tree.root_node().has_error(),
@@ -209,10 +209,22 @@ mod tests {
let present = tree.root_node().named_child(0).unwrap();
let wrapped = present.child_by_field_name("value").unwrap();
assert_eq!(wrapped.kind(), "unary_expression");
assert_eq!(wrapped.kind(), "pipeline_expression");
assert_eq!(
wrapped
.child_by_field_name("operator")
wrapped.child_by_field_name("input").unwrap().kind(),
"signed_numeric_literal"
);
let stage = wrapped.named_child(1).unwrap();
assert_eq!(stage.kind(), "direct_pipeline_stage");
let constructor = stage
.child_by_field_name("callable")
.unwrap()
.named_child(0)
.unwrap();
assert_eq!(constructor.kind(), "sum_constructor");
assert_eq!(
constructor
.child_by_field_name("variant")
.unwrap()
.utf8_text(source.as_bytes())
.unwrap(),
@@ -233,25 +245,270 @@ mod tests {
let body = function.child_by_field_name("body").unwrap();
let arm = body.named_child(0).unwrap().named_child(1).unwrap();
let pattern = arm.child_by_field_name("pattern").unwrap();
let wrapper = pattern.named_child(0).unwrap();
assert_eq!(wrapper.kind(), "wrapper_pattern");
let variant = pattern.named_child(0).unwrap();
assert_eq!(variant.kind(), "sum_pattern");
assert_eq!(
wrapper
.child_by_field_name("wrapper")
variant
.child_by_field_name("variant")
.unwrap()
.utf8_text(source.as_bytes())
.unwrap(),
"ok"
);
assert_eq!(
variant.child_by_field_name("payload").unwrap().kind(),
"identifier"
);
}
#[test]
fn rejects_removed_wrapper_forms() {
fn binds_same_line_stages_tighter_than_operators() {
let source = "mixed :: -3->add_ten + 2 * 3->double - {1 + 1}->double\n";
let tree = parse(source);
assert!(
!tree.root_node().has_error(),
"{}",
tree.root_node().to_sexp()
);
let declaration = tree.root_node().named_child(0).unwrap();
let subtraction = declaration.child_by_field_name("value").unwrap();
assert_eq!(subtraction.kind(), "binary_expression");
assert_eq!(
subtraction
.child_by_field_name("operator")
.unwrap()
.utf8_text(source.as_bytes())
.unwrap(),
"-"
);
let addition = subtraction.child_by_field_name("left").unwrap();
assert_eq!(addition.kind(), "binary_expression");
let negation = addition.child_by_field_name("left").unwrap();
assert_eq!(negation.kind(), "unary_expression");
let negated = negation.child_by_field_name("operand").unwrap();
assert_eq!(negated.kind(), "pipeline_expression");
assert_eq!(
negated
.child_by_field_name("input")
.unwrap()
.utf8_text(source.as_bytes())
.unwrap(),
"3"
);
let product = addition.child_by_field_name("right").unwrap();
assert_eq!(product.kind(), "binary_expression");
assert_eq!(
product.child_by_field_name("right").unwrap().kind(),
"pipeline_expression"
);
let grouped = subtraction.child_by_field_name("right").unwrap();
assert_eq!(grouped.kind(), "pipeline_expression");
assert_eq!(
grouped.child_by_field_name("input").unwrap().kind(),
"block"
);
}
#[test]
fn applies_continuation_stages_to_the_whole_preceding_expression() {
let source = "flat := value->first\n->second\nwhole := a + b->f\n->g\n";
let tree = parse(source);
assert!(
!tree.root_node().has_error(),
"{}",
tree.root_node().to_sexp()
);
let flat = tree
.root_node()
.named_child(0)
.unwrap()
.child_by_field_name("value")
.unwrap();
assert_eq!(flat.kind(), "pipeline_expression");
assert_eq!(
flat.child_by_field_name("input").unwrap().kind(),
"identifier"
);
assert_eq!(flat.named_child(1).unwrap().kind(), "direct_pipeline_stage");
assert_eq!(
flat.named_child(2).unwrap().kind(),
"continuation_pipeline_stage"
);
assert_eq!(flat.named_child_count(), 3);
let whole = tree
.root_node()
.named_child(1)
.unwrap()
.child_by_field_name("value")
.unwrap();
assert_eq!(whole.kind(), "pipeline_expression");
let head = whole.child_by_field_name("input").unwrap();
assert_eq!(head.kind(), "binary_expression");
assert_eq!(
head.child_by_field_name("right").unwrap().kind(),
"pipeline_expression"
);
assert_eq!(
whole.named_child(1).unwrap().kind(),
"continuation_pipeline_stage"
);
}
#[test]
fn exposes_generic_binders_constraints_and_output_types() {
let source = "Show :: <>{ to_str: str; ...Display }\n\
pick: <T: Show> (T, T) -> T: @ (first, second): (T, T) { first }\n\
Card :: &{ label: Output<make_label: Show>: }\n";
let tree = parse(source);
assert!(
!tree.root_node().has_error(),
"{}",
tree.root_node().to_sexp()
);
let show = tree.root_node().named_child(0).unwrap();
let definition = show.child_by_field_name("value").unwrap();
assert_eq!(definition.kind(), "type_definition");
let constraint = definition.named_child(0).unwrap();
assert_eq!(constraint.kind(), "constraint_body");
let entry = constraint.named_child(0).unwrap();
assert_eq!(entry.kind(), "constraint_entry");
assert_eq!(
entry
.child_by_field_name("name")
.unwrap()
.utf8_text(source.as_bytes())
.unwrap(),
"to_str"
);
assert_eq!(
entry.child_by_field_name("output").unwrap().kind(),
"primitive_type"
);
let spread = constraint.named_child(1).unwrap();
assert_eq!(spread.kind(), "constraint_spread");
assert_eq!(
spread
.child_by_field_name("constraint")
.unwrap()
.utf8_text(source.as_bytes())
.unwrap(),
"Display"
);
let pick = tree.root_node().named_child(1).unwrap();
let binder = pick.child_by_field_name("binder").unwrap();
assert_eq!(binder.kind(), "binder");
let parameter = binder.named_child(0).unwrap();
assert_eq!(parameter.kind(), "binder_entry");
assert_eq!(
parameter
.child_by_field_name("type")
.unwrap()
.utf8_text(source.as_bytes())
.unwrap(),
"T"
);
assert_eq!(
parameter
.child_by_field_name("constraint")
.unwrap()
.utf8_text(source.as_bytes())
.unwrap(),
"Show"
);
assert_eq!(
pick.child_by_field_name("type").unwrap().kind(),
"function_type"
);
assert_eq!(
pick.child_by_field_name("value").unwrap().kind(),
"function_literal"
);
let card = tree.root_node().named_child(2).unwrap();
let field = card
.child_by_field_name("value")
.unwrap()
.named_child(0)
.unwrap()
.named_child(0)
.unwrap();
assert_eq!(field.kind(), "product_field");
let output = field.child_by_field_name("type").unwrap();
assert_eq!(output.kind(), "output_type");
assert_eq!(
output
.child_by_field_name("function")
.unwrap()
.utf8_text(source.as_bytes())
.unwrap(),
"make_label"
);
assert_eq!(
output
.child_by_field_name("output")
.unwrap()
.utf8_text(source.as_bytes())
.unwrap(),
"Show"
);
}
#[test]
fn exposes_import_filters() {
let source = "use math.{sqrt for f64, pi}\nuse .shapes.area for Circle\npub { area for Circle; sqrt }\n";
let tree = parse(source);
assert!(
!tree.root_node().has_error(),
"{}",
tree.root_node().to_sexp()
);
let group = tree.root_node().named_child(0).unwrap();
assert_eq!(group.kind(), "use_declaration");
let member = group.named_child(1).unwrap();
assert_eq!(member.kind(), "import_member");
let filter = member.named_child(1).unwrap();
assert_eq!(filter.kind(), "import_filter");
assert_eq!(
filter.child_by_field_name("type").unwrap().kind(),
"primitive_type"
);
assert_eq!(group.named_child(2).unwrap().named_child_count(), 1);
let single = tree.root_node().named_child(1).unwrap();
assert_eq!(single.named_child(1).unwrap().kind(), "import_filter");
let publication = tree.root_node().named_child(2).unwrap();
assert_eq!(publication.kind(), "pub_declaration");
let filtered = publication.named_child(0).unwrap();
assert_eq!(filtered.kind(), "pub_entry");
assert_eq!(
filtered
.child_by_field_name("name")
.unwrap()
.utf8_text(source.as_bytes())
.unwrap(),
"area"
);
assert_eq!(filtered.named_child(1).unwrap().kind(), "import_filter");
assert_eq!(publication.named_child(1).unwrap().named_child_count(), 1);
}
#[test]
fn rejects_removed_wrapper_forms_and_reserved_words() {
for source in [
"old: i32?: 1->|.some\n",
"present: i32?: some 1\n",
"old :: @ value: i32 -> i32 ! str { value! }\n",
"old :: @ value: str -> i32 ! str { !value }\n",
"some := 1\n",
"context := 1\n",
] {
assert!(parse(source).root_node().has_error(), "accepted {source}");
}
+58 -15
View File
@@ -15,6 +15,7 @@ and `value_expression` rules are hidden supertypes.
- `name` for a named declaration;
- `pattern` for a destructuring declaration;
- `binder` for the type parameters of a generic function declaration;
- `type` for an explicit type;
- `value` for an initializer.
@@ -22,6 +23,21 @@ The punctuation distinguishes immutable, mutable, typed, and deferred forms.
Consumers should inspect that punctuation rather than infer the form from the
set of fields alone.
A `binder` holds one `binder_entry` per parameter or bound. Each entry exposes
`type` and, after `:`, an optional `constraint`, which is a constraint name or
an inline `constraint_body`. A `constraint_body` (`<>{ ... }`) also appears as
the whole initializer of a `::` declaration, inside `type_definition`. It
contains `constraint_entry` nodes with `name` and `output` fields and
`constraint_spread` nodes with a `constraint` field.
## Modules
`use_declaration` contains a `module_path` and either an `alias`, an
`import_filter`, or `import_member` nodes. An `import_member` exposes `name`
and either `alias` or an `import_filter`. `import_filter` wraps the `for`
clause and exposes its `type`. `pub_declaration` contains `pub_entry` nodes,
each with a `name` and an optional `import_filter`.
## Expressions
Only expressions that actually contain an operator produce an operator node:
@@ -39,16 +55,27 @@ Precedence-only rules are hidden. For example, `a + b * -c` has one outer and
one inner `binary_expression`, then a `unary_expression`; it does not contain a
wrapper for every precedence level.
Literal leaves are named. Boolean and nil values use `boolean_literal` and
`nil_literal`; numeric, string, character, and unit literals retain their
specific nodes. `default_expression` is also named.
Pipeline stages are postfix. A same-line stage binds to the operand
immediately to its left, tighter than every prefix and infix operator, so
`-x->abs` is a `unary_expression` whose operand is a `pipeline_expression`,
and `a + b->f` is a `binary_expression` whose right operand is one. A
`pipeline_expression` is one flat node: its `input` is followed by every stage
that applies to it, same-line and continuation alike. A leading-operator
continuation applies to the complete ordinary expression on the preceding
line, so in `a + b->f` followed by a continuation `->g` the outer
`pipeline_expression` has the `binary_expression` as its `input`. A negative
literal is never a stage input: `-3->f` negates the pipeline, while a bare `-3`
remains one `signed_numeric_literal`.
`some`, `ok`, and `err` construct optional and result values. They are prefix
operators, so `some value` and `ok {x->f}` appear as `unary_expression` nodes
whose `operator` is the keyword. They bind like `-` and `~`, so `ok x->f`
wraps `x` and then pipes the result into `f`, while `ok {x->f}` wraps the
pipeline. The older `|.some`/`|.ok`/`|.err`
constructors, postfix `value!`, and prefix `!error` are no longer accepted.
Literal leaves are named. Boolean values use `boolean_literal`; numeric,
string, character, and unit literals retain their specific nodes.
`default_expression` is also named.
Optionals and results are ordinary sums. `|.none` is a `sum_constructor`, and
`value->|.some`, `value->|.ok`, and `error->|.err` are pipelines whose stage
callable is a `sum_constructor`. `sum_constructor` exposes an optional `type`
and the `variant`. The former `some`, `ok`, `err`, and `nil` keywords are
ordinary identifiers. Postfix `value!` and prefix `!error` are not accepted.
`function_literal` takes one input and owns the `body` field. A pattern with
one annotation for the whole input, such as `@ value: i32` or
@@ -66,10 +93,10 @@ may also expose `effect` (`->` or `=>`) and `result`.
## Patterns
`wrapper_pattern` matches `some`, `ok`, or `err` applied to one pattern atom.
It exposes `wrapper` (the keyword) and `value` (the inner pattern). `nil` is
still a `nil_literal` pattern. Sum patterns retain `sum_pattern` with a
`variant` field.
`sum_pattern` matches a variant, including `|.none`, `|.some pattern`,
`|.ok pattern`, and `|.err pattern`. It exposes an optional `type`, the
`variant`, and an optional `payload` pattern atom. `bits_pattern` exposes the
optional `type` as well.
## Types
@@ -84,9 +111,13 @@ Meaningful type constructors are named directly:
| `map_type` | `key`, `value` |
| `grouped_type` | `value` |
| `type_application` | `constructor` followed by its type arguments |
| `output_type` | `constructor`, `function`, `output` |
Primitive, unit, tuple, product, sum, and bits types retain their corresponding
named nodes. Internal primary/container/result categories are hidden.
`output_type` is the `Output<f: U>` spelling: a type name applied to a
function name and a type. The grammar does not check that the constructor is
spelled `Output`. Primitive, unit, tuple, product, sum, and bits types retain
their corresponding named nodes. Internal primary/container/result categories
are hidden.
## Trivia and physical lines
@@ -96,6 +127,9 @@ alongside the tree. Leading-operator and pipeline continuations use composite
tokens containing their newline; this prevents a scope separator from stealing
a valid continuation and preserves the language's physical-line rules.
The future-reserved word `context` is a `reserved_word` token that no
production accepts, so it is rejected wherever an identifier is expected.
## Compatibility
Adding nodes or fields is compatible. Removing or renaming a named node or
@@ -103,5 +137,14 @@ field, changing its meaning, or changing which source construct owns it is a
breaking CST change. Until the package reaches 1.0, such changes require corpus
snapshot updates and a version change, and must be called out in release notes.
Version 0.2.0 made these breaking changes:
- `nil_literal` and `wrapper_pattern` are gone, and `some`, `ok`, `err`, and
`nil` are no longer keywords; optionals and results use `sum_constructor`
and `sum_pattern`.
- A same-line pipeline is an operand of prefix and infix operators rather than
taking an operator expression as its `input`.
- `pub_declaration` contains `pub_entry` nodes instead of bare identifiers.
The Tree-sitter tree is designed for source tooling and error recovery; it does
not perform semantic validation.
+150 -28
View File
@@ -17,10 +17,12 @@ const PREC = {
POSTFIX: 13,
};
// Hard keywords and the primitive type names; `reserved_word` adds the
// future-reserved `context`.
const RESERVED_WORDS = [
"and", "as", "bits", "break", "continue", "default", "distinct", "else",
"err", "false", "for", "if", "in", "loop", "match", "nil", "not", "ok", "or",
"pub", "return", "some", "true", "use",
"and", "as", "bits", "break", "continue", "default", "distinct",
"else", "false", "for", "if", "in", "loop", "match", "not", "or", "pub",
"return", "true", "use",
"bit", "chr", "f32", "f64", "i8", "i16", "i32", "i64", "nvr", "str",
"u8", "u16", "u32", "u64",
];
@@ -37,7 +39,7 @@ module.exports = grammar({
extras: $ => [/[ \t]/, $.comment],
word: $ => $.identifier,
reserved: {
global: _ => RESERVED_WORDS,
global: $ => [...RESERVED_WORDS, $.reserved_word],
},
supertypes: $ => [
@@ -67,6 +69,7 @@ module.exports = grammar({
[$.product_constructor, $._scope_separator],
[$.tuple_type],
[$.type_application],
[$.binder],
[$.tuple_pattern],
[$.product_pattern],
[$.array_pattern],
@@ -87,6 +90,7 @@ module.exports = grammar({
[$.pattern_atom, $.primary_expression, $.type_name],
[$.type_name, $.qualified_name],
[$.primary_expression, $.type_name],
[$._pipeline_input_primary, $.type_name],
[$.type_expression, $.function_type],
[$.array_type, $.collection_literal],
],
@@ -110,22 +114,29 @@ module.exports = grammar({
use_declaration: $ => seq(
"use",
choice(
seq($.module_path, optional(seq("as", field("alias", $.identifier)))),
seq(
$.module_path,
optional(choice(seq("as", field("alias", $.identifier)), $.import_filter)),
),
seq($.module_path, ".", "{", listBody($, $.import_member), "}"),
),
),
import_member: $ => seq(
field("name", $.identifier),
optional(seq("as", field("alias", $.identifier))),
optional(choice(seq("as", field("alias", $.identifier)), $.import_filter)),
),
import_filter: $ => seq("for", field("type", $.type_expression)),
module_path: $ => choice(
$.type_name,
seq(choice(".", ".."), $.type_name),
),
pub_declaration: $ => seq("pub", "{", scopeBody($, $.identifier), "}"),
pub_declaration: $ => seq("pub", "{", scopeBody($, $.pub_entry), "}"),
pub_entry: $ => seq(field("name", $.identifier), optional($.import_filter)),
expression: $ => choice(
$.declaration,
@@ -161,6 +172,14 @@ module.exports = grammar({
optional(seq("=", field("value", $.value_expression))),
),
),
seq(
field("name", $.identifier),
":",
field("binder", $.binder),
field("type", $.type_expression),
":",
field("value", $.value_expression),
),
seq(
field("pattern", choice($.tuple_pattern, $.product_pattern)),
choice("::", ":="),
@@ -168,7 +187,33 @@ module.exports = grammar({
),
),
type_definition: $ => seq(optional("distinct"), $.type_expression),
binder: $ => seq("<", listBodyOneOrMore($, $.binder_entry), ">"),
binder_entry: $ => seq(
field("type", $.type_expression),
optional(seq(":", field("constraint", choice($.type_name, $.constraint_body)))),
),
type_definition: $ => choice(
seq(optional("distinct"), $.type_expression),
$.constraint_body,
),
constraint_body: $ => seq(
"<",
">",
"{",
scopeBody($, choice($.constraint_entry, $.constraint_spread)),
"}",
),
constraint_entry: $ => seq(
field("name", $.identifier),
":",
field("output", $.type_expression),
),
constraint_spread: $ => seq("...", field("constraint", $.type_name)),
type_expression: $ => choice(
$.function_type,
@@ -226,6 +271,7 @@ module.exports = grammar({
$.sum_type,
$.bits_type,
$.type_application,
$.output_type,
$.grouped_type,
),
@@ -246,6 +292,15 @@ module.exports = grammar({
">",
),
output_type: $ => seq(
field("constructor", $.type_name),
"<",
field("function", $.type_name),
":",
field("output", $.type_expression),
">",
),
product_type: $ => seq("&", "{", scopeBody($, $.product_field), "}"),
product_field: $ => seq(
@@ -383,8 +438,6 @@ module.exports = grammar({
$.string_literal,
$.character_literal,
$.boolean_literal,
$.nil_literal,
$.wrapper_pattern,
$.sum_pattern,
$.bits_pattern,
$.unit_pattern,
@@ -401,20 +454,15 @@ module.exports = grammar({
sum_pattern: $ => seq(
"|",
optional($.type_name),
optional(field("type", $.type_name)),
".",
field("variant", $.identifier),
optional($.pattern_atom),
),
wrapper_pattern: $ => seq(
field("wrapper", choice("some", "ok", "err")),
field("value", $.pattern_atom),
optional(field("payload", $.pattern_atom)),
),
bits_pattern: $ => seq(
"|",
optional($.type_name),
optional(field("type", $.type_name)),
".",
"{",
listBody($, $.identifier),
@@ -424,11 +472,58 @@ module.exports = grammar({
array_pattern: $ => seq("[", listBody($, $.array_pattern_entry), "]"),
array_pattern_entry: $ => choice($.pattern, seq("...", choice($.identifier, "_"))),
pipeline_expression: $ => prec.dynamic(1, prec.left(choice(
seq(field("input", choice($._prefix_expression, $.function_literal)), $.inline_pipeline_stage, repeat($.pipeline_stage)),
seq(field("input", choice($._non_assignment_expression, $.function_literal)), $.continuation_pipeline_stage, repeat($.pipeline_stage)),
// A pipeline headed by a leading-operator continuation applies to the
// complete ordinary expression on the preceding line. Its input excludes
// a bare same-line pipeline, which instead absorbs the continuation
// stages itself, so every pipeline is one flat node.
pipeline_expression: $ => prec.dynamic(1, prec.left(seq(
field("input", $._pipeline_head),
$.continuation_pipeline_stage,
repeat($.pipeline_stage),
))),
_pipeline_head: $ => choice(
$.range_expression,
$.binary_expression,
$.unary_expression,
$.postfix_expression,
$.primary_expression,
),
// Same-line stages are postfix: they bind to the operand immediately to
// their left, tighter than every prefix and infix operator. The static
// precedence makes a stage sequence inside an operator expression close
// before a continuation stage, which then applies to the whole
// expression. The dynamic precedence reads a category-ambiguous
// `name :: a -> b` as a pipeline rather than a function type.
_inline_pipeline_expression: $ => prec.dynamic(1, prec.left(1, seq(
field("input", choice(
$.postfix_expression,
$._pipeline_input_primary,
$.function_literal,
)),
$.inline_pipeline_stage,
repeat($.pipeline_stage),
))),
// `-3->f` negates the pipeline, so a stage input is never a negative
// literal; a bare `-3` remains one `signed_numeric_literal`.
_pipeline_input_primary: $ => choice(
$._unsigned_literal,
$.identifier,
$.block,
$.if_expression,
$.match_expression,
$.loop_expression,
$.for_expression,
$.tuple_value,
$.collection_literal,
$.product_constructor,
$.sum_constructor,
$.bits_constructor,
$.default_expression,
),
pipeline_stage: $ => choice($.inline_pipeline_stage, $.continuation_pipeline_stage),
inline_pipeline_stage: $ => choice($.direct_pipeline_stage, $.aggregate_pipeline_stage),
continuation_pipeline_stage: $ => choice(
@@ -495,6 +590,7 @@ module.exports = grammar({
_prefix_expression: $ => choice(
$.unary_expression,
alias($._inline_pipeline_expression, $.pipeline_expression),
$.postfix_expression,
$.primary_expression,
),
@@ -529,7 +625,7 @@ module.exports = grammar({
),
unary_expression: $ => prec.right(PREC.PREFIX, seq(
field("operator", choice("not", "-", "~", "some", "ok", "err")),
field("operator", choice("not", "-", "~")),
field("operand", $._prefix_expression),
)),
@@ -580,27 +676,49 @@ module.exports = grammar({
seq("...", $.expression),
),
sum_constructor: $ => seq("|", optional($.type_name), ".", $.identifier),
bits_constructor: $ => seq("|", optional($.type_name), ".", "{", listBody($, $.identifier), "}"),
sum_constructor: $ => seq(
"|",
optional(field("type", $.type_name)),
".",
field("variant", $.identifier),
),
bits_constructor: $ => seq(
"|",
optional(field("type", $.type_name)),
".",
"{",
listBody($, $.identifier),
"}",
),
literal: $ => choice(
$.signed_numeric_literal,
$.string_literal,
$.character_literal,
$.boolean_literal,
$.nil_literal,
$.unit_literal,
),
_unsigned_literal: $ => choice(
alias($._unsigned_numeric_literal, $.signed_numeric_literal),
$.string_literal,
$.character_literal,
$.boolean_literal,
$.unit_literal,
),
boolean_literal: _ => choice("true", "false"),
nil_literal: _ => "nil",
default_expression: _ => "default",
unit_literal: _ => seq("(", ")"),
signed_numeric_literal: $ => seq(optional("-"), choice(
signed_numeric_literal: $ => choice(
prec.dynamic(1, seq("-", $._unsigned_numeric_literal)),
$._unsigned_numeric_literal,
),
_unsigned_numeric_literal: $ => choice(
$.integer_literal,
$.float_literal,
alias($._trailing_dot_float, $.float_literal),
)),
),
signed_integer_literal: $ => seq(optional("-"), $.integer_literal),
integer_literal: _ => token(choice(
@@ -627,6 +745,10 @@ module.exports = grammar({
_direct_pipeline_continuation: _ => token(seq(/\r?\n[ \t]*/, "->")),
_aggregate_pipeline_continuation: _ => token(seq(/\r?\n[ \t]*/, "|>")),
_comparison_continuation: _ => token(seq(/\r?\n[ \t]*/, choice("<=", ">=", "==", "<", ">"))),
// `context` is reserved for future language use and has no production,
// so this token exists only to keep it out of identifiers.
reserved_word: _ => "context",
_newline: _ => /\r?\n/,
_newlines: $ => repeat1($._newline),
_scope_separator: $ => choice($._newlines, ";"),
+5 -4
View File
@@ -6,18 +6,15 @@
"continue"
"distinct"
"else"
"err"
"for"
"if"
"in"
"loop"
"match"
"not"
"ok"
"or"
"pub"
"return"
"some"
"use"
] @keyword
@@ -26,7 +23,6 @@
(comment) @comment
(default_expression) @keyword
(boolean_literal) @constant.builtin
(nil_literal) @constant.builtin
(string_literal) @string
(character_literal) @character
(integer_literal) @number
@@ -39,7 +35,12 @@
(product_field name: (identifier) @property)
(product_entry (identifier) @property)
(sum_variant name: (identifier) @constructor)
(sum_constructor variant: (identifier) @constructor)
(sum_pattern variant: (identifier) @constructor)
(bits_member (identifier) @constant)
(constraint_entry name: (identifier) @function)
(output_type function: (identifier) @function)
(output_type function: (qualified_name (identifier) @function .))
[
"=" "+=" "-=" "*=" "/=" "%=" "&=" "^=" "|="
+718 -205
View File
File diff suppressed because it is too large. Load diff
+361 -108
View File
@@ -65,10 +65,6 @@
"type": "character_literal",
"named": true
},
{
"type": "nil_literal",
"named": true
},
{
"type": "signed_numeric_literal",
"named": true
@@ -129,10 +125,6 @@
"type": "identifier",
"named": true
},
{
"type": "nil_literal",
"named": true
},
{
"type": "product_pattern",
"named": true
@@ -156,10 +148,6 @@
{
"type": "unit_pattern",
"named": true
},
{
"type": "wrapper_pattern",
"named": true
}
]
},
@@ -263,6 +251,10 @@
"type": "optional_type",
"named": true
},
{
"type": "output_type",
"named": true
},
{
"type": "primitive_type",
"named": true
@@ -494,6 +486,10 @@
"type": "map_type",
"named": true
},
{
"type": "output_type",
"named": true
},
{
"type": "primitive_type",
"named": true
@@ -538,6 +534,10 @@
"type": "binary_expression",
"named": true
},
{
"type": "pipeline_expression",
"named": true
},
{
"type": "postfix_expression",
"named": true
@@ -654,6 +654,10 @@
"type": "binary_expression",
"named": true
},
{
"type": "pipeline_expression",
"named": true
},
{
"type": "postfix_expression",
"named": true
@@ -742,6 +746,10 @@
"type": "binary_expression",
"named": true
},
{
"type": "pipeline_expression",
"named": true
},
{
"type": "postfix_expression",
"named": true
@@ -759,15 +767,71 @@
}
},
{
"type": "bits_constructor",
"type": "binder",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "binder_entry",
"named": true
}
]
}
},
{
"type": "binder_entry",
"named": true,
"fields": {
"constraint": {
"multiple": false,
"required": false,
"types": [
{
"type": "constraint_body",
"named": true
},
{
"type": "type_name",
"named": true
}
]
},
"type": {
"multiple": false,
"required": true,
"types": [
{
"type": "type_expression",
"named": true
}
]
}
}
},
{
"type": "bits_constructor",
"named": true,
"fields": {
"type": {
"multiple": false,
"required": false,
"types": [
{
"type": "type_name",
"named": true
}
]
}
},
"children": {
"multiple": true,
"required": false,
"types": [
{
"type": "type_name",
"type": "identifier",
"named": true
}
]
@@ -795,13 +859,24 @@
{
"type": "bits_pattern",
"named": true,
"fields": {},
"fields": {
"type": {
"multiple": false,
"required": false,
"types": [
{
"type": "type_name",
"named": true
}
]
}
},
"children": {
"multiple": true,
"required": false,
"types": [
{
"type": "type_name",
"type": "identifier",
"named": true
}
]
@@ -895,6 +970,67 @@
]
}
},
{
"type": "constraint_body",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": false,
"types": [
{
"type": "constraint_entry",
"named": true
},
{
"type": "constraint_spread",
"named": true
}
]
}
},
{
"type": "constraint_entry",
"named": true,
"fields": {
"name": {
"multiple": false,
"required": true,
"types": [
{
"type": "identifier",
"named": true
}
]
},
"output": {
"multiple": false,
"required": true,
"types": [
{
"type": "type_expression",
"named": true
}
]
}
}
},
{
"type": "constraint_spread",
"named": true,
"fields": {
"constraint": {
"multiple": false,
"required": true,
"types": [
{
"type": "type_name",
"named": true
}
]
}
}
},
{
"type": "continuation_pipeline_stage",
"named": true,
@@ -944,6 +1080,16 @@
"type": "declaration",
"named": true,
"fields": {
"binder": {
"multiple": false,
"required": false,
"types": [
{
"type": "binder",
"named": true
}
]
},
"name": {
"multiple": false,
"required": false,
@@ -1152,6 +1298,10 @@
"type": "optional_type",
"named": true
},
{
"type": "output_type",
"named": true
},
{
"type": "primitive_type",
"named": true
@@ -1254,6 +1404,22 @@
}
}
},
{
"type": "import_filter",
"named": true,
"fields": {
"type": {
"multiple": false,
"required": true,
"types": [
{
"type": "type_expression",
"named": true
}
]
}
}
},
{
"type": "import_member",
"named": true,
@@ -1278,6 +1444,16 @@
}
]
}
},
"children": {
"multiple": false,
"required": false,
"types": [
{
"type": "import_filter",
"named": true
}
]
}
},
{
@@ -1353,6 +1529,10 @@
"type": "map_type",
"named": true
},
{
"type": "output_type",
"named": true
},
{
"type": "primitive_type",
"named": true
@@ -1486,6 +1666,10 @@
"type": "map_type",
"named": true
},
{
"type": "output_type",
"named": true
},
{
"type": "primitive_type",
"named": true
@@ -1518,6 +1702,42 @@
}
}
},
{
"type": "output_type",
"named": true,
"fields": {
"constructor": {
"multiple": false,
"required": true,
"types": [
{
"type": "type_name",
"named": true
}
]
},
"function": {
"multiple": false,
"required": true,
"types": [
{
"type": "type_name",
"named": true
}
]
},
"output": {
"multiple": false,
"required": true,
"types": [
{
"type": "type_expression",
"named": true
}
]
}
}
},
{
"type": "pattern",
"named": true,
@@ -1564,6 +1784,14 @@
"type": "binary_expression",
"named": true
},
{
"type": "boolean_literal",
"named": true
},
{
"type": "character_literal",
"named": true
},
{
"type": "function_literal",
"named": true
@@ -1580,9 +1808,21 @@
"type": "range_expression",
"named": true
},
{
"type": "signed_numeric_literal",
"named": true
},
{
"type": "string_literal",
"named": true
},
{
"type": "unary_expression",
"named": true
},
{
"type": "unit_literal",
"named": true
}
]
}
@@ -1789,7 +2029,33 @@
"required": false,
"types": [
{
"type": "identifier",
"type": "pub_entry",
"named": true
}
]
}
},
{
"type": "pub_entry",
"named": true,
"fields": {
"name": {
"multiple": false,
"required": true,
"types": [
{
"type": "identifier",
"named": true
}
]
}
},
"children": {
"multiple": false,
"required": false,
"types": [
{
"type": "import_filter",
"named": true
}
]
@@ -1822,6 +2088,10 @@
"type": "binary_expression",
"named": true
},
{
"type": "pipeline_expression",
"named": true
},
{
"type": "postfix_expression",
"named": true
@@ -1858,6 +2128,10 @@
"type": "binary_expression",
"named": true
},
{
"type": "pipeline_expression",
"named": true
},
{
"type": "postfix_expression",
"named": true
@@ -1898,6 +2172,10 @@
"type": "map_type",
"named": true
},
{
"type": "output_type",
"named": true
},
{
"type": "primitive_type",
"named": true
@@ -1948,6 +2226,10 @@
"type": "map_type",
"named": true
},
{
"type": "output_type",
"named": true
},
{
"type": "primitive_type",
"named": true
@@ -2048,22 +2330,53 @@
{
"type": "sum_constructor",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "type_name",
"named": true
}
]
}
},
{
"type": "sum_pattern",
"named": true,
"fields": {
"type": {
"multiple": false,
"required": false,
"types": [
{
"type": "type_name",
"named": true
}
]
},
"variant": {
"multiple": false,
"required": true,
"types": [
{
"type": "identifier",
"named": true
}
]
}
}
},
{
"type": "sum_pattern",
"named": true,
"fields": {
"payload": {
"multiple": false,
"required": false,
"types": [
{
"type": "pattern_atom",
"named": true
}
]
},
"type": {
"multiple": false,
"required": false,
"types": [
{
"type": "type_name",
"named": true
}
]
},
"variant": {
"multiple": false,
"required": true,
@@ -2074,20 +2387,6 @@
}
]
}
},
"children": {
"multiple": true,
"required": false,
"types": [
{
"type": "pattern_atom",
"named": true
},
{
"type": "type_name",
"named": true
}
]
}
},
{
@@ -2224,6 +2523,10 @@
"multiple": false,
"required": true,
"types": [
{
"type": "constraint_body",
"named": true
},
{
"type": "type_expression",
"named": true
@@ -2269,6 +2572,10 @@
"type": "optional_type",
"named": true
},
{
"type": "output_type",
"named": true
},
{
"type": "primitive_type",
"named": true
@@ -2354,6 +2661,10 @@
"multiple": false,
"required": true,
"types": [
{
"type": "pipeline_expression",
"named": true
},
{
"type": "postfix_expression",
"named": true
@@ -2376,22 +2687,10 @@
"type": "-",
"named": false
},
{
"type": "err",
"named": false
},
{
"type": "not",
"named": false
},
{
"type": "ok",
"named": false
},
{
"type": "some",
"named": false
},
{
"type": "~",
"named": false
@@ -2439,6 +2738,10 @@
"multiple": true,
"required": true,
"types": [
{
"type": "import_filter",
"named": true
},
{
"type": "import_member",
"named": true
@@ -2450,40 +2753,6 @@
]
}
},
{
"type": "wrapper_pattern",
"named": true,
"fields": {
"value": {
"multiple": false,
"required": true,
"types": [
{
"type": "pattern_atom",
"named": true
}
]
},
"wrapper": {
"multiple": false,
"required": true,
"types": [
{
"type": "err",
"named": false
},
{
"type": "ok",
"named": false
},
{
"type": "some",
"named": false
}
]
}
}
},
{
"type": "!",
"named": false
@@ -2697,10 +2966,6 @@
"type": "else",
"named": false
},
{
"type": "err",
"named": false
},
{
"type": "f32",
"named": false
@@ -2761,10 +3026,6 @@
"type": "match",
"named": false
},
{
"type": "nil_literal",
"named": true
},
{
"type": "not",
"named": false
@@ -2773,10 +3034,6 @@
"type": "nvr",
"named": false
},
{
"type": "ok",
"named": false
},
{
"type": "or",
"named": false
@@ -2789,10 +3046,6 @@
"type": "return",
"named": false
},
{
"type": "some",
"named": false
},
{
"type": "str",
"named": false
+45540 -37130
View File
File diff suppressed because it is too large. Load diff
+240 -16
View File
@@ -36,7 +36,7 @@ Point :: &{
y: i32 = 0
}
point := &Point.{ x: 1; y = 2 }
maybe: []{i32?}: [nil, some 1]
maybe: []{i32?}: [|.none, 1->|.some]
---
@@ -73,11 +73,16 @@ maybe: []{i32?}: [nil, some 1]
(primitive_type))))
(collection_literal
(collection_entry
(nil_literal))
(sum_constructor
(identifier)))
(collection_entry
(unary_expression
(pipeline_expression
(signed_numeric_literal
(integer_literal)))))))
(integer_literal))
(direct_pipeline_stage
(pipeline_callable
(sum_constructor
(identifier)))))))))
==================
Control flow and patterns
@@ -302,17 +307,17 @@ unit :: @ () -> i32 { 42 }
Optional and result wrappers
==================
wrap :: @ value: i32 -> i32 ! str { ok value }
wrap :: @ value: i32 -> i32 ! str { value->|.ok }
first :: @ items: []i32 -> i32? {
match items {
[] => nil
[head, ...rest] => some head
[] => |.none
[head, ...rest] => head->|.some
}
}
unwrap :: @ result: i32 ! str -> i32 {
match result {
ok value => value
err _ => 0
|.ok value => value
|.err _ => 0
}
}
@@ -329,8 +334,12 @@ unwrap :: @ result: i32 ! str -> i32 {
(primitive_type)
(primitive_type))
(block
(unary_expression
(identifier)))))
(pipeline_expression
(identifier)
(direct_pipeline_stage
(pipeline_callable
(sum_constructor
(identifier))))))))
(declaration
(identifier)
(function_literal
@@ -346,7 +355,8 @@ unwrap :: @ result: i32 ! str -> i32 {
(match_arm
(pattern
(array_pattern))
(nil_literal))
(sum_constructor
(identifier)))
(match_arm
(pattern
(array_pattern
@@ -355,8 +365,12 @@ unwrap :: @ result: i32 ! str -> i32 {
(identifier)))
(array_pattern_entry
(identifier))))
(unary_expression
(identifier)))))))
(pipeline_expression
(identifier)
(direct_pipeline_stage
(pipeline_callable
(sum_constructor
(identifier))))))))))
(declaration
(identifier)
(function_literal
@@ -371,12 +385,14 @@ unwrap :: @ result: i32 ! str -> i32 {
(identifier)
(match_arm
(pattern
(wrapper_pattern
(sum_pattern
(identifier)
(identifier)))
(identifier))
(match_arm
(pattern
(wrapper_pattern))
(sum_pattern
(identifier)))
(signed_numeric_literal
(integer_literal))))))))
@@ -433,3 +449,211 @@ mixed :: @ (left: i32, right) -> i32 { left + right }
(binary_expression
(identifier)
(identifier))))))
==================
Postfix pipeline stages
==================
mixed :: -3->add_ten + 2 * 3->double - {1 + 1}->double
checked :: not c->is_digit
unwrapped :: input->fallible?->next
---
(source_file
(declaration
(identifier)
(binary_expression
(binary_expression
(unary_expression
(pipeline_expression
(signed_numeric_literal
(integer_literal))
(direct_pipeline_stage
(pipeline_callable
(identifier)))))
(binary_expression
(signed_numeric_literal
(integer_literal))
(pipeline_expression
(signed_numeric_literal
(integer_literal))
(direct_pipeline_stage
(pipeline_callable
(identifier))))))
(pipeline_expression
(block
(binary_expression
(signed_numeric_literal
(integer_literal))
(signed_numeric_literal
(integer_literal))))
(direct_pipeline_stage
(pipeline_callable
(identifier))))))
(declaration
(identifier)
(unary_expression
(pipeline_expression
(identifier)
(direct_pipeline_stage
(pipeline_callable
(identifier))))))
(declaration
(identifier)
(pipeline_expression
(identifier)
(direct_pipeline_stage
(pipeline_callable
(identifier)))
(direct_pipeline_stage
(pipeline_callable
(identifier))))))
==================
Continuations after same-line stages
==================
flat := value->first->second
->third
whole := a + b->f
->g
---
(source_file
(declaration
(identifier)
(pipeline_expression
(identifier)
(direct_pipeline_stage
(pipeline_callable
(identifier)))
(direct_pipeline_stage
(pipeline_callable
(identifier)))
(continuation_pipeline_stage
(pipeline_callable
(identifier)))))
(declaration
(identifier)
(pipeline_expression
(binary_expression
(identifier)
(pipeline_expression
(identifier)
(direct_pipeline_stage
(pipeline_callable
(identifier)))))
(continuation_pipeline_stage
(pipeline_callable
(identifier))))))
==================
Generic binders and constraints
==================
Show :: <>{
to_str: str
...Display
}
pick: <T: Show, (T, T): Ordered> (T, T) -> T: @ (first, second): (T, T) { first }
Card :: &{ label: Output<make_label: Show>: }
---
(source_file
(declaration
(identifier)
(type_definition
(constraint_body
(constraint_entry
(identifier)
(primitive_type))
(constraint_spread
(identifier)))))
(declaration
(identifier)
(binder
(binder_entry
(identifier)
(identifier))
(binder_entry
(tuple_type
(identifier)
(identifier))
(identifier)))
(function_type
(tuple_type
(identifier)
(identifier))
(identifier))
(function_literal
(typed_parameter
(tuple_pattern
(pattern
(identifier))
(pattern
(identifier)))
(tuple_type
(identifier)
(identifier)))
(block
(identifier))))
(declaration
(identifier)
(type_definition
(product_type
(product_field
(identifier)
(output_type
(identifier)
(identifier)
(identifier)))))))
==================
Import filters
==================
use math.{sqrt for f64, pi as p}
use .shapes.area for Circle
pub { area for Circle; sqrt }
---
(source_file
(use_declaration
(module_path
(identifier))
(import_member
(identifier)
(import_filter
(primitive_type)))
(import_member
(identifier)
(identifier)))
(use_declaration
(module_path
(qualified_name
(identifier)
(identifier)))
(import_filter
(identifier)))
(pub_declaration
(pub_entry
(identifier)
(import_filter
(identifier)))
(pub_entry
(identifier))))
==================
Reserved word
:error
==================
context := 1
---
-1
View File
@@ -1 +0,0 @@
value :: @ x: i32 -> i32 ! str { x! }
-1
View File
@@ -1 +0,0 @@
value :: @ x: str -> i32 ! str { !x }
-1
View File
@@ -1 +0,0 @@
ok :: @ value { value }
-1
View File
@@ -1 +0,0 @@
some := 1
-1
View File
@@ -1 +0,0 @@
value: i32?: 1->|.some
-1
View File
@@ -1 +0,0 @@
value :: @ x: i32? { match x { |.some y => y; nil => 0 } }
-1
View File
@@ -1 +0,0 @@
value :: 1 + 2->to_str
+2 -2
View File
@@ -6,8 +6,8 @@ more_codes := [...codes, 'failed' => 500]
empty_numbers: []i32: []
empty_codes: [str]i32: []
optional_numbers: []i32?: nil
optional_elements: []{i32?}: [nil, some 1]
optional_numbers: []i32?: |.none
optional_elements: []{i32?}: [|.none, 1->|.some]
Options :: &{
name: str: 'default'
+2 -2
View File
@@ -11,10 +11,10 @@ choose_block: (bit, i32, i32) -> i32: @ (condition: bit, when_true: i32, when_fa
first_positive: []i32 -> i32?: @ values: []i32 {
for value in values {
if value > 0 {
return some value
return value->|.some
}
}
nil
|.none
}
labelled_search: []i32 -> i32: @ values: []i32 { loop `outer {
+5
View File
@@ -0,0 +1,5 @@
add_ten: i32 -> i32: @ value: i32 { value + 10 }
double: i32 -> i32: @ value: i32 { value * 2 }
# Stages bind tighter than prefix and infix operators: -{3->add_ten} + 2 * {3->double}.
mixed: i32: -3->add_ten + 2 * 3->double - {1 + 1}->double
+2 -2
View File
@@ -15,9 +15,9 @@ commented := 'value' # an inline comment preserves continuation
computed := 1 + 2
->to_str
fallible: i32 -> i32 ! Error: @ value: i32 { ok value }
fallible: i32 -> i32 ! Error: @ value: i32 { value->|.ok }
propagate: i32 -> i32 ! Error: @ value: i32 {
result := {value->fallible}?
ok result
result->|.ok
}
-19
View File
@@ -1,19 +0,0 @@
use math.{pi, sqrt}
use math as m
use random.{Pcg32, pcg32, draw_u32}
use time.{Duration, TimeError, duration_neg}
use time as t
circle: f64: pi
root: f64: 4.0->sqrt
# Host overloads are selected by the operand, so the literal is typed first.
nine: f32: 9.0
qualified_root: f32: nine->m.sqrt
qualified_pi: f32: m.pi
seed: u64: 42
generator: Pcg32: seed->pcg32
draw: u32: generator->draw_u32
negate: Duration => Duration ! TimeError: duration_neg
qualified_negate: t.Duration => t.Duration ! t.TimeError: t.duration_neg
-24
View File
@@ -1,24 +0,0 @@
present: i32?: some 1
absent: i32?: nil
success: i32 ! str: ok 1
failure: i32 ! str: err 'failed'
piped: i32?: some 4->double
grouped: i32?: some {4->double}
double :: @ value { value * 2 }
describe :: @ (label: str, count) -> str { label }
unwrap_or: i32? -> i32: @ value {
match value {
some inner => inner
nil => 0
}
}
handle: i32 ! str -> i32: @ result {
match result {
ok value if value > 0 => value
ok _ => 0
err message => message->length
}
}