This commit is contained in:
2026-09-23 00:36:22 -04:00
commit 6e4f9cd65a
74 changed files with 57163 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
/node_modules/
/target/
Generated
+215
View File
@@ -0,0 +1,215 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
dependencies = [
"memchr",
]
[[package]]
name = "cc"
version = "1.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54413ede23c2daf518f35156dfde027feb2374004d63bd497f983c8db9c0e313"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "find-msvc-tools"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef25905e51abafe4dcea6c15fec58c57b601cdbd0ee53d22ea1d3016c587d39b"
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]]
name = "indexmap"
version = "2.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "memchr"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
[[package]]
name = "serde"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
dependencies = [
"indexmap",
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "shlex"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520"
[[package]]
name = "syn"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tree-sitter"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2038684e0058edba0d17302619f62eabce4a8e11c6ac59506996a8d79848851d"
dependencies = [
"cc",
"regex",
"serde_json",
"streaming-iterator",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-language"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca0d1bf6fdd806e43ae5198f82f527056d359def39e54e67a0f478ac09dac081"
[[package]]
name = "tree-sitter-lscript"
version = "0.1.0"
dependencies = [
"cc",
"tree-sitter",
"tree-sitter-language",
]
[[package]]
name = "unicode-ident"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
[[package]]
name = "zmij"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
+24
View File
@@ -0,0 +1,24 @@
[package]
name = "tree-sitter-lscript"
description = "LScript grammar for Tree-sitter"
version = "0.1.0"
publish = false
readme = "README.md"
keywords = ["incremental", "parsing", "tree-sitter", "lscript"]
categories = ["parsing", "text-editors"]
edition = "2024"
autoexamples = false
build = "bindings/rust/build.rs"
include = ["bindings/rust/*", "docs/*", "grammar.js", "queries/*", "src/*", "tree-sitter.json"]
[lib]
path = "bindings/rust/lib.rs"
[dependencies]
tree-sitter-language = "0.1"
[build-dependencies]
cc = "1.2"
[dev-dependencies]
tree-sitter = "0.27"
+36
View File
@@ -0,0 +1,36 @@
# tree-sitter-lscript
Tree-sitter grammar and editor queries for LScript.
This package provides a lossless, error-tolerant concrete syntax tree for
LScript editors and source tools. Syntax fixtures and tests are included here.
The public named-node and field contract is documented in
[docs/cst.md](docs/cst.md).
## Development
Tree-sitter 0.27 and Node.js are required.
```sh
npm install
npm run generate
npm test
npm run test:fixtures
npm run test:queries
cargo test
```
`test:fixtures` requires every syntax-valid fixture to parse without an `ERROR`
or `MISSING` node. It also checks syntax-invalid fixtures and fails if a new one
is accepted without recovery. Three exceptions are listed explicitly by the
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.
Generated parser sources under `src/` are committed so consumers do not need
Node.js or the Tree-sitter CLI.
+15
View File
@@ -0,0 +1,15 @@
fn main() {
let source_directory = std::path::Path::new("src");
let parser = source_directory.join("parser.c");
let scanner = source_directory.join("scanner.c");
cc::Build::new()
.std("c11")
.include(source_directory)
.file(&parser)
.file(&scanner)
.compile("tree-sitter-lscript");
println!("cargo:rerun-if-changed={}", parser.display());
println!("cargo:rerun-if-changed={}", scanner.display());
}
+187
View File
@@ -0,0 +1,187 @@
//! LScript language support for the Tree-sitter parsing library.
use tree_sitter_language::LanguageFn;
unsafe extern "C" {
fn tree_sitter_lscript() -> *const ();
}
/// The Tree-sitter language function for LScript.
pub const LANGUAGE: LanguageFn = unsafe { LanguageFn::from_raw(tree_sitter_lscript) };
/// Generated descriptions of the grammar's named node types.
pub const NODE_TYPES: &str = include_str!("../../src/node-types.json");
/// The default syntax-highlighting query.
pub const HIGHLIGHTS_QUERY: &str = include_str!("../../queries/highlights.scm");
#[cfg(test)]
mod tests {
fn parse(source: &str) -> tree_sitter::Tree {
let mut parser = tree_sitter::Parser::new();
parser
.set_language(&super::LANGUAGE.into())
.expect("generated LScript grammar must load");
parser
.parse(source, None)
.expect("parser must produce a tree")
}
#[test]
fn loads_and_parses_lscript() {
let tree = parse("answer :: 42\n");
assert!(!tree.root_node().has_error());
}
#[test]
fn exposes_stable_expression_fields_without_precedence_wrappers() {
let source = "answer := left + right * -value\n";
let tree = parse(source);
let declaration = tree.root_node().named_child(0).unwrap();
let addition = declaration.child_by_field_name("value").unwrap();
assert_eq!(addition.kind(), "binary_expression");
assert_eq!(
addition
.child_by_field_name("operator")
.unwrap()
.utf8_text(source.as_bytes())
.unwrap(),
"+"
);
assert_eq!(
addition.child_by_field_name("left").unwrap().kind(),
"identifier"
);
let multiplication = addition.child_by_field_name("right").unwrap();
assert_eq!(multiplication.kind(), "binary_expression");
let negation = multiplication.child_by_field_name("right").unwrap();
assert_eq!(negation.kind(), "unary_expression");
assert_eq!(
negation.child_by_field_name("operand").unwrap().kind(),
"identifier"
);
}
#[test]
fn exposes_meaningful_type_constructor_fields() {
let source = "Transform :: []{i32?} -> [str]i32 ! Error\n";
let tree = parse(source);
let declaration = tree.root_node().named_child(0).unwrap();
let definition = declaration.child_by_field_name("value").unwrap();
let function = definition.named_child(0).unwrap();
assert_eq!(function.kind(), "function_type");
assert_eq!(
function
.child_by_field_name("effect")
.unwrap()
.utf8_text(source.as_bytes())
.unwrap(),
"->"
);
assert_eq!(
function.child_by_field_name("input").unwrap().kind(),
"array_type"
);
let result = function.child_by_field_name("result").unwrap();
assert_eq!(result.kind(), "result_type");
assert_eq!(
result.child_by_field_name("value").unwrap().kind(),
"map_type"
);
assert_eq!(
result.child_by_field_name("error").unwrap().kind(),
"identifier"
);
}
#[test]
fn exposes_typed_function_headers() {
let source =
"named :: @ value: i32 => i32 { value }\nadd :: @ (a: i32, b: i32) { a + b }\n";
let tree = parse(source);
assert!(
!tree.root_node().has_error(),
"{}",
tree.root_node().to_sexp()
);
let named = tree.root_node().named_child(0).unwrap();
let function = named.child_by_field_name("value").unwrap();
assert_eq!(function.kind(), "function_literal");
let parameter = function.child_by_field_name("parameter").unwrap();
assert_eq!(parameter.kind(), "typed_parameter");
assert_eq!(
parameter
.child_by_field_name("pattern")
.unwrap()
.utf8_text(source.as_bytes())
.unwrap(),
"value"
);
assert_eq!(
function
.child_by_field_name("effect")
.unwrap()
.utf8_text(source.as_bytes())
.unwrap(),
"=>"
);
assert_eq!(
function.child_by_field_name("result").unwrap().kind(),
"primitive_type"
);
assert_eq!(
function.child_by_field_name("body").unwrap().kind(),
"block"
);
let tuple_declaration = tree.root_node().named_child(1).unwrap();
let tuple_function = tuple_declaration.child_by_field_name("value").unwrap();
let tuple_input = tuple_function.child_by_field_name("parameter").unwrap();
assert_eq!(tuple_input.kind(), "typed_tuple_pattern");
assert!(tuple_function.child_by_field_name("parameters").is_none());
assert_eq!(tuple_input.named_child_count(), 2);
for index in 0..2 {
assert_eq!(
tuple_input.named_child(index).unwrap().kind(),
"typed_tuple_component"
);
}
}
#[test]
fn rejects_removed_function_literal_forms() {
for source in [
"old: i32 -> i32: value@ value\n",
"old: i32 -> i32:\n@ value: i32 { value }\n",
"old :: @ value\n",
"old :: @ (value:i32) { value }\n",
"old :: @ (value:i32,) { value }\n",
"old :: @ (\nvalue:i32\n) -> i32 { value }\n",
] {
assert!(parse(source).root_node().has_error(), "accepted {source}");
}
}
#[test]
fn distinguishes_compact_ranges_floats_and_word_prefixes() {
let source = "value := (item.value, 0..<limit)->slice\n\
trailing_dot := 1.\n\
anchor := value\n\
android := value\n\
order: []Item = []\n\
ready := first\n\
and second\n\
or fallback\n";
let tree = parse(source);
assert!(
!tree.root_node().has_error(),
"{}",
tree.root_node().to_sexp()
);
}
}
+87
View File
@@ -0,0 +1,87 @@
# Concrete syntax tree contract
This document describes the named nodes and fields intended for consumers such
as editor integrations, formatters, and structural search. Rules prefixed with `_`
and rules listed as Tree-sitter supertypes are implementation details and do not
appear as named wrapper nodes in ordinary trees.
## Source and declarations
`source_file` contains declarations, imports, publication declarations, and
standalone expressions directly. The choice-only `module_item`, `expression`,
and `value_expression` rules are hidden supertypes.
`declaration` uses these fields:
- `name` for a named declaration;
- `pattern` for a destructuring declaration;
- `type` for an explicit type;
- `value` for an initializer.
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.
## Expressions
Only expressions that actually contain an operator produce an operator node:
| Node | Fields |
| --- | --- |
| `assignment_expression` | `left`, `operator`, `right` |
| `range_expression` | `left`, `operator`, `right` |
| `binary_expression` | `left`, `operator`, `right` |
| `unary_expression` | `operator`, `operand` |
| `postfix_expression` | `value`, repeated `operator`; member and index operators additionally expose `member` or `index` |
| `pipeline_expression` | `input`, followed by stage nodes |
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.
`function_literal` takes one input and owns the `body` field. Direct binding
appears as `parameter: (typed_parameter)`; tuple destructuring appears as
`parameter: (typed_tuple_pattern)`. A `typed_tuple_pattern` contains zero or at
least two `typed_tuple_component` nodes. These bind components of one tuple
input, not separate function arguments. `()` matches unit; singleton tuple
patterns are invalid. Typed bindings expose `pattern` and `type`; a header may
also expose `effect` (`->` or `=>`) and `result`.
## Types
Meaningful type constructors are named directly:
| Node | Fields |
| --- | --- |
| `function_type` | `input`, `effect`, `result` |
| `result_type` | `value`, `error` |
| `optional_type` | `value` |
| `array_type` | `element` |
| `map_type` | `key`, `value` |
| `grouped_type` | `value` |
| `type_application` | `constructor` followed by its type arguments |
Primitive, unit, tuple, product, sum, and bits types retain their corresponding
named nodes. Internal primary/container/result categories are hidden.
## Trivia and physical lines
Comments are named extra nodes. Spaces, tabs, and structural newlines remain in
the source gaps between nodes, so a formatter must retain the original source
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.
## Compatibility
Adding nodes or fields is compatible. Removing or renaming a named node or
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.
The Tree-sitter tree is designed for source tooling and error recovery; it does
not perform semantic validation.
+692
View File
@@ -0,0 +1,692 @@
/// <reference types="tree-sitter-cli/dsl" />
// @ts-check
const PREC = {
ASSIGNMENT: 1,
RANGE: 2,
OR: 3,
AND: 4,
NOT: 5,
COMPARE: 6,
BIT_OR: 7,
BIT_XOR: 8,
BIT_AND: 9,
ADD: 10,
MULTIPLY: 11,
PREFIX: 12,
POSTFIX: 13,
};
const RESERVED_WORDS = [
"and", "as", "bits", "break", "continue", "default", "distinct", "else",
"false", "for", "if", "in", "loop", "match", "nil", "not", "or", "pub",
"return", "true", "use",
"bit", "chr", "f32", "f64", "i8", "i16", "i32", "i64", "nvr", "str",
"u8", "u16", "u32", "u64",
];
module.exports = grammar({
name: "lscript",
externals: $ => [
$._and_continuation,
$._or_continuation,
$._trailing_dot_float,
],
extras: $ => [/[ \t]/, $.comment],
word: $ => $.identifier,
reserved: {
global: _ => RESERVED_WORDS,
},
supertypes: $ => [
$.module_item,
$.expression,
$.value_expression,
$.type_expression,
$.primary_expression,
$.literal,
$.type_name,
$.binding_pattern,
$.pattern_atom,
$.pipeline_stage,
$.inline_pipeline_stage,
],
conflicts: $ => [
[$.product_pattern_field, $.primary_expression],
[$.pattern_atom, $.literal],
[$.pattern_atom, $.primary_expression],
[$.signed_numeric_literal],
[$.source_file, $._scope_separator],
[$.qualified_name],
[$.block, $._scope_separator],
[$.match_expression, $._scope_separator],
[$.product_constructor, $._scope_separator],
[$.tuple_type],
[$.type_application],
[$.tuple_pattern],
[$.product_pattern],
[$.array_pattern],
[$.tuple_value],
[$.collection_literal],
[$.block, $.product_pattern],
[$.array_pattern, $.collection_literal],
[$.typed_tuple_pattern],
[$.unit_pattern, $.unit_literal],
[$.sum_pattern, $.sum_constructor],
[$.bits_pattern, $.bits_constructor],
[$.primary_expression, $.qualified_name],
[$.array_pattern_entry, $.primary_expression],
[$.continue_expression],
[$.unit_type, $.unit_literal],
[$.unit_type, $.unit_pattern, $.unit_literal],
[$.product_pattern_field, $.primary_expression, $.type_name],
[$.pattern_atom, $.primary_expression, $.type_name],
[$.type_name, $.qualified_name],
[$.primary_expression, $.type_name],
[$.type_expression, $.function_type],
[$.array_type, $.collection_literal],
],
rules: {
source_file: $ => seq(
repeat($._newline),
optional(seq(
$.module_item,
repeat(seq($._scope_separator, $.module_item)),
repeat($._newline),
)),
),
module_item: $ => choice(
$.use_declaration,
$.pub_declaration,
$.expression,
),
use_declaration: $ => seq(
"use",
choice(
seq($.module_path, optional(seq("as", field("alias", $.identifier)))),
seq($.module_path, ".", "{", listBody($, $.import_member), "}"),
),
),
import_member: $ => seq(
field("name", $.identifier),
optional(seq("as", field("alias", $.identifier))),
),
module_path: $ => choice(
$.type_name,
seq(choice(".", ".."), $.type_name),
),
pub_declaration: $ => seq("pub", "{", scopeBody($, $.identifier), "}"),
expression: $ => choice(
$.declaration,
$.value_expression,
),
value_expression: $ => choice(
$.function_literal,
$.return_expression,
$.break_expression,
$.continue_expression,
$.pipeline_expression,
$._ordinary_expression,
),
declaration: $ => choice(
seq(
field("name", $.identifier),
"::",
field("value", choice($.type_definition, prec(1, $.value_expression))),
),
seq(
field("name", $.identifier),
":=",
field("value", $.value_expression),
),
seq(
field("name", $.identifier),
":",
field("type", $.type_expression),
choice(
seq(":", field("value", $.value_expression)),
optional(seq("=", field("value", $.value_expression))),
),
),
seq(
field("pattern", choice($.tuple_pattern, $.product_pattern)),
choice("::", ":="),
field("value", $.value_expression),
),
),
type_definition: $ => seq(optional("distinct"), $.type_expression),
type_expression: $ => choice(
$.function_type,
$._result_type,
),
function_type: $ => prec.right(seq(
field("input", $._result_type),
field("effect", choice("->", "=>")),
field("result", $.type_expression),
)),
_result_type: $ => choice(
$.result_type,
$.optional_type,
$._container_type,
),
result_type: $ => seq(
field("value", $._container_type),
"!",
field("error", $._container_type),
),
optional_type: $ => seq(
field("value", $._container_type),
repeat1("?"),
),
_container_type: $ => choice(
$.array_type,
$.map_type,
$._primary_type,
),
array_type: $ => prec.right(seq(
"[",
"]",
field("element", $._container_type),
)),
map_type: $ => prec.right(seq(
"[",
field("key", $.type_expression),
"]",
field("value", $._container_type),
)),
_primary_type: $ => choice(
$.primitive_type,
$.type_name,
$.unit_type,
$.tuple_type,
$.product_type,
$.sum_type,
$.bits_type,
$.type_application,
$.grouped_type,
),
grouped_type: $ => seq("{", field("value", $.type_expression), "}"),
unit_type: _ => seq("(", ")"),
tuple_type: $ => seq(
"(",
listBodyAtLeastTwo($, $.type_expression),
")",
),
type_application: $ => seq(
field("constructor", $.type_name),
"<",
listBody($, $.type_expression),
">",
),
product_type: $ => seq("&", "{", scopeBody($, $.product_field), "}"),
product_field: $ => seq(
field("name", $.identifier),
":",
field("type", $.type_expression),
choice(":", "="),
optional(field("default", $.expression)),
),
sum_type: $ => seq(
"|",
optional($.integer_type),
"{",
scopeBody($, $.sum_variant),
"}",
),
sum_variant: $ => seq(
field("name", $.identifier),
optional(choice(
seq(":", field("payload", $.type_expression)),
seq("::", field("discriminant", $.signed_integer_literal)),
)),
),
bits_type: $ => seq(
"bits",
"|",
$.unsigned_integer_type,
"{",
scopeBody($, $.bits_member),
"}",
),
bits_member: $ => seq($.identifier, "::", $.integer_literal),
primitive_type: _ => choice(
"f32", "f64", "bit", "chr", "str", "nvr",
"i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64",
),
integer_type: _ => choice("i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64"),
unsigned_integer_type: _ => choice("u8", "u16", "u32", "u64"),
function_literal: $ => choice(
prec(1, seq(
"@",
field("parameter", $.typed_parameter),
optional(seq(
field("effect", choice("->", "=>")),
field("result", $.type_expression),
)),
field("body", $.block),
)),
prec(1, seq(
"@",
field("parameter", $.typed_tuple_pattern),
optional(seq(
field("effect", choice("->", "=>")),
field("result", $.type_expression),
)),
field("body", $.block),
)),
prec(1, seq("@", field("body", $.block))),
),
typed_tuple_pattern: $ => seq("(", optional(listBodyAtLeastTwo($, $.typed_tuple_component)), ")"),
typed_parameter: $ => seq(
field("pattern", $.binding_pattern),
":",
field("type", $._result_type),
),
typed_tuple_component: $ => seq(
field("pattern", $.binding_pattern),
":",
field("type", $.type_expression),
),
return_expression: $ => prec.right(seq("return", optional($.expression))),
break_expression: $ => prec.right(seq("break", optional($.label), optional($.expression))),
continue_expression: $ => seq("continue", optional($.label)),
label: $ => seq("`", $.identifier),
if_expression: $ => prec.right(seq(
"if",
field("condition", $.expression),
field("consequence", $.block),
optional(seq("else", field("alternative", choice($.block, $.if_expression)))),
)),
match_expression: $ => seq(
"match",
field("value", $.expression),
"{",
scopeBody($, $.match_arm),
"}",
),
match_arm: $ => seq(
field("pattern", $.pattern),
optional(seq("if", field("guard", $.expression))),
"=>",
field("body", $.expression),
),
loop_expression: $ => seq("loop", optional($.label), $.block),
for_expression: $ => seq(
"for",
field("pattern", $.binding_pattern),
"in",
field("value", $.expression),
optional($.label),
field("body", $.block),
),
block: $ => seq("{", scopeBody($, $.expression), "}"),
pattern: $ => prec.left(seq($.pattern_atom, repeat(seq("or", $.pattern_atom)))),
binding_pattern: $ => choice(
"_",
$.identifier,
$.unit_pattern,
$.tuple_pattern,
$.product_pattern,
),
pattern_atom: $ => choice(
"_",
$.identifier,
$.signed_numeric_literal,
$.string_literal,
$.character_literal,
$.boolean_literal,
$.nil_literal,
$.sum_pattern,
$.bits_pattern,
$.unit_pattern,
$.tuple_pattern,
$.product_pattern,
$.array_pattern,
),
unit_pattern: _ => seq("(", ")"),
tuple_pattern: $ => seq("(", listBodyAtLeastTwo($, $.pattern), ")"),
product_pattern: $ => seq("{", listBody($, $.product_pattern_field), "}"),
product_pattern_field: $ => seq($.identifier, optional(seq("as", $.identifier))),
sum_pattern: $ => seq(
"|",
optional($.type_name),
".",
field("variant", $.identifier),
optional($.pattern_atom),
),
bits_pattern: $ => seq(
"|",
optional($.type_name),
".",
"{",
listBody($, $.identifier),
"}",
),
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)),
))),
pipeline_stage: $ => choice($.inline_pipeline_stage, $.continuation_pipeline_stage),
inline_pipeline_stage: $ => choice($.direct_pipeline_stage, $.aggregate_pipeline_stage),
continuation_pipeline_stage: $ => choice(
seq(
$._direct_pipeline_continuation,
field("callable", $.pipeline_callable),
repeat(choice("?", "!")),
),
seq(
$._aggregate_pipeline_continuation,
field("callable", $.aggregate_pipeline_callable),
field("arguments", $.aggregate_tail),
repeat(choice("?", "!")),
),
),
direct_pipeline_stage: $ => seq(
"->",
field("callable", $.pipeline_callable),
repeat(choice("?", "!")),
),
aggregate_pipeline_stage: $ => seq(
"|>",
field("callable", $.aggregate_pipeline_callable),
field("arguments", $.aggregate_tail),
repeat(choice("?", "!")),
),
aggregate_tail: $ => choice(
seq("(", listBodyOneOrMore($, $.expression), ")"),
seq("[", optional(listBody($, $.array_tail_entry)), "]"),
),
array_tail_entry: $ => choice($.expression, seq("...", $.expression)),
pipeline_callable: $ => seq(
$.primary_expression,
repeat(choice(
seq(".", $.identifier),
seq("[", $.expression, "]"),
)),
),
aggregate_pipeline_callable: $ => seq(
$.primary_expression,
repeat(seq(".", $.identifier)),
),
_ordinary_expression: $ => choice(
$.assignment_expression,
$._non_assignment_expression,
),
_non_assignment_expression: $ => choice(
$.range_expression,
$._binary_expression,
),
_binary_expression: $ => choice(
$.binary_expression,
$._prefix_expression,
),
_prefix_expression: $ => choice(
$.unary_expression,
$.postfix_expression,
$.primary_expression,
),
assignment_expression: $ => prec.right(PREC.ASSIGNMENT, seq(
field("left", $._non_assignment_expression),
field("operator", choice("=", "+=", "-=", "*=", "/=", "%=", "&=", "^=", "|=")),
field("right", choice(
$.function_literal,
$.return_expression,
$.break_expression,
$.pipeline_expression,
$._non_assignment_expression,
)),
)),
range_expression: $ => prec.left(PREC.RANGE, seq(
field("left", $._binary_expression),
field("operator", choice("..<", "..=")),
field("right", $._binary_expression),
)),
binary_expression: $ => choice(
binaryExpression($, "or", PREC.OR, $._or_continuation, true),
binaryExpression($, "and", PREC.AND, $._and_continuation, true),
binaryExpression($, choice("<", "<=", ">", ">=", "=="), PREC.COMPARE, $._comparison_continuation, true),
binaryExpression($, "|", PREC.BIT_OR, null),
binaryExpression($, "^", PREC.BIT_XOR),
binaryExpression($, "&", PREC.BIT_AND, null),
binaryExpression($, choice("+", "-"), PREC.ADD, "+"),
binaryExpression($, choice("*", "/", "%"), PREC.MULTIPLY),
),
unary_expression: $ => prec.right(PREC.PREFIX, seq(
field("operator", choice("not", "!", "-", "~")),
field("operand", $._prefix_expression),
)),
postfix_expression: $ => prec.left(PREC.POSTFIX, seq(
field("value", $.primary_expression),
repeat1(field("operator", choice(
seq(".", field("member", $.identifier)),
seq("[", field("index", $.expression), "]"),
"?",
"!",
))),
)),
primary_expression: $ => choice(
$.literal,
$.identifier,
$.block,
$.if_expression,
$.match_expression,
$.loop_expression,
$.for_expression,
$.tuple_value,
$.collection_literal,
$.product_constructor,
$.sum_constructor,
$.bits_constructor,
$.default_expression,
),
tuple_value: $ => seq("(", listBodyAtLeastTwo($, $.expression), ")"),
collection_literal: $ => seq("[", listBody($, $.collection_entry), "]"),
collection_entry: $ => choice(
seq($.expression, optional(seq("=>", $.expression))),
seq("...", $.expression),
),
product_constructor: $ => seq(
"&",
optional($.type_name),
".",
"{",
scopeBody($, $.product_entry),
"}",
),
product_entry: $ => choice(
seq($.identifier, choice(":", "="), $.expression),
seq("...", $.expression),
),
sum_constructor: $ => seq("|", optional($.type_name), ".", $.identifier),
bits_constructor: $ => seq("|", optional($.type_name), ".", "{", listBody($, $.identifier), "}"),
literal: $ => choice(
$.signed_numeric_literal,
$.string_literal,
$.character_literal,
$.boolean_literal,
$.nil_literal,
$.unit_literal,
),
boolean_literal: _ => choice("true", "false"),
nil_literal: _ => "nil",
default_expression: _ => "default",
unit_literal: _ => seq("(", ")"),
signed_numeric_literal: $ => seq(optional("-"), choice(
$.integer_literal,
$.float_literal,
alias($._trailing_dot_float, $.float_literal),
)),
signed_integer_literal: $ => seq(optional("-"), $.integer_literal),
integer_literal: _ => token(choice(
/0[bB][01](?:_?[01])*/,
/0[oO][0-7](?:_?[0-7])*/,
/0[xX][0-9a-fA-F](?:_?[0-9a-fA-F])*/,
/[0-9](?:_?[0-9])*/,
)),
float_literal: _ => token(choice(
/[0-9](?:_?[0-9])*\.[0-9](?:_?[0-9])*(?:[eE][+-]?[0-9](?:_?[0-9])*)?/,
/\.[0-9](?:_?[0-9])*(?:[eE][+-]?[0-9](?:_?[0-9])*)?/,
/[0-9](?:_?[0-9])*[eE][+-]?[0-9](?:_?[0-9])*/,
)),
string_literal: _ => token(/'(?:[^'\\\r\n]|\\(?:[\\'nrt0]|u\{[0-9a-fA-F]{1,6}\}))*'/),
character_literal: _ => token(/c'(?:[^'\\\r\n]|\\(?:[\\'nrt0]|u\{[0-9a-fA-F]{1,6}\}))'/),
type_name: $ => choice($.identifier, $.qualified_name),
qualified_name: $ => seq($.identifier, repeat1(seq(".", $.identifier))),
identifier: _ => /[A-Za-z_][A-Za-z0-9_]*/,
comment: _ => token(seq("#", /[^\r\n]*/)),
_direct_pipeline_continuation: _ => token(seq(/\r?\n[ \t]*/, "->")),
_aggregate_pipeline_continuation: _ => token(seq(/\r?\n[ \t]*/, "|>")),
_comparison_continuation: _ => token(seq(/\r?\n[ \t]*/, choice("<=", ">=", "==", "<", ">"))),
_newline: _ => /\r?\n/,
_newlines: $ => repeat1($._newline),
_scope_separator: $ => choice($._newlines, ";"),
},
});
function scopeBody($, rule) {
return seq(
repeat($._newline),
optional(seq(
rule,
repeat(seq($._scope_separator, rule)),
repeat($._newline),
)),
);
}
function listBody($, rule) {
return seq(
repeat($._newline),
optional(seq(
rule,
repeat(seq(choice(seq(",", repeat($._newline)), $._newlines), rule)),
optional(","),
repeat($._newline),
)),
);
}
function listBodyOneOrMore($, rule) {
return seq(
repeat($._newline),
rule,
repeat(seq(choice(seq(",", repeat($._newline)), $._newlines), rule)),
optional(","),
repeat($._newline),
);
}
function listBodyAtLeastTwo($, rule) {
return seq(
repeat($._newline),
rule,
choice(seq(",", repeat($._newline)), $._newlines),
rule,
repeat(seq(choice(seq(",", repeat($._newline)), $._newlines), rule)),
optional(","),
repeat($._newline),
);
}
function binaryExpression($, operator, precedence, continuationOperator = operator, continuationIsToken = false) {
const operatorRule = continuationOperator === null
? seq(operator, optional($._newline))
: choice(
seq(operator, optional($._newline)),
continuationIsToken
? continuationOperator
: token(seq(/\r?\n[ \t]*/, continuationOperator)),
);
return prec.left(precedence, seq(
field("left", $._binary_expression),
field("operator", operatorRule),
field("right", $._binary_expression),
));
}
+31
View File
@@ -0,0 +1,31 @@
{
"name": "tree-sitter-lscript",
"version": "0.1.0",
"description": "LScript grammar for Tree-sitter",
"private": true,
"license": "UNLICENSED",
"keywords": [
"incremental",
"parsing",
"tree-sitter",
"lscript"
],
"files": [
"grammar.js",
"tree-sitter.json",
"docs/*",
"queries/*",
"scripts/*",
"test/**",
"src/**"
],
"devDependencies": {
"tree-sitter-cli": "0.27.0"
},
"scripts": {
"generate": "tree-sitter generate",
"test": "tree-sitter test",
"test:fixtures": "sh ./scripts/check-fixtures.sh",
"test:queries": "tree-sitter query -p . --quiet queries/highlights.scm test/fixtures/valid/*.ls"
}
}
+46
View File
@@ -0,0 +1,46 @@
[
"and"
"as"
"bits"
"break"
"continue"
"distinct"
"else"
"for"
"if"
"in"
"loop"
"match"
"not"
"or"
"pub"
"return"
"use"
] @keyword
(primitive_type) @type.builtin
(identifier) @variable
(comment) @comment
(default_expression) @keyword
(boolean_literal) @constant.builtin
(nil_literal) @constant.builtin
(string_literal) @string
(character_literal) @character
(integer_literal) @number
(float_literal) @number.float
(label (identifier) @label)
(declaration name: (identifier) @variable.definition)
(typed_parameter pattern: (identifier) @variable.parameter)
(typed_tuple_component pattern: (identifier) @variable.parameter)
(product_field name: (identifier) @property)
(product_entry (identifier) @property)
(sum_variant name: (identifier) @constructor)
(bits_member (identifier) @constant)
[
"=" "+=" "-=" "*=" "/=" "%=" "&=" "^=" "|="
"==" "<" "<=" ">" ">="
"+" "-" "*" "/" "%" "&" "|" "^" "~" "!"
"->" "=>" "|>" "..<" "..=" "@"
] @operator
+32
View File
@@ -0,0 +1,32 @@
#!/bin/sh
set -eu
tool_directory=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
tree_sitter=${TREE_SITTER:-"$tool_directory/node_modules/.bin/tree-sitter"}
cache_directory=${TMPDIR:-/tmp}/tree-sitter-lscript-cache
cd "$tool_directory"
XDG_CACHE_HOME="$cache_directory" "$tree_sitter" parse \
-p . --quiet --stat "$tool_directory"/test/fixtures/valid/*.ls
unexpected_success=0
for fixture in "$tool_directory"/test/fixtures/invalid/*.ls; do
if XDG_CACHE_HOME="$cache_directory" "$tree_sitter" parse \
-p . --quiet "$fixture" >/dev/null 2>&1; then
case "$(basename -- "$fixture")" in
adjacent_equal_minus.ls|bang_inequality.ls|repeated_result.ls)
;;
*)
echo "invalid fixture parsed without recovery errors: $fixture" >&2
unexpected_success=1
;;
esac
fi
done
if [ "$unexpected_success" -ne 0 ]; then
exit 1
fi
echo "Known recovery exceptions: adjacent_equal_minus, bang_inequality, repeated_result"
+5632
View File
File diff suppressed because it is too large Load Diff
+2798
View File
File diff suppressed because it is too large Load Diff
+45982
View File
File diff suppressed because it is too large Load Diff
+129
View File
@@ -0,0 +1,129 @@
#include "tree_sitter/parser.h"
#include <stdbool.h>
#include <stdint.h>
enum TokenType {
AND_CONTINUATION,
OR_CONTINUATION,
TRAILING_DOT_FLOAT,
};
static bool is_identifier_character(int32_t character) {
return (character >= 'a' && character <= 'z') ||
(character >= 'A' && character <= 'Z') ||
(character >= '0' && character <= '9') || character == '_';
}
static bool scan_word_continuation(TSLexer *lexer, enum TokenType symbol,
const char *word) {
for (const char *character = word; *character != '\0'; character++) {
if (lexer->lookahead != *character) {
return false;
}
lexer->advance(lexer, false);
}
if (is_identifier_character(lexer->lookahead)) {
return false;
}
lexer->mark_end(lexer);
lexer->result_symbol = symbol;
return true;
}
void *tree_sitter_lscript_external_scanner_create(void) { return NULL; }
void tree_sitter_lscript_external_scanner_destroy(void *payload) {
(void)payload;
}
unsigned tree_sitter_lscript_external_scanner_serialize(void *payload,
char *buffer) {
(void)payload;
(void)buffer;
return 0;
}
void tree_sitter_lscript_external_scanner_deserialize(void *payload,
const char *buffer,
unsigned length) {
(void)payload;
(void)buffer;
(void)length;
}
bool tree_sitter_lscript_external_scanner_scan(void *payload, TSLexer *lexer,
const bool *valid_symbols) {
(void)payload;
while (lexer->lookahead == ' ' || lexer->lookahead == '\t') {
lexer->advance(lexer, true);
}
if (lexer->lookahead == '\n' || lexer->lookahead == '\r') {
if (lexer->lookahead == '\r') {
lexer->advance(lexer, false);
if (lexer->lookahead != '\n') {
return false;
}
}
lexer->advance(lexer, false);
while (lexer->lookahead == ' ' || lexer->lookahead == '\t') {
lexer->advance(lexer, false);
}
if (valid_symbols[AND_CONTINUATION] && lexer->lookahead == 'a') {
return scan_word_continuation(lexer, AND_CONTINUATION, "and");
}
if (valid_symbols[OR_CONTINUATION] && lexer->lookahead == 'o') {
return scan_word_continuation(lexer, OR_CONTINUATION, "or");
}
return false;
}
if (!valid_symbols[TRAILING_DOT_FLOAT]) {
return false;
}
if (lexer->lookahead < '0' || lexer->lookahead > '9') {
return false;
}
bool previous_was_digit = false;
while ((lexer->lookahead >= '0' && lexer->lookahead <= '9') ||
lexer->lookahead == '_') {
if (lexer->lookahead == '_' && !previous_was_digit) {
return false;
}
previous_was_digit = lexer->lookahead != '_';
lexer->advance(lexer, false);
}
if (!previous_was_digit) {
return false;
}
if (lexer->lookahead != '.') {
return false;
}
lexer->advance(lexer, false);
if (lexer->lookahead == '.' ||
(lexer->lookahead >= '0' && lexer->lookahead <= '9')) {
return false;
}
lexer->mark_end(lexer);
if (lexer->lookahead == 'e' || lexer->lookahead == 'E') {
lexer->advance(lexer, false);
if (lexer->lookahead == '+' || lexer->lookahead == '-') {
lexer->advance(lexer, false);
}
if (lexer->lookahead < '0' || lexer->lookahead > '9') {
return false;
}
while ((lexer->lookahead >= '0' && lexer->lookahead <= '9') ||
lexer->lookahead == '_') {
lexer->advance(lexer, false);
}
lexer->mark_end(lexer);
}
lexer->result_symbol = TRAILING_DOT_FLOAT;
return true;
}
+54
View File
@@ -0,0 +1,54 @@
#ifndef TREE_SITTER_ALLOC_H_
#define TREE_SITTER_ALLOC_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
// Allow clients to override allocation functions
#ifdef TREE_SITTER_REUSE_ALLOCATOR
extern void *(*ts_current_malloc)(size_t size);
extern void *(*ts_current_calloc)(size_t count, size_t size);
extern void *(*ts_current_realloc)(void *ptr, size_t size);
extern void (*ts_current_free)(void *ptr);
#ifndef ts_malloc
#define ts_malloc ts_current_malloc
#endif
#ifndef ts_calloc
#define ts_calloc ts_current_calloc
#endif
#ifndef ts_realloc
#define ts_realloc ts_current_realloc
#endif
#ifndef ts_free
#define ts_free ts_current_free
#endif
#else
#ifndef ts_malloc
#define ts_malloc malloc
#endif
#ifndef ts_calloc
#define ts_calloc calloc
#endif
#ifndef ts_realloc
#define ts_realloc realloc
#endif
#ifndef ts_free
#define ts_free free
#endif
#endif
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_ALLOC_H_
+336
View File
@@ -0,0 +1,336 @@
#ifndef TREE_SITTER_ARRAY_H_
#define TREE_SITTER_ARRAY_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "./alloc.h"
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4101)
#elif defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
#endif
#define Array(T) \
struct { \
T *contents; \
uint32_t size; \
uint32_t capacity; \
}
/// Initialize an array.
#define array_init(self) \
((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL)
/// Create an empty array.
#define array_new() \
{ NULL, 0, 0 }
/// Get a pointer to the element at a given `index` in the array.
#define array_get(self, _index) \
(assert((uint32_t)(_index) < (self)->size), &(self)->contents[_index])
/// Get a pointer to the first element in the array.
#define array_front(self) array_get(self, 0)
/// Get a pointer to the last element in the array.
#define array_back(self) array_get(self, (self)->size - 1)
/// Clear the array, setting its size to zero. Note that this does not free any
/// memory allocated for the array's contents.
#define array_clear(self) ((self)->size = 0)
#ifdef __cplusplus
#define _array__cast(self, expr) (decltype((self)->contents))(expr)
#else
#define _array__cast(self, expr) (expr)
#endif
/// Reserve `new_capacity` elements of space in the array. If `new_capacity` is
/// less than the array's current capacity, this function has no effect.
#define array_reserve(self, new_capacity) \
((self)->contents = _array__cast(self, _array__reserve( \
(void *)(self)->contents, &(self)->capacity, \
array_elem_size(self), new_capacity)) \
)
/// Free any memory allocated for this array. Note that this does not free any
/// memory allocated for the array's contents.
#define array_delete(self) \
do { \
if ((self)->contents) ts_free((self)->contents); \
(self)->contents = NULL; \
(self)->size = 0; \
(self)->capacity = 0; \
} while (0)
/// Push a new `element` onto the end of the array.
#define array_push(self, element) \
do { \
(self)->contents = _array__cast(self, _array__grow( \
(void *)(self)->contents, (self)->size, &(self)->capacity, \
1, array_elem_size(self) \
)); \
(self)->contents[(self)->size++] = (element); \
} while(0)
/// Increase the array's size by `count` elements.
/// New elements are zero-initialized.
#define array_grow_by(self, count) \
do { \
if ((count) == 0) break; \
(self)->contents = _array__cast(self, _array__grow( \
(self)->contents, (self)->size, &(self)->capacity, \
count, array_elem_size(self) \
)); \
memset((self)->contents + (self)->size, 0, (count) * array_elem_size(self)); \
(self)->size += (count); \
} while (0)
/// Append all elements from one array to the end of another.
#define array_push_all(self, other) \
array_extend((self), (other)->size, (other)->contents)
/// Append `count` elements to the end of the array, reading their values from the
/// `contents` pointer.
#define array_extend(self, count, other_contents) \
((self)->contents = _array__cast(self, _array__splice( \
(void*)(self)->contents, &(self)->size, &(self)->capacity, \
array_elem_size(self), (self)->size, 0, count, other_contents \
)))
/// Remove `old_count` elements from the array starting at the given `index`. At
/// the same index, insert `new_count` new elements, reading their values from the
/// `new_contents` pointer.
#define array_splice(self, _index, old_count, new_count, new_contents) \
((self)->contents = _array__cast(self, _array__splice( \
(void *)(self)->contents, &(self)->size, &(self)->capacity, \
array_elem_size(self), _index, old_count, new_count, new_contents \
)))
/// Insert one `element` into the array at the given `index`.
#define array_insert(self, _index, element) \
((self)->contents = _array__cast(self, _array__splice( \
(void *)(self)->contents, &(self)->size, &(self)->capacity, \
array_elem_size(self), _index, 0, 1, &(element) \
)))
/// Remove one element from the array at the given `index`.
#define array_erase(self, _index) \
_array__erase((void *)(self)->contents, &(self)->size, array_elem_size(self), _index)
/// Pop the last element off the array, returning the element by value.
#define array_pop(self) ((self)->contents[--(self)->size])
/// Assign the contents of one array to another, reallocating if necessary.
#define array_assign(self, other) \
((self)->contents = _array__cast(self, _array__assign( \
(void *)(self)->contents, &(self)->size, &(self)->capacity, \
(const void *)(other)->contents, (other)->size, array_elem_size(self) \
)))
/// Swap one array with another
#define array_swap(self, other) \
do { \
void *_array_swap_tmp = (void *)(self)->contents; \
(self)->contents = (other)->contents; \
(other)->contents = _array__cast(other, _array_swap_tmp); \
_array__swap(&(self)->size, &(self)->capacity, \
&(other)->size, &(other)->capacity); \
} while (0)
/// Get the size of the array contents
#define array_elem_size(self) (sizeof *(self)->contents)
/// Search a sorted array for a given `needle` value, using the given `compare`
/// callback to determine the order.
///
/// If an existing element is found to be equal to `needle`, then the `index`
/// out-parameter is set to the existing value's index, and the `exists`
/// out-parameter is set to true. Otherwise, `index` is set to an index where
/// `needle` should be inserted in order to preserve the sorting, and `exists`
/// is set to false.
#define array_search_sorted_with(self, compare, needle, _index, _exists) \
_array__search_sorted(self, 0, compare, , needle, _index, _exists)
/// Search a sorted array for a given `needle` value, using integer comparisons
/// of a given struct field (specified with a leading dot) to determine the order.
///
/// See also `array_search_sorted_with`.
#define array_search_sorted_by(self, field, needle, _index, _exists) \
_array__search_sorted(self, 0, _compare_int, field, needle, _index, _exists)
/// Insert a given `value` into a sorted array, using the given `compare`
/// callback to determine the order.
#define array_insert_sorted_with(self, compare, value) \
do { \
unsigned _index, _exists; \
array_search_sorted_with(self, compare, &(value), &_index, &_exists); \
if (!_exists) array_insert(self, _index, value); \
} while (0)
/// Insert a given `value` into a sorted array, using integer comparisons of
/// a given struct field (specified with a leading dot) to determine the order.
///
/// See also `array_search_sorted_by`.
#define array_insert_sorted_by(self, field, value) \
do { \
unsigned _index, _exists; \
array_search_sorted_by(self, field, (value) field, &_index, &_exists); \
if (!_exists) array_insert(self, _index, value); \
} while (0)
// Private
// Pointers to individual `Array` fields (rather than the entire `Array` itself)
// are passed to the various `_array__*` functions below to address strict aliasing
// violations that arises when the _entire_ `Array` struct is passed as `Array(void)*`.
//
// The `Array` type itself was not altered as a solution in order to avoid breakage
// with existing consumers (in particular, parsers with external scanners).
/// This is not what you're looking for, see `array_erase`.
static inline void _array__erase(void* self_contents, uint32_t *size,
size_t element_size, uint32_t index) {
assert(index < *size);
char *contents = (char *)self_contents;
memmove(contents + index * element_size, contents + (index + 1) * element_size,
(*size - index - 1) * element_size);
(*size)--;
}
/// This is not what you're looking for, see `array_reserve`.
static inline void *_array__reserve(void *contents, uint32_t *capacity,
size_t element_size, uint32_t new_capacity) {
void *new_contents = contents;
if (new_capacity > *capacity) {
if (contents) {
new_contents = ts_realloc(contents, new_capacity * element_size);
} else {
new_contents = ts_malloc(new_capacity * element_size);
}
*capacity = new_capacity;
}
return new_contents;
}
/// This is not what you're looking for, see `array_assign`.
static inline void *_array__assign(void* self_contents, uint32_t *self_size, uint32_t *self_capacity,
const void *other_contents, uint32_t other_size, size_t element_size) {
void *new_contents = _array__reserve(self_contents, self_capacity, element_size, other_size);
*self_size = other_size;
memcpy(new_contents, other_contents, *self_size * element_size);
return new_contents;
}
/// This is not what you're looking for, see `array_swap`.
static inline void _array__swap(uint32_t *self_size, uint32_t *self_capacity,
uint32_t *other_size, uint32_t *other_capacity) {
uint32_t tmp_size = *self_size;
uint32_t tmp_capacity = *self_capacity;
*self_size = *other_size;
*self_capacity = *other_capacity;
*other_size = tmp_size;
*other_capacity = tmp_capacity;
}
/// This is not what you're looking for, see `array_push` or `array_grow_by`.
static inline void *_array__grow(void *contents, uint32_t size, uint32_t *capacity,
uint32_t count, size_t element_size) {
void *new_contents = contents;
uint32_t new_size = size + count;
if (new_size > *capacity) {
uint32_t new_capacity = *capacity * 2;
if (new_capacity < 8) new_capacity = 8;
if (new_capacity < new_size) new_capacity = new_size;
new_contents = _array__reserve(contents, capacity, element_size, new_capacity);
}
return new_contents;
}
/// This is not what you're looking for, see `array_splice`.
static inline void *_array__splice(void *self_contents, uint32_t *size, uint32_t *capacity,
size_t element_size,
uint32_t index, uint32_t old_count,
uint32_t new_count, const void *elements) {
uint32_t new_size = *size + new_count - old_count;
uint32_t old_end = index + old_count;
uint32_t new_end = index + new_count;
assert(old_end <= *size);
void *new_contents = _array__reserve(self_contents, capacity, element_size, new_size);
char *contents = (char *)new_contents;
if (*size > old_end) {
memmove(
contents + new_end * element_size,
contents + old_end * element_size,
(*size - old_end) * element_size
);
}
if (new_count > 0) {
if (elements) {
memcpy(
(contents + index * element_size),
elements,
new_count * element_size
);
} else {
memset(
(contents + index * element_size),
0,
new_count * element_size
);
}
}
*size += new_count - old_count;
return new_contents;
}
/// A binary search routine, based on Rust's `std::slice::binary_search_by`.
/// This is not what you're looking for, see `array_search_sorted_with` or `array_search_sorted_by`.
#define _array__search_sorted(self, start, compare, suffix, needle, _index, _exists) \
do { \
*(_index) = start; \
*(_exists) = false; \
uint32_t size = (self)->size - *(_index); \
if (size == 0) break; \
int comparison; \
while (size > 1) { \
uint32_t half_size = size / 2; \
uint32_t mid_index = *(_index) + half_size; \
comparison = compare(&((self)->contents[mid_index] suffix), (needle)); \
if (comparison <= 0) *(_index) = mid_index; \
size -= half_size; \
} \
comparison = compare(&((self)->contents[*(_index)] suffix), (needle)); \
if (comparison == 0) *(_exists) = true; \
else if (comparison < 0) *(_index) += 1; \
} while (0)
/// Helper macro for the `_sorted_by` routines below. This takes the left (existing)
/// parameter by reference in order to work with the generic sorting function above.
#define _compare_int(a, b) ((int)*(a) - (int)(b))
#ifdef _MSC_VER
#pragma warning(pop)
#elif defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic pop
#endif
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_ARRAY_H_
+286
View File
@@ -0,0 +1,286 @@
#ifndef TREE_SITTER_PARSER_H_
#define TREE_SITTER_PARSER_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#define ts_builtin_sym_error ((TSSymbol)-1)
#define ts_builtin_sym_end 0
#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024
#ifndef TREE_SITTER_API_H_
typedef uint16_t TSStateId;
typedef uint16_t TSSymbol;
typedef uint16_t TSFieldId;
typedef struct TSLanguage TSLanguage;
typedef struct TSLanguageMetadata {
uint8_t major_version;
uint8_t minor_version;
uint8_t patch_version;
} TSLanguageMetadata;
#endif
typedef struct {
TSFieldId field_id;
uint8_t child_index;
bool inherited;
} TSFieldMapEntry;
// Used to index the field and supertype maps.
typedef struct {
uint16_t index;
uint16_t length;
} TSMapSlice;
typedef struct {
bool visible;
bool named;
bool supertype;
} TSSymbolMetadata;
typedef struct TSLexer TSLexer;
struct TSLexer {
int32_t lookahead;
TSSymbol result_symbol;
void (*advance)(TSLexer *, bool);
void (*mark_end)(TSLexer *);
uint32_t (*get_column)(TSLexer *);
bool (*is_at_included_range_start)(const TSLexer *);
bool (*eof)(const TSLexer *);
void (*log)(const TSLexer *, const char *, ...);
};
typedef enum {
TSParseActionTypeShift,
TSParseActionTypeReduce,
TSParseActionTypeAccept,
TSParseActionTypeRecover,
} TSParseActionType;
typedef union {
struct {
uint8_t type;
TSStateId state;
bool extra;
bool repetition;
} shift;
struct {
uint8_t type;
uint8_t child_count;
TSSymbol symbol;
int16_t dynamic_precedence;
uint16_t production_id;
} reduce;
uint8_t type;
} TSParseAction;
typedef struct {
uint16_t lex_state;
uint16_t external_lex_state;
} TSLexMode;
typedef struct {
uint16_t lex_state;
uint16_t external_lex_state;
uint16_t reserved_word_set_id;
} TSLexerMode;
typedef union {
TSParseAction action;
struct {
uint8_t count;
bool reusable;
} entry;
} TSParseActionEntry;
typedef struct {
int32_t start;
int32_t end;
} TSCharacterRange;
struct TSLanguage {
uint32_t abi_version;
uint32_t symbol_count;
uint32_t alias_count;
uint32_t token_count;
uint32_t external_token_count;
uint32_t state_count;
uint32_t large_state_count;
uint32_t production_id_count;
uint32_t field_count;
uint16_t max_alias_sequence_length;
const uint16_t *parse_table;
const uint16_t *small_parse_table;
const uint32_t *small_parse_table_map;
const TSParseActionEntry *parse_actions;
const char * const *symbol_names;
const char * const *field_names;
const TSMapSlice *field_map_slices;
const TSFieldMapEntry *field_map_entries;
const TSSymbolMetadata *symbol_metadata;
const TSSymbol *public_symbol_map;
const uint16_t *alias_map;
const TSSymbol *alias_sequences;
const TSLexerMode *lex_modes;
bool (*lex_fn)(TSLexer *, TSStateId);
bool (*keyword_lex_fn)(TSLexer *, TSStateId);
TSSymbol keyword_capture_token;
struct {
const bool *states;
const TSSymbol *symbol_map;
void *(*create)(void);
void (*destroy)(void *);
bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist);
unsigned (*serialize)(void *, char *);
void (*deserialize)(void *, const char *, unsigned);
} external_scanner;
const TSStateId *primary_state_ids;
const char *name;
const TSSymbol *reserved_words;
uint16_t max_reserved_word_set_size;
uint32_t supertype_count;
const TSSymbol *supertype_symbols;
const TSMapSlice *supertype_map_slices;
const TSSymbol *supertype_map_entries;
TSLanguageMetadata metadata;
};
static inline bool set_contains(const TSCharacterRange *ranges, uint32_t len, int32_t lookahead) {
uint32_t index = 0;
uint32_t size = len - index;
while (size > 1) {
uint32_t half_size = size / 2;
uint32_t mid_index = index + half_size;
const TSCharacterRange *range = &ranges[mid_index];
if (lookahead >= range->start && lookahead <= range->end) {
return true;
} else if (lookahead > range->end) {
index = mid_index;
}
size -= half_size;
}
const TSCharacterRange *range = &ranges[index];
return (lookahead >= range->start && lookahead <= range->end);
}
/*
* Lexer Macros
*/
#ifdef _MSC_VER
#define UNUSED __pragma(warning(suppress : 4101))
#else
#define UNUSED __attribute__((unused))
#endif
#define START_LEXER() \
bool result = false; \
bool skip = false; \
UNUSED \
bool eof = false; \
int32_t lookahead; \
goto start; \
next_state: \
lexer->advance(lexer, skip); \
start: \
skip = false; \
lookahead = lexer->lookahead;
#define ADVANCE(state_value) \
{ \
state = state_value; \
goto next_state; \
}
#define ADVANCE_MAP(...) \
{ \
static const uint16_t map[] = { __VA_ARGS__ }; \
for (uint32_t i = 0; i < sizeof(map) / sizeof(map[0]); i += 2) { \
if (map[i] == lookahead) { \
state = map[i + 1]; \
goto next_state; \
} \
} \
}
#define SKIP(state_value) \
{ \
skip = true; \
state = state_value; \
goto next_state; \
}
#define ACCEPT_TOKEN(symbol_value) \
result = true; \
lexer->result_symbol = symbol_value; \
lexer->mark_end(lexer);
#define END_STATE() return result;
/*
* Parse Table Macros
*/
#define SMALL_STATE(id) ((id) - LARGE_STATE_COUNT)
#define STATE(id) id
#define ACTIONS(id) id
#define SHIFT(state_value) \
{{ \
.shift = { \
.type = TSParseActionTypeShift, \
.state = (state_value) \
} \
}}
#define SHIFT_REPEAT(state_value) \
{{ \
.shift = { \
.type = TSParseActionTypeShift, \
.state = (state_value), \
.repetition = true \
} \
}}
#define SHIFT_EXTRA() \
{{ \
.shift = { \
.type = TSParseActionTypeShift, \
.extra = true \
} \
}}
#define REDUCE(symbol_name, children, precedence, prod_id) \
{{ \
.reduce = { \
.type = TSParseActionTypeReduce, \
.symbol = symbol_name, \
.child_count = children, \
.dynamic_precedence = precedence, \
.production_id = prod_id \
}, \
}}
#define RECOVER() \
{{ \
.type = TSParseActionTypeRecover \
}}
#define ACCEPT_INPUT() \
{{ \
.type = TSParseActionTypeAccept \
}}
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_PARSER_H_
+235
View File
@@ -0,0 +1,235 @@
==================
Declarations
==================
immutable :: 1
mutable := 2
typed: i32: 3
deferred: i32
---
(source_file
(declaration
(identifier)
(signed_numeric_literal
(integer_literal)))
(declaration
(identifier)
(signed_numeric_literal
(integer_literal)))
(declaration
(identifier)
(primitive_type)
(signed_numeric_literal
(integer_literal)))
(declaration
(identifier)
(primitive_type)))
==================
Types and constructors
==================
Point :: &{
x: i32:
y: i32 = 0
}
point := &Point.{ x: 1; y = 2 }
maybe: []{i32?}: [nil, 1->|.some]
---
(source_file
(declaration
(identifier)
(type_definition
(product_type
(product_field
(identifier)
(primitive_type))
(product_field
(identifier)
(primitive_type)
(signed_numeric_literal
(integer_literal))))))
(declaration
(identifier)
(product_constructor
(identifier)
(product_entry
(identifier)
(signed_numeric_literal
(integer_literal)))
(product_entry
(identifier)
(signed_numeric_literal
(integer_literal)))))
(declaration
(identifier)
(array_type
(grouped_type
(optional_type
(primitive_type))))
(collection_literal
(collection_entry
(nil_literal))
(collection_entry
(pipeline_expression
(signed_numeric_literal
(integer_literal))
(direct_pipeline_stage
(pipeline_callable
(sum_constructor
(identifier)))))))))
==================
Control flow and patterns
==================
describe: []i32 -> str: @ values: []i32 { match values {
[] => 'empty'
[first, ...rest] if first > 0 => 'positive'
_ => 'other'
} }
---
(source_file
(declaration
(identifier)
(function_type
(array_type
(primitive_type))
(primitive_type))
(function_literal
(typed_parameter
(identifier)
(array_type
(primitive_type)))
(block
(match_expression
(identifier)
(match_arm
(pattern
(array_pattern))
(string_literal))
(match_arm
(pattern
(array_pattern
(array_pattern_entry
(pattern
(identifier)))
(array_pattern_entry
(identifier))))
(binary_expression
(identifier)
(signed_numeric_literal
(integer_literal)))
(string_literal))
(match_arm
(pattern)
(string_literal)))))))
==================
Physical-line continuations
==================
value := 'text' # the newline remains significant
->reverse_chrs
ready :: first
and second
---
(source_file
(declaration
(identifier)
(pipeline_expression
(string_literal)
(comment)
(continuation_pipeline_stage
(pipeline_callable
(identifier)))))
(declaration
(identifier)
(binary_expression
(identifier)
(identifier))))
==================
Compact range after an integer
==================
slice :: (source,0..<limit)
trailing_dot :: 1.
---
(source_file
(declaration
(identifier)
(tuple_value
(identifier)
(range_expression
(signed_numeric_literal
(integer_literal))
(identifier))))
(declaration
(identifier)
(signed_numeric_literal
(float_literal))))
==================
Typed function declarations
==================
double :: @ value: i32 { value * 2 }
square :: @ value: i32 => i32 { value * value }
add :: @ (left: i32, right: i32) { left + right }
unit :: @ { () }
---
(source_file
(declaration
(identifier)
(function_literal
(typed_parameter
(identifier)
(primitive_type))
(block
(binary_expression
(identifier)
(signed_numeric_literal
(integer_literal))))))
(declaration
(identifier)
(function_literal
(typed_parameter
(identifier)
(primitive_type))
(primitive_type)
(block
(binary_expression
(identifier)
(identifier)))))
(declaration
(identifier)
(function_literal
(typed_tuple_pattern
(typed_tuple_component
(identifier)
(primitive_type))
(typed_tuple_component
(identifier)
(primitive_type)))
(block
(binary_expression
(identifier)
(identifier)))))
(declaration
(identifier)
(function_literal
(block
(unit_literal)))))
+2
View File
@@ -0,0 +1,2 @@
value := 0
value=-1
+1
View File
@@ -0,0 +1 @@
identity :: @ value { value }
+1
View File
@@ -0,0 +1 @@
text :: 'bad\qescape'
+1
View File
@@ -0,0 +1 @@
different :: left != right
+1
View File
@@ -0,0 +1 @@
->initialize
+1
View File
@@ -0,0 +1 @@
Point :: &{ x: i32:, y: i32: }
+1
View File
@@ -0,0 +1 @@
value: i32:
+1
View File
@@ -0,0 +1 @@
outer := inner := 1
+1
View File
@@ -0,0 +1 @@
text :: "double quoted"
+1
View File
@@ -0,0 +1 @@
values := [1,, 2]
+1
View File
@@ -0,0 +1 @@
(left, right): (i32, i32): (1, 2)
+2
View File
@@ -0,0 +1,2 @@
identity :: @ value: i32
{ value }
+1
View File
@@ -0,0 +1 @@
in := 1
+1
View File
@@ -0,0 +1 @@
answer: () -> i32: @ 42
+2
View File
@@ -0,0 +1,2 @@
identity: i32 -> i32:
@ value: i32 { value }
+1
View File
@@ -0,0 +1 @@
identity: i32 -> i32: value@ value
+9
View File
@@ -0,0 +1,9 @@
Choice :: |{
one
}
describe :: @ choice: Choice -> str { match choice {
|.one =>
'one'
}
}
+1
View File
@@ -0,0 +1 @@
{start as {line}} :: value
+1
View File
@@ -0,0 +1 @@
value :: 1__000
+1
View File
@@ -0,0 +1 @@
value :: 1.0f
+1
View File
@@ -0,0 +1 @@
StringIterator :: Iter(str)
+2
View File
@@ -0,0 +1,2 @@
Error :: |{ failed }
Bad :: i32? ! Error
+2
View File
@@ -0,0 +1,2 @@
Error :: |{ failed }
Bad :: i32 ! Error?
+1
View File
@@ -0,0 +1 @@
value :: (1 + 2)
+3
View File
@@ -0,0 +1,3 @@
value := 1
->to_str
+3
View File
@@ -0,0 +1,3 @@
value := 1
# this line terminates the preceding expression
->to_str
+1
View File
@@ -0,0 +1 @@
value :: 1 + 2->to_str
+3
View File
@@ -0,0 +1,3 @@
FirstError :: |{ failed }
SecondError :: |{ failed }
Bad :: i32 ! FirstError ! SecondError
+4
View File
@@ -0,0 +1,4 @@
value := {
first;
second
}
+2
View File
@@ -0,0 +1,2 @@
value := 1
value >>= 1
+1
View File
@@ -0,0 +1 @@
value :: 1 << 2
+1
View File
@@ -0,0 +1 @@
identity :: @ (value: i32) { value }
@@ -0,0 +1 @@
identity :: @ (value: i32,) -> i32 { value }
+1
View File
@@ -0,0 +1 @@
value :: (1,)
+1
View File
@@ -0,0 +1 @@
value :: if true then 1 else 2
+2
View File
@@ -0,0 +1,2 @@
value := 1->
to_str
+1
View File
@@ -0,0 +1 @@
value: i32 =
+1
View File
@@ -0,0 +1 @@
value := { 1; }
@@ -0,0 +1 @@
iterator: Iter<i32>=
+1
View File
@@ -0,0 +1 @@
Bad :: (i32,)
+1
View File
@@ -0,0 +1 @@
value :=
+1
View File
@@ -0,0 +1 @@
use module.*
+19
View File
@@ -0,0 +1,19 @@
numbers := [1, 2, 3]
more_numbers := [0, ...numbers, 4]
codes := ['ok' => 200, 'missing' => 404]
more_codes := [...codes, 'failed' => 500]
empty_numbers: []i32: []
empty_codes: [str]i32: []
optional_numbers: []i32?: nil
optional_elements: []{i32?}: [nil, 1->|.some]
Options :: &{
name: str: 'default'
count: i32 = 0
}
defaults := &Options.{}
overridden := &Options.{ ...defaults; count = 2 }
anonymous := &.{ label: 'value'; count = 1 }
+28
View File
@@ -0,0 +1,28 @@
choose: (bit, i32, i32) -> i32: @ (condition: bit, when_true: i32, when_false: i32) { if condition { when_true } else { when_false } }
choose_block: (bit, i32, i32) -> i32: @ (condition: bit, when_true: i32, when_false: i32) { if condition {
when_true
} else if not condition {
when_false
} else {
0
} }
first_positive: []i32 -> i32?: @ values: []i32 {
for value in values {
if value > 0 {
return value->|.some
}
}
nil
}
labelled_search: []i32 -> i32: @ values: []i32 { loop `outer {
for value in values `items {
if value > 0 {
break `outer value
}
continue `items
}
break 0
} }
+18
View File
@@ -0,0 +1,18 @@
immutable :: 1
mutable := 2
typed_immutable: i32: 3
typed_mutable: i32 = 4
deferred: i32
deferred = 5
then :: 6
(left, right) :: (1, 2)
(mutable_left, mutable_right) := (3, 4)
Point :: &{
x: i32:
y: i32 =
}
point := &Point.{ x: 10; y = 20 }
{x as horizontal, y} :: point
+18
View File
@@ -0,0 +1,18 @@
double: i32 -> i32: @ value: i32 { value * 2 }
square: i32 => i32: @ value: i32 { value * value }
add: (i32, i32) -> i32: @ (left: i32, right: i32) { left + right }
normalize: i32 -> i32: @ value: i32 {
adjusted := value + 1
adjusted->double
}
apply: (i32, i32 -> i32) -> i32: @ (value, operation): {(i32, i32 -> i32)} { value->operation }
factory: i32 => i32 -> i32: @ offset: i32 { @ value: i32 { value + offset } }
initialize: () -> i32: @ () { 1 }
computed := (4, @ value: i32 { value + 1 })->apply
+18
View File
@@ -0,0 +1,18 @@
inline_array := [1, 2, 3]
newline_array := [
1
2
3
]
comma_array := [
1,
2,
3,
]
inline_block := { first := 1; second := 2; first + second }
InlinePoint :: &{ x: i32:; y: i32: }
inline_point := &InlinePoint.{ x: 1; y: 2 }
+17
View File
@@ -0,0 +1,17 @@
decimal :: 1_000_000
binary :: 0b1010_0101
octal :: 0o755
hexadecimal :: 0xCAFE_beef
leading_zeroes :: 007
fraction_without_integer :: .5
fraction_without_digits :: 1.
scientific :: 1.25e-10
scientific_integer :: 1E+4
negative_minimum: i8: -128
spaced_negative: i8: - 127
text :: 'can\'t\nstop'
letter :: c'é'
escaped_letter :: c'\u{1f642}'
nul :: c'\0'
+14
View File
@@ -0,0 +1,14 @@
use math
use math as arithmetic
use math.{pi, sqrt as square_root}
ExportedType :: distinct i32
exported :: 1
circle: f64: pi
root: f64: 4.0->square_root
qualified_root: f64: 9.0->arithmetic.sqrt
pub {
ExportedType
exported
}
+27
View File
@@ -0,0 +1,27 @@
ready: bit: true
connected: bit: true
busy: bit: false
can_run :: ready
and connected
and not busy
one: i32: 1
two: i32: 2
three: i32: 3
sum :: one
+ two
* three
eight: i32: 8
five: i32: 5
difference :: eight -
five
first_mask: u8: 1
second_mask: u8: 2
mask :: first_mask |
second_mask
is_ordered :: sum
> difference
+26
View File
@@ -0,0 +1,26 @@
Location :: |{
unknown
known: (f32, f32)
}
describe: Location -> str: @ location: Location { match location {
|.unknown => 'unknown'
|.known (latitude, longitude) if latitude >= 0.0 => 'known'
|.known (_, _) => 'known'
} }
Point :: &{
x: i32:
y: i32:
}
sum_point: Point -> i32: @ point: Point {
{x as horizontal, y as vertical} :: point
horizontal + vertical
}
classify_array: []i32 -> str: @ values: []i32 { match values {
[] => 'empty'
[only] => 'one'
[first, ...rest] => 'many'
} }
+23
View File
@@ -0,0 +1,23 @@
Error :: distinct |{
failed
}
reversed := 'value'->reverse_chrs
sum_text := {1 + 2}->to_str
continued := 'value'
->reverse_chrs
->reverse_chrs
commented := 'value' # an inline comment preserves continuation
->reverse_chrs
computed := 1 + 2
->to_str
fallible: i32 -> i32 ! Error: @ value: i32 { value! }
propagate: i32 -> i32 ! Error: @ value: i32 {
result := {value->fallible}?
result!
}
+21
View File
@@ -0,0 +1,21 @@
double :: @ value: i32 { value * 2 }
add :: @ (a: i32, b: i32) { a + b }
square :: @ value: i32 => i32 { value * value }
constant :: @ () -> i32 { 42 }
unit :: @ { () }
apply :: @ (value: i32, callback: i32 -> i32) {
value->callback
}
sum :: @ (left: i32, right: i32) { left + right }
multiline :: @ (
first: i32
second: i32
) -> i32 { first + second }
explicit: i32 -> i32: @ value: i32 { value + 1 }
mutable := @ value: i32 { value + 1 }
run_callback :: @ callback: {() -> i32} { ()->callback }
callback_result := @ () -> i32 { 42 }->run_callback
+22
View File
@@ -0,0 +1,22 @@
Error :: distinct |{
failed
}
OptionalArray :: []u8?
OptionalElements :: []{u8?}
OptionalMap :: [str]i32?
OptionalValues :: [str]{i32?}
OptionalSuccessResult :: {i32?} ! Error
OptionalResult :: {i32 ! Error}?
OptionalErrorResult :: i32 ! {Error?}
Curried :: i32 -> str -> bit
PureTransform :: str => str
MixedTransform :: i32 => str -> bit
ReturningResult :: str -> i32 ! Error
IntegerRange :: Range<i32>
StringIterator :: Iter<str>
NestedIterator :: Iter<Iter<str>>
TupleIterator :: Iter<(i32, str)>
+27
View File
@@ -0,0 +1,27 @@
{
"grammars": [
{
"name": "lscript",
"camelcase": "LScript",
"scope": "source.lscript",
"path": ".",
"file-types": [
"ls"
],
"highlights": "queries/highlights.scm"
}
],
"metadata": {
"version": "0.1.0",
"license": "UNLICENSED",
"description": "LScript grammar for Tree-sitter"
},
"bindings": {
"c": true,
"go": false,
"node": false,
"python": false,
"rust": true,
"swift": false
}
}