Merge commit '6ece453acf8b14568c10f629f8cd25d3dde3794f'

pull/844/head
Eman Resu 2025-06-05 20:59:19 +07:00
commit 49c8f28c43
45 changed files with 71984 additions and 35514 deletions

@ -0,0 +1,12 @@
---
name: Issue
about: A problem or suggestion
title: ''
labels: ''
assignees: ''
---
> [!IMPORTANT]
> Please ensure you are using the latest commit in this repo.
> Older versions are not supported.

@ -5,19 +5,21 @@ on: [push, pull_request]
jobs:
test:
name: Test
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
matrix:
os:
- macos-latest
- ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Install Node
uses: actions/setup-node@v2
with:
node-version: "14.x"
uses: actions/setup-node@v4
- name: Cache npm dependencies
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
@ -42,15 +44,13 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Install Node
uses: actions/setup-node@v2
with:
node-version: "14.x"
uses: actions/setup-node@v4
- name: Cache npm dependencies
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}

@ -13,15 +13,13 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Install Node
uses: actions/setup-node@v2
with:
node-version: "14.x"
uses: actions/setup-node@v4
- name: Cache npm dependencies
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}

@ -15,12 +15,10 @@ jobs:
- ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Install Node
uses: actions/setup-node@v2
with:
node-version: "14.x"
uses: actions/setup-node@v4
- name: Install npm dependencies
run: npm ci

@ -1,5 +1,6 @@
node_modules
target
build
*.wasm
build
gleam.so*
log.html
node_modules
target

@ -4,30 +4,45 @@ version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "cc"
version = "1.0.72"
version = "1.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0"
dependencies = [
"shlex",
]
[[package]]
name = "memchr"
version = "2.4.1"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "regex"
version = "1.5.4"
version = "1.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
dependencies = [
"aho-corasick",
"memchr",
@ -36,24 +51,39 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.6.25"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "tree-sitter"
version = "0.20.2"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c36be3222512d85a112491ae0cc280a38076022414f00b64582da1b7565ffd82"
checksum = "20f4cd3642c47a85052a887d86704f4eac272969f61b686bdd3f772122aabaff"
dependencies = [
"cc",
"regex",
"regex-syntax",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-gleam"
version = "0.22.1"
version = "1.0.0"
dependencies = [
"cc",
"tree-sitter",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-language"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2545046bd1473dac6c626659cc2567c6c0ff302fc8b84a56c4243378276f7f57"

@ -1,7 +1,7 @@
[package]
name = "tree-sitter-gleam"
description = "gleam grammar for the tree-sitter parsing library"
version = "0.22.1"
version = "1.0.0"
keywords = ["incremental", "parsing", "gleam"]
categories = ["parsing", "text-editors"]
repository = "https://github.com/tree-sitter/tree-sitter-gleam"
@ -20,7 +20,10 @@ include = [
path = "bindings/rust/lib.rs"
[dependencies]
tree-sitter = "~0.20"
tree-sitter-language = "0.1.0"
[dev-dependencies]
tree-sitter = "0.23"
[build-dependencies]
cc = "1.0"

@ -37,6 +37,7 @@ Various Gotchas
---------------
There are a few nodes in the generated AST that may be confusing at first:
- `type` :: A very ambiguous name, but this refers to a concrete type such as
`List(#(String, Int))`
- `type_name` :: Refers to essentially the left side of a type declaration and
@ -52,6 +53,7 @@ There are a few nodes in the generated AST that may be confusing at first:
—there's no way for the parser to know. In this case, it will be parsed to
`(function_call function: (field_access ...) ...)` , as I arbitrarily decided
to always assume the code is accessing a field on a record.
- `constant_field_access` :: Recognizes when a reference to a remote function is used as a constant's value. Generally field accesses are indistinguishable from remote function invocations by the parser so `field_access` is the node name used for both (hence this misnomer).
This is not a comprehensive list. If you find a node confusing, search for it
in `grammar.js`, as it might have an explanatory comment. Either way, feel free
@ -66,13 +68,36 @@ To-do List
[open an issue]: https://github.com/J3RN/tree-sitter-gleam/issues/new
Contributing
------------
1. Change files such as `grammar.js` and `queries/highlight.scm`.
2. The grammar needs to be generated from the `grammar.js` file by running `npm run generate`.
3. Add parser feature tests to the relevant file(s) in `test/corpus/`, or make a new one.
4. Run `npm run test` and fix any failing tests.
Policies
--------
### Backwards-Compatibility Policy
Per the conversation in [#55](https://github.com/gleam-lang/tree-sitter-gleam/pull/55), we have decided that from v0.28.0 forward, tree-sitter-gleam will maintain backwards compatibility with the previous two minor versions; meaning that each release will support three versions:
- 0.x.0
- 0.x-1.*
- 0.x-2.*
e.g. The v0.30.0 release of tree-sitter gleam will support the following version of the Gleam language:
- v0.30.0
- v0.29.*
- v0.28.*
Style
-----
To prevent headaches from stylistic differences, I request that you please
follow these style suggestions. 🙏
- Remove all non-mandatory trailing whitespace
- Remove all non-mandatory trailing whitespace.
- Ensure a final newline is present at the end of all files (this is the default
in Vim, Emacs)
- Format JavaScript by running `npm run format`
in Vim, Emacs).
- Format JavaScript by running `npm run format`.

@ -9,7 +9,7 @@
"sources": [
"bindings/node/binding.cc",
"src/parser.c",
# If your language uses an external scanner, add it here.
"src/scanner.c"
],
"cflags_c": [
"-std=c99",

@ -10,14 +10,9 @@ fn main() {
let parser_path = src_dir.join("parser.c");
c_config.file(&parser_path);
// If your language uses an external scanner written in C,
// then include this block of code:
/*
let scanner_path = src_dir.join("scanner.c");
c_config.file(&scanner_path);
println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap());
*/
c_config.compile("parser");
println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap());

@ -1,13 +1,25 @@
//! This crate provides gleam language support for the [tree-sitter][] parsing library.
//! This crate provides Gleam language support for the [tree-sitter][] parsing library.
//!
//! Typically, you will use the [language][language func] function to add this language to a
//! tree-sitter [Parser][], and then use the parser to parse some code:
//!
//! ```
//! let code = "";
//! let mut parser = tree_sitter::Parser::new();
//! parser.set_language(tree_sitter_gleam::language()).expect("Error loading gleam grammar");
//! use tree_sitter::Parser;
//!
//! let code = r#"
//! import gleam/io
//!
//! pub fn main() {
//! io.println("hello, friend!")
//! }
//! "#;
//! let mut parser = Parser::new();
//! let language = tree_sitter_gleam::LANGUAGE;
//! parser
//! .set_language(&language.into())
//! .expect("Error loading Gleam parser");
//! let tree = parser.parse(code, None).unwrap();
//! assert!(!tree.root_node().has_error());
//! ```
//!
//! [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html
@ -15,30 +27,28 @@
//! [Parser]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Parser.html
//! [tree-sitter]: https://tree-sitter.github.io/
use tree_sitter::Language;
use tree_sitter_language::LanguageFn;
extern "C" {
fn tree_sitter_gleam() -> Language;
fn tree_sitter_gleam() -> *const ();
}
/// Get the tree-sitter [Language][] for this grammar.
///
/// [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html
pub fn language() -> Language {
unsafe { tree_sitter_gleam() }
}
/// The tree-sitter [`LanguageFn`] for this grammar.
pub const LANGUAGE: LanguageFn = unsafe { LanguageFn::from_raw(tree_sitter_gleam) };
/// The content of the [`node-types.json`][] file for this grammar.
///
/// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers#static-node-types
pub const NODE_TYPES: &'static str = include_str!("../../src/node-types.json");
// Uncomment these to include any queries that this grammar contains
/// The syntax highlighting query for this language.
pub const HIGHLIGHT_QUERY: &'static str = include_str!("../../queries/highlights.scm");
// The locals tagging query for this language.
pub const LOCALS_QUERY: &'static str = include_str!("../../queries/locals.scm");
pub const HIGHLIGHTS_QUERY: &'static str = include_str!("../../queries/highlights.scm");
// pub const INJECTIONS_QUERY: &'static str = include_str!("../../queries/injections.scm");
// pub const LOCALS_QUERY: &'static str = include_str!("../../queries/locals.scm");
// pub const TAGS_QUERY: &'static str = include_str!("../../queries/tags.scm");
/// The symbol tagging query for this language.
pub const TAGS_QUERY: &'static str = include_str!("../../queries/tags.scm");
#[cfg(test)]
mod tests {
@ -46,7 +56,7 @@ mod tests {
fn test_can_load_grammar() {
let mut parser = tree_sitter::Parser::new();
parser
.set_language(super::language())
.expect("Error loading gleam language");
.set_language(&super::LANGUAGE.into())
.expect("Error loading Gleam parser");
}
}

@ -4,7 +4,6 @@ module.exports = grammar({
name: "gleam",
externals: ($) => [$.quoted_content],
extras: ($) => [
";",
NEWLINE,
/\s/,
$.module_comment,
@ -13,11 +12,6 @@ module.exports = grammar({
],
conflicts: ($) => [
[$._maybe_record_expression, $._maybe_tuple_expression],
[
$._maybe_record_expression,
$._maybe_tuple_expression,
$.remote_type_identifier,
],
[
$._maybe_record_expression,
$._maybe_tuple_expression,
@ -25,13 +19,16 @@ module.exports = grammar({
],
[$.case_subjects],
[$.source_file],
[$._constant_value, $._case_clause_guard_unit],
[$.integer],
[$.echo],
],
rules: {
/* General rules */
source_file: ($) =>
repeat(choice($.target_group, $._statement, $._expression_seq)),
repeat(choice($.target_group, $._module_statement, $._statement_seq)),
_statement: ($) =>
_module_statement: ($) =>
choice(
$.import,
$.constant,
@ -39,7 +36,8 @@ module.exports = grammar({
$.external_function,
$.function,
$.type_definition,
$.type_alias
$.type_alias,
$.attribute
),
/* Comments */
@ -47,18 +45,43 @@ module.exports = grammar({
statement_comment: ($) => token(seq("///", /.*/)),
comment: ($) => token(seq("//", /.*/)),
/* Target groups */
/* Target groups
* DEPRECATED: This syntax was replaced with attributes in v0.30.
*/
target_group: ($) =>
seq("if", field("target", $.target), "{", repeat($._statement), "}"),
seq(
"if",
field("target", $.target),
"{",
repeat($._module_statement),
"}"
),
target: ($) => choice("erlang", "javascript"),
/* Attributes */
attribute: ($) =>
seq(
"@",
field("name", $.identifier),
optional(field("arguments", alias($._attribute_arguments, $.arguments)))
),
_attribute_arguments: ($) =>
seq("(", series_of($.attribute_value, ","), ")"),
attribute_value: ($) =>
choice(
$._constant_value,
seq(field("label", $.label), ":", field("value", $._constant_value))
),
/* Import statements */
import: ($) =>
seq(
"import",
field("module", $.module),
optional(seq(".", field("imports", $.unqualified_imports))),
optional(seq("as", field("alias", $.identifier)))
optional(seq("as", field("alias", choice($.identifier, $.discard))))
),
module: ($) => seq($._name, repeat(seq("/", $._name))),
unqualified_imports: ($) =>
@ -70,8 +93,13 @@ module.exports = grammar({
optional(seq("as", field("alias", $.identifier)))
),
seq(
"type",
field("name", $.type_identifier),
optional(seq("as", field("alias", $.type_identifier)))
),
seq(
field("name", $.constructor_name),
optional(seq("as", field("alias", $.constructor_name)))
)
),
@ -93,7 +121,9 @@ module.exports = grammar({
alias($.constant_tuple, $.tuple),
alias($.constant_list, $.list),
alias($._constant_bit_string, $.bit_string),
alias($.constant_record, $.record)
alias($.constant_record, $.record),
$.identifier,
alias($.constant_field_access, $.field_access)
),
constant_tuple: ($) =>
seq("#", "(", optional(series_of($._constant_value, ",")), ")"),
@ -114,10 +144,17 @@ module.exports = grammar({
")"
),
constant_record_argument: ($) =>
seq(
optional(seq(field("label", $.label), ":")),
field("value", $._constant_value)
choice(
seq(
optional(seq(field("label", $.label), ":")),
field("value", $._constant_value)
),
seq(field("label", $.label), ":")
),
// This rule exists to parse remote function references which are generally
// indistinguishable from field accesses and so share an AST node.
constant_field_access: ($) =>
seq(field("record", $.identifier), ".", field("field", $.label)),
/* Special constant types */
// Versions of $._type, $._type_annotation, etc, that have constraints
@ -126,11 +163,29 @@ module.exports = grammar({
choice(
$.type_hole,
alias($.constant_tuple_type, $.tuple_type),
alias($.constant_function_type, $.function_type),
alias($.constant_type, $.type)
),
_constant_type_annotation: ($) => seq(":", field("type", $._constant_type)),
constant_tuple_type: ($) =>
seq("#", "(", optional(series_of($._constant_type, ",")), ")"),
constant_function_type: ($) =>
seq(
"fn",
optional(
field(
"parameter_types",
alias(
$.constant_function_parameter_types,
$.function_parameter_types
)
)
),
"->",
field("return_type", $._constant_type)
),
constant_function_parameter_types: ($) =>
seq("(", optional(series_of($._constant_type, ",")), ")"),
constant_type: ($) =>
seq(
field("name", choice($.type_identifier, $.remote_type_identifier)),
@ -149,7 +204,15 @@ module.exports = grammar({
constant_type_argument: ($) => $._constant_type,
external_type: ($) =>
seq(optional($.visibility_modifier), "external", "type", $.type_name),
prec.right(
seq(
optional($.visibility_modifier),
// DEPRECATED: the external token was removed in v0.30.
optional("external"),
"type",
$.type_name
)
),
/* External function */
external_function: ($) =>
@ -189,15 +252,15 @@ module.exports = grammar({
/* Functions */
function: ($) =>
seq(
optional($.visibility_modifier),
"fn",
field("name", $.identifier),
field("parameters", $.function_parameters),
optional(seq("->", field("return_type", $._type))),
"{",
field("body", alias($._expression_seq, $.function_body)),
"}"
prec.right(
seq(
optional($.visibility_modifier),
"fn",
field("name", $.identifier),
field("parameters", $.function_parameters),
optional(seq("->", field("return_type", $._type))),
optional(field("body", $.block))
)
),
function_parameters: ($) =>
seq("(", optional(series_of($.function_parameter, ",")), ")"),
@ -217,36 +280,9 @@ module.exports = grammar({
_labeled_name_param: ($) =>
seq(field("label", $.label), field("name", $.identifier)),
_name_param: ($) => field("name", $.identifier),
// This method diverges from the parser's `parse_expression_seq` somewhat.
// The parser considers all expressions after a `try` to be part of its AST
// node, namely the "then" section. Gleam code like this:
//
// try int_a = parse(a)
// try int_b = parse(b)
// Ok(int_a + int_b)
//
// is parsed as:
//
// (try
// pattern: (pattern)
// value: (call (identifier))
// then: (try
// pattern: (pattern)
// value: (call (identifier))
// then: (record (...))))
//
// This makes sense for the parser, but (IMO) would be more confusing for
// users and tooling which don't think about `try`s as having a "then". Thus,
// `try`s are essentially treated the same as any other expression.
_expression_seq: ($) => repeat1(choice($._expression, $.try)),
try: ($) =>
seq(
"try",
field("pattern", $._pattern),
optional($._type_annotation),
"=",
field("value", $._expression)
),
_statement_seq: ($) => repeat1($._statement),
_statement: ($) =>
choice($._expression, $.let, $.let_assert, $.use, $.assert),
_expression: ($) => choice($._expression_unit, $.binary_expression),
binary_expression: ($) =>
choice(
@ -262,7 +298,13 @@ module.exports = grammar({
binaryExpr(prec.left, 4, ">=", $._expression),
binaryExpr(prec.left, 4, ">.", $._expression),
binaryExpr(prec.left, 4, ">=.", $._expression),
binaryExpr(prec.left, 5, "|>", $._expression),
binaryExpr(
prec.left,
5,
"|>",
$._expression,
choice($.pipeline_echo, $._expression)
),
binaryExpr(prec.left, 6, "+", $._expression),
binaryExpr(prec.left, 6, "+.", $._expression),
binaryExpr(prec.left, 6, "-", $._expression),
@ -271,7 +313,8 @@ module.exports = grammar({
binaryExpr(prec.left, 7, "*.", $._expression),
binaryExpr(prec.left, 7, "/", $._expression),
binaryExpr(prec.left, 7, "/.", $._expression),
binaryExpr(prec.left, 7, "%", $._expression)
binaryExpr(prec.left, 7, "%", $._expression),
binaryExpr(prec.left, 7, "<>", $._expression)
),
// The way that this function is written in the Gleam parser is essentially
// incompatible with tree-sitter. It first parses some base expression,
@ -291,15 +334,16 @@ module.exports = grammar({
$.record,
$.identifier,
$.todo,
$.panic,
$.tuple,
$.echo,
$.list,
alias($._expression_bit_string, $.bit_string),
$.anonymous_function,
$.expression_group,
$.block,
$.case,
$.let,
$.assert,
$.negation,
$.boolean_negation,
$.integer_negation,
$.record_update,
$.tuple_access,
$.field_access,
@ -311,7 +355,32 @@ module.exports = grammar({
optional(field("arguments", $.arguments))
),
todo: ($) =>
seq("todo", optional(seq("(", field("message", $.string), ")"))),
prec.left(
seq(
"todo",
optional(
choice(
// DEPRECATED: The 'as' syntax was introduced in v0.30.
seq("(", field("message", $.string), ")"),
seq("as", field("message", $._expression))
)
)
)
),
panic: ($) =>
prec.left(
seq(
"panic",
optional(
choice(
seq("(", field("message", $.string), ")"),
seq("as", field("message", $._expression))
)
)
)
),
pipeline_echo: (_$) => prec.left("echo"),
echo: ($) => seq("echo", $._expression),
tuple: ($) => seq("#", "(", optional(series_of($._expression, ",")), ")"),
list: ($) =>
seq(
@ -335,9 +404,7 @@ module.exports = grammar({
alias($.anonymous_function_parameters, $.function_parameters)
),
optional(seq("->", field("return_type", $._type))),
"{",
field("body", alias($._expression_seq, $.function_body)),
"}"
field("body", $.block)
),
anonymous_function_parameters: ($) =>
seq(
@ -355,13 +422,13 @@ module.exports = grammar({
choice($._discard_param, $._name_param),
optional($._type_annotation)
),
expression_group: ($) => seq("{", $._expression_seq, "}"),
block: ($) => seq("{", optional($._statement_seq), "}"),
case: ($) =>
seq(
"case",
field("subjects", $.case_subjects),
"{",
field("clauses", $.case_clauses),
optional(field("clauses", $.case_clauses)),
"}"
),
case_subjects: ($) => seq(series_of($._expression, ",")),
@ -385,7 +452,8 @@ module.exports = grammar({
_case_clause_guard_expression: ($) =>
choice(
$._case_clause_guard_unit,
alias($._case_clause_guard_binary_expression, $.binary_expression)
alias($._case_clause_guard_binary_expression, $.binary_expression),
$.boolean_negation
),
_case_clause_guard_binary_expression: ($) =>
choice(
@ -400,7 +468,16 @@ module.exports = grammar({
binaryExpr(prec.left, 4, ">", $._case_clause_guard_expression),
binaryExpr(prec.left, 4, ">=", $._case_clause_guard_expression),
binaryExpr(prec.left, 4, ">.", $._case_clause_guard_expression),
binaryExpr(prec.left, 4, ">=.", $._case_clause_guard_expression)
binaryExpr(prec.left, 4, ">=.", $._case_clause_guard_expression),
binaryExpr(prec.left, 5, "+", $._case_clause_guard_expression),
binaryExpr(prec.left, 5, "+.", $._case_clause_guard_expression),
binaryExpr(prec.left, 5, "-", $._case_clause_guard_expression),
binaryExpr(prec.left, 5, "-.", $._case_clause_guard_expression),
binaryExpr(prec.left, 6, "*", $._case_clause_guard_expression),
binaryExpr(prec.left, 6, "*.", $._case_clause_guard_expression),
binaryExpr(prec.left, 6, "/", $._case_clause_guard_expression),
binaryExpr(prec.left, 6, "/.", $._case_clause_guard_expression),
binaryExpr(prec.left, 6, "%", $._case_clause_guard_expression)
),
_case_clause_guard_unit: ($) =>
choice(
@ -411,9 +488,31 @@ module.exports = grammar({
),
_case_clause_tuple_access: ($) =>
seq(field("tuple", $.identifier), ".", field("index", $.integer)),
let_assert: ($) =>
seq(
"let",
"assert",
$._assignment,
optional(seq("as", field("message", $._expression)))
),
assert: ($) =>
seq(
"assert",
field("value", $._expression),
optional(seq("as", field("message", $._expression)))
),
let: ($) => seq("let", $._assignment),
assert: ($) => seq("assert", $._assignment),
negation: ($) => seq("!", $._expression_unit),
use: ($) =>
seq(
"use",
optional(field("assignments", $.use_assignments)),
"<-",
field("value", $._expression)
),
use_assignments: ($) => series_of($.use_assignment, ","),
use_assignment: ($) => seq($._pattern, optional($._type_annotation)),
boolean_negation: ($) => seq("!", $._expression_unit),
integer_negation: ($) => seq("-", $._expression_unit),
_assignment: ($) =>
seq(
field("pattern", $._pattern),
@ -436,7 +535,10 @@ module.exports = grammar({
),
record_update_arguments: ($) => series_of($.record_update_argument, ","),
record_update_argument: ($) =>
seq(field("label", $.label), ":", field("value", $._expression)),
choice(
seq(field("label", $.label), ":", field("value", $._expression)),
seq(field("label", $.label), ":")
),
// As with other AST nodes in this section, `_maybe_record_expression`,
// `_maybe_tuple_expression`, and `_maybe_function_expresssion` have no
// corollaries in the Gleam parser. These anonymous AST node denote any
@ -450,7 +552,7 @@ module.exports = grammar({
$.identifier,
$.function_call,
$.tuple,
$.expression_group,
$.block,
$.case,
$.field_access,
$.tuple_access
@ -468,7 +570,7 @@ module.exports = grammar({
$.record,
$.identifier,
$.function_call,
$.expression_group,
$.block,
$.case,
$.record_update,
$.field_access,
@ -494,7 +596,7 @@ module.exports = grammar({
choice(
$.identifier,
$.anonymous_function,
$.expression_group,
$.block,
$.case,
$.tuple_access,
$.field_access,
@ -504,9 +606,12 @@ module.exports = grammar({
// record arguments, hence the ambiguous name.
arguments: ($) => seq("(", optional(series_of($.argument, ",")), ")"),
argument: ($) =>
seq(
optional(seq(field("label", $.label), ":")),
field("value", choice($.hole, $._expression))
choice(
seq(
optional(seq(field("label", $.label), ":")),
field("value", choice($.hole, $._expression))
),
seq(field("label", $.label), ":")
),
hole: ($) => $._discard_name,
function_call: ($) =>
@ -514,19 +619,27 @@ module.exports = grammar({
field("function", $._maybe_function_expression),
field("arguments", $.arguments)
),
_pattern_expression: ($) =>
choice(
$.identifier,
$.discard,
$.record_pattern,
$.string,
$.integer,
$.float,
$.tuple_pattern,
alias($._pattern_bit_string, $.bit_string_pattern),
$.list_pattern,
alias($._pattern_binary_expression, $.binary_expression)
),
_pattern_binary_expression: ($) =>
choice(
binaryExpr(prec.left, 1, "<>", $._pattern_expression),
binaryExpr(prec.left, 1, "as", $.string, $.identifier)
),
_pattern: ($) =>
seq(
choice(
$.identifier,
$.discard,
$.record_pattern,
$.string,
$.integer,
$.float,
$.tuple_pattern,
alias($._pattern_bit_string, $.bit_string_pattern),
$.list_pattern
),
$._pattern_expression,
optional(field("assign", seq("as", $.identifier)))
),
record_pattern: ($) =>
@ -542,9 +655,12 @@ module.exports = grammar({
")"
),
record_pattern_argument: ($) =>
seq(
optional(seq(field("label", $.label), ":")),
field("pattern", $._pattern)
choice(
seq(
optional(seq(field("label", $.label), ":")),
field("pattern", $._pattern)
),
seq(field("label", $.label), ":")
),
pattern_spread: ($) => seq("..", optional(",")),
tuple_pattern: ($) =>
@ -586,6 +702,7 @@ module.exports = grammar({
data_constructors: ($) => repeat1($.data_constructor),
data_constructor: ($) =>
seq(
optional($.attribute),
field("name", $.constructor_name),
optional(field("arguments", $.data_constructor_arguments))
),
@ -612,8 +729,12 @@ module.exports = grammar({
repeat(choice($.escape_sequence, $.quoted_content)),
token.immediate('"')
),
escape_sequence: ($) => token.immediate(/\\[efnrt\"\\]/),
float: ($) => /-?[0-9_]+\.[0-9_]*/,
escape_sequence: ($) =>
choice(
token.immediate(/\\[efnrt\"\\]/),
token.immediate(/\\u\{[0-9a-fA-F]{1,6}\}/)
),
float: ($) => /-?[0-9_]+\.[0-9_]*(e-?[0-9_]+)?/,
integer: ($) =>
seq(optional("-"), choice($._hex, $._decimal, $._octal, $._binary)),
_hex: ($) => /0[xX][0-9a-fA-F_]+/,
@ -769,9 +890,13 @@ function series_of(rule, separator) {
// A binary expression with a left-hand side, infix operator, and then right-hand-side
// https://github.com/elixir-lang/tree-sitter-elixir/blob/de20391afe5cb03ef1e8a8e43167e7b58cc52869/grammar.js#L850-L859
function binaryExpr(assoc, precedence, operator, expr) {
function binaryExpr(assoc, precedence, operator, left, right = null) {
return assoc(
precedence,
seq(field("left", expr), field("operator", operator), field("right", expr))
seq(
field("left", left),
field("operator", operator),
field("right", right || left)
)
);
}

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
{
"name": "tree-sitter-gleam",
"version": "0.22.1",
"version": "1.0.0",
"description": "A tree-sitter grammar for the Gleam programming language",
"main": "bindings/node",
"scripts": {
@ -27,11 +27,12 @@
},
"homepage": "https://github.com/J3RN/tree-sitter-gleam#readme",
"dependencies": {
"nan": "^2.15.0"
"nan": "^2.18.0"
},
"devDependencies": {
"prettier": "^2.5.1",
"tree-sitter-cli": "^0.20.6"
"tree-sitter-cli": "^0.20.6",
"node-gyp": "^10.0.1"
},
"tree-sitter": [
{

@ -21,6 +21,8 @@
; Functions
(unqualified_import (identifier) @function)
(unqualified_import "type" (type_identifier) @type)
(unqualified_import (type_identifier) @constructor)
(function
name: (identifier) @function)
(external_function
@ -43,6 +45,13 @@
(tuple_access
index: (integer) @property)
; Attributes
(attribute
"@" @attribute
name: (identifier) @attribute)
(attribute_value (identifier) @constant)
; Type names
(remote_type_identifier) @type
(type_identifier) @type
@ -52,19 +61,24 @@
; Literals
(string) @string
((escape_sequence) @warning
; Deprecated in v0.33.0-rc2:
(#eq? @warning "\\e"))
(escape_sequence) @string.escape
(bit_string_segment_option) @function.builtin
(integer) @number
(float) @number
; Reserved identifiers
; TODO: when tree-sitter supports `#any-of?` in the Rust bindings,
; refactor this to use `#any-of?` rather than `#match?`
((identifier) @error
(#match? @error "^(auto|delegate|derive|else|implement|macro|test)$"))
; Variables
(identifier) @variable
(discard) @comment.unused
; Operators
(binary_expression
operator: _ @operator)
"!" @operator
; Keywords
[
(visibility_modifier) ; "pub"
@ -73,16 +87,25 @@
"assert"
"case"
"const"
"echo"
; DEPRECATED: 'external' was removed in v0.30.
"external"
"fn"
"if"
"import"
"let"
"panic"
"todo"
"try"
"type"
"use"
] @keyword
; Operators
(binary_expression
operator: _ @operator)
(boolean_negation "!" @operator)
(integer_negation "-" @operator)
; Punctuation
[
"("
@ -104,4 +127,5 @@
"->"
".."
"-"
"<-"
] @punctuation.delimiter

@ -1,4 +1,6 @@
; Scopes
(block) @local.scope
(function) @local.scope
(case_clause) @local.scope

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,101 @@
================================================================================
Target attribute
================================================================================
@target(erlang)
pub fn main() { todo }
--------------------------------------------------------------------------------
(source_file
(attribute
name: (identifier)
arguments: (arguments
(attribute_value
(identifier))))
(function
(visibility_modifier)
name: (identifier)
parameters: (function_parameters)
body: (block
(todo))))
================================================================================
Attribute with multiple values
================================================================================
@deprecated(since: "1.2.0", replacement: wobble)
pub fn wibble() { todo }
--------------------------------------------------------------------------------
(source_file
(attribute
name: (identifier)
arguments: (arguments
(attribute_value
label: (label)
value: (string
(quoted_content)))
(attribute_value
label: (label)
value: (identifier))))
(function
(visibility_modifier)
name: (identifier)
parameters: (function_parameters)
body: (block
(todo))))
================================================================================
Attribute without arguments
================================================================================
@internal
pub fn wibble() { todo }
--------------------------------------------------------------------------------
(source_file
(attribute
name: (identifier))
(function
(visibility_modifier)
name: (identifier)
parameters: (function_parameters)
body: (block
(todo))))
================================================================================
Individually deprecated constructors
================================================================================
pub type SomeType {
NotDeprecated
@deprecated("Please use the NotDeprecated variant")
Deprecated(reason: String)
}
--------------------------------------------------------------------------------
(source_file
(type_definition
(visibility_modifier)
(type_name
name: (type_identifier))
(data_constructors
(data_constructor
name: (constructor_name))
(data_constructor
(attribute
name: (identifier)
arguments: (arguments
(attribute_value
(string
(quoted_content)))))
name: (constructor_name)
arguments: (data_constructor_arguments
(data_constructor_argument
label: (label)
value: (type
name: (type_identifier))))))))

@ -0,0 +1,255 @@
================================================================================
Case examples
================================================================================
case value {
"A" -> True
_ -> False
}
case value {}
--------------------------------------------------------------------------------
(source_file
(case
(case_subjects
(identifier))
(case_clauses
(case_clause
(case_clause_patterns
(case_clause_pattern
(string
(quoted_content))))
(record
(constructor_name)))
(case_clause
(case_clause_patterns
(case_clause_pattern
(discard)))
(record
(constructor_name)))))
(case
(case_subjects
(identifier))))
================================================================================
Case examples
================================================================================
// From https://gleam.run/news/v0.31-keeping-dependencies-explicit/#quality-of-life-improvements
pub fn listed(names: List(String), person: Person) -> String {
case names {
[name, ..names] if name == person.name -> True
[_, ..names] -> listed(names, person)
[] -> False
}
}
--------------------------------------------------------------------------------
(source_file
(comment)
(function
(visibility_modifier)
(identifier)
(function_parameters
(function_parameter
(identifier)
(type
(type_identifier)
(type_arguments
(type_argument
(type
(type_identifier))))))
(function_parameter
(identifier)
(type
(type_identifier))))
(type
(type_identifier))
(block
(case
(case_subjects
(identifier))
(case_clauses
(case_clause
(case_clause_patterns
(case_clause_pattern
(list_pattern
(identifier)
(list_pattern_tail
(identifier)))))
(case_clause_guard
(binary_expression
(identifier)
(field_access
(identifier)
(label))))
(record
(constructor_name)))
(case_clause
(case_clause_patterns
(case_clause_pattern
(list_pattern
(discard)
(list_pattern_tail
(identifier)))))
(function_call
(identifier)
(arguments
(argument
(identifier))
(argument
(identifier)))))
(case_clause
(case_clause_patterns
(case_clause_pattern
(list_pattern)))
(record
(constructor_name))))))))
================================================================================
Pattern matching binaries with 'as'
================================================================================
// From https://gleam.run/news/v0.31-keeping-dependencies-explicit/#quality-of-life-improvements
case tag {
"category " as key <> value
| "region " as key <> value
| "priority " as key <> value -> {
let key = string.trim(key)
Ok(Tag(key, value))
}
_ -> Error(Nil)
}
--------------------------------------------------------------------------------
(source_file
(comment)
(case
(case_subjects
(identifier))
(case_clauses
(case_clause
(case_clause_patterns
(case_clause_pattern
(binary_expression
(binary_expression
(string
(quoted_content))
(identifier))
(identifier)))
(case_clause_pattern
(binary_expression
(binary_expression
(string
(quoted_content))
(identifier))
(identifier)))
(case_clause_pattern
(binary_expression
(binary_expression
(string
(quoted_content))
(identifier))
(identifier))))
(block
(let
(identifier)
(function_call
(field_access
(identifier)
(label))
(arguments
(argument
(identifier)))))
(record
(constructor_name)
(arguments
(argument
(record
(constructor_name)
(arguments
(argument
(identifier))
(argument
(identifier)))))))))
(case_clause
(case_clause_patterns
(case_clause_pattern
(discard)))
(record
(constructor_name)
(arguments
(argument
(record
(constructor_name)))))))))
================================================================================
Case with boolean negation in a guard
================================================================================
case var {
1 if !other_var -> True
_ -> False
}
--------------------------------------------------------------------------------
(source_file
(case
(case_subjects
(identifier))
(case_clauses
(case_clause
(case_clause_patterns
(case_clause_pattern
(integer)))
(case_clause_guard
(boolean_negation
(identifier)))
(record
(constructor_name)))
(case_clause
(case_clause_patterns
(case_clause_pattern
(discard)))
(record
(constructor_name))))))
================================================================================
Case with int remainder in guard
================================================================================
case var {
_ if 11 % 2 == 0 -> True
_ -> False
}
--------------------------------------------------------------------------------
(source_file
(case
(case_subjects
(identifier))
(case_clauses
(case_clause
(case_clause_patterns
(case_clause_pattern
(discard)))
(case_clause_guard
(binary_expression
(binary_expression
(integer)
(integer))
(integer)))
(record
(constructor_name)))
(case_clause
(case_clause_patterns
(case_clause_pattern
(discard)))
(record
(constructor_name))))))

@ -26,6 +26,9 @@ const a = Cat("Ginny", 1950)
const a = Person(name: "Billy", age: 52)
const a = uri.Uri(host: "github.com")
const a: option.Option(String) = option.Some("Hello, World!")
const var_alias = b
const int_identity_alias: fn(Int) -> Int = int_identity
const fun_tuple: #(fn(Float) -> String, fn(Int) -> String) = #(float.to_string, int.to_string)
--------------------------------------------------------------------------------
@ -206,7 +209,41 @@ const a: option.Option(String) = option.Some("Hello, World!")
arguments: (arguments
(argument
value: (string
(quoted_content)))))))
(quoted_content))))))
(constant
name: (identifier)
value: (identifier))
(constant
name: (identifier)
type: (function_type
parameter_types: (function_parameter_types
(type
name: (type_identifier)))
return_type: (type
name: (type_identifier)))
value: (identifier))
(constant
name: (identifier)
type: (tuple_type
(function_type
parameter_types: (function_parameter_types
(type
name: (type_identifier)))
return_type: (type
name: (type_identifier)))
(function_type
parameter_types: (function_parameter_types
(type
name: (type_identifier)))
return_type: (type
name: (type_identifier))))
value: (tuple
(field_access
record: (identifier)
field: (label))
(field_access
record: (identifier)
field: (label)))))
================================================================================
Public constants
@ -386,3 +423,43 @@ pub const a = uri.Uri(host: "github.com")
label: (label)
value: (string
(quoted_content)))))))
================================================================================
Scientific notation
================================================================================
const a = 0.0e0
const a = 1.0e123_456
const a = -100.001e-1_230
--------------------------------------------------------------------------------
(source_file
(constant
(identifier)
(float))
(constant
(identifier)
(float))
(constant
(identifier)
(float)))
================================================================================
Constant with shorthand labels
================================================================================
const b = Wibble(arg:, arg:)
--------------------------------------------------------------------------------
(source_file
(constant
(identifier)
(record
(constructor_name)
(arguments
(argument
(label))
(argument
(label))))))

@ -247,3 +247,26 @@ pub opaque type Animal(name, cuteness) {
label: (label)
value: (type
name: (type_identifier))))))))
================================================================================
Record update with shorthand labels
================================================================================
Wibble(..wibble, arg:, arg:, arg: todo as "no shorthand")
--------------------------------------------------------------------------------
(source_file
(record_update
(constructor_name)
(identifier)
(record_update_arguments
(record_update_argument
(label))
(record_update_argument
(label))
(record_update_argument
(label)
(todo
(string
(quoted_content)))))))

@ -16,7 +16,7 @@ pub fn main() {
(visibility_modifier)
(identifier)
(function_parameters)
(function_body
(block
(case
(case_subjects
(identifier))
@ -48,3 +48,31 @@ pub fn main() {
(argument
(string
(quoted_content)))))))))))
================================================================================
Pattern with label shorthand
================================================================================
pub fn main() {
let Wibble(arg1:, arg2:) = todo as "a"
}
--------------------------------------------------------------------------------
(source_file
(function
(visibility_modifier)
(identifier)
(function_parameters)
(block
(let
(record_pattern
(constructor_name)
(record_pattern_arguments
(record_pattern_argument
(label))
(record_pattern_argument
(label))))
(todo
(string
(quoted_content)))))))

@ -0,0 +1,112 @@
================================================================================
Echo with expression
================================================================================
pub fn main() {
echo 1
}
--------------------------------------------------------------------------------
(source_file
(function
(visibility_modifier)
(identifier)
(function_parameters)
(block
(echo
(integer)))))
================================================================================
Echo in pipeline
================================================================================
pub fn main() {
[]
|> echo
|> panic
}
--------------------------------------------------------------------------------
(source_file
(function
(visibility_modifier)
(identifier)
(function_parameters)
(block
(binary_expression
(binary_expression
(list)
(pipeline_echo))
(panic)))))
================================================================================
Echo last in pipeline
================================================================================
pub fn main() {
[]
|> echo
1
}
--------------------------------------------------------------------------------
(source_file
(function
(visibility_modifier)
(identifier)
(function_parameters)
(block
(binary_expression
(list)
(pipeline_echo))
(integer))))
================================================================================
Echo precedence with pipes
================================================================================
pub fn main() {
echo 1 |> 2
3
}
--------------------------------------------------------------------------------
(source_file
(function
(visibility_modifier)
(identifier)
(function_parameters)
(block
(echo
(binary_expression
(integer)
(integer)))
(integer))))
================================================================================
Echo precedence with binop
================================================================================
pub fn main() {
echo 1 + 2
3
}
--------------------------------------------------------------------------------
(source_file
(function
(visibility_modifier)
(identifier)
(function_parameters)
(block
(echo
(binary_expression
(integer)
(integer)))
(integer))))

@ -22,7 +22,7 @@ Bit-string expression
(bit_string_segment_option)))))
================================================================================
Negation
Boolean Negation
================================================================================
!False
@ -31,12 +31,149 @@ True && !False
--------------------------------------------------------------------------------
(source_file
(negation
(boolean_negation
(record
(constructor_name)))
(binary_expression
(record
(constructor_name))
(negation
(boolean_negation
(record
(constructor_name)))))
================================================================================
Integer Negation
================================================================================
{-x}
{-{5*30}}
{-my_fun()}
--------------------------------------------------------------------------------
(source_file
(block
(integer_negation
(identifier)))
(block
(integer_negation
(block
(binary_expression
(integer)
(integer)))))
(block
(integer_negation
(function_call
(identifier)
(arguments)))))
================================================================================
Concatenation
================================================================================
let concat = "a" <> "b"
case "12345" {
"0" <> rest -> rest
"12" <> "34" <> "5" -> "match"
_ -> ""
}
--------------------------------------------------------------------------------
(source_file
(let
(identifier)
(binary_expression
(string
(quoted_content))
(string
(quoted_content))))
(case
(case_subjects
(string
(quoted_content)))
(case_clauses
(case_clause
(case_clause_patterns
(case_clause_pattern
(binary_expression
(string
(quoted_content))
(identifier))))
(identifier))
(case_clause
(case_clause_patterns
(case_clause_pattern
(binary_expression
(binary_expression
(string
(quoted_content))
(string
(quoted_content)))
(string
(quoted_content)))))
(string
(quoted_content)))
(case_clause
(case_clause_patterns
(case_clause_pattern
(discard)))
(string)))))
================================================================================
Todo and panic 'as' with string expressions
================================================================================
todo as { "Hello, " <> "world!" }
panic as { "Hello, " <> "world!" }
--------------------------------------------------------------------------------
(source_file
(todo
(block
(binary_expression
(string
(quoted_content))
(string
(quoted_content)))))
(panic
(block
(binary_expression
(string
(quoted_content))
(string
(quoted_content))))))
================================================================================
Todo and panic in function application style
================================================================================
todo("don't panic")
panic("aaaah!")
--------------------------------------------------------------------------------
(source_file
(todo
(string
(quoted_content)))
(panic
(string
(quoted_content))))
================================================================================
Nested field access
================================================================================
config.connection.host
--------------------------------------------------------------------------------
(source_file
(field_access
(field_access
(identifier)
(label))
(label)))

@ -136,3 +136,40 @@ pub external fn a() -> #(List(Int), fn(Int) -> String) = "x" "y"
(quoted_content))
(string
(quoted_content)))))
================================================================================
External function with attribute syntax
================================================================================
@external(erlang, "erlang", "integer_to_list")
fn integer_to_list(int int: Int, base base: Int) -> String
---
(source_file
(attribute
(identifier)
(arguments
(attribute_value
(identifier))
(attribute_value
(string
(quoted_content)))
(attribute_value
(string
(quoted_content)))))
(function
(identifier)
(function_parameters
(function_parameter
(label)
(identifier)
(type
(type_identifier)))
(function_parameter
(label)
(identifier)
(type
(type_identifier))))
(type
(type_identifier))))

@ -2,9 +2,9 @@
External types
================================================================================
external type IODevice
external type IODevice()
external type Map(key, value)
type IODevice
type IODevice()
type Map(key, value)
--------------------------------------------------------------------------------
@ -27,9 +27,9 @@ external type Map(key, value)
Public external types
================================================================================
pub external type IODevice
pub external type IODevice()
pub external type Map(key, value)
pub type IODevice
pub type IODevice()
pub type Map(key, value)
--------------------------------------------------------------------------------

@ -35,7 +35,7 @@ fn replace(
name: (type_identifier))))
return_type: (type
name: (type_identifier))
body: (function_body
body: (block
(binary_expression
left: (identifier)
right: (identifier))))
@ -52,7 +52,7 @@ fn replace(
name: (identifier)
type: (type_var)))
return_type: (type_var)
body: (function_body
body: (block
(function_call
function: (identifier)
arguments: (arguments
@ -67,7 +67,7 @@ fn replace(
parameters: (function_parameters
(function_parameter
name: (identifier)))
body: (function_body
body: (block
(identifier)))
(function
name: (identifier)
@ -87,7 +87,7 @@ fn replace(
name: (identifier)
type: (type
name: (type_identifier))))
body: (function_body
body: (block
(function_call
function: (field_access
record: (identifier)
@ -141,7 +141,7 @@ pub fn replace(
name: (type_identifier))))
return_type: (type
name: (type_identifier))
body: (function_body
body: (block
(binary_expression
left: (identifier)
right: (identifier))))
@ -159,7 +159,7 @@ pub fn replace(
name: (identifier)
type: (type_var)))
return_type: (type_var)
body: (function_body
body: (block
(function_call
function: (identifier)
arguments: (arguments
@ -175,7 +175,7 @@ pub fn replace(
parameters: (function_parameters
(function_parameter
name: (identifier)))
body: (function_body
body: (block
(identifier)))
(function
(visibility_modifier)
@ -196,7 +196,7 @@ pub fn replace(
name: (identifier)
type: (type
name: (type_identifier))))
body: (function_body
body: (block
(function_call
function: (field_access
record: (identifier)
@ -216,6 +216,7 @@ pub fn replace(
Basic functions
================================================================================
fn unfinished() {}
fn str() {
"Hello, World!"
}
@ -238,7 +239,11 @@ fn funcall() {
myfun()
}
fn unfinished() {
todo("Finish me!")
todo as "Finish me!"
}
fn do_panic() {
panic
panic as "aaaah!"
}
fn tuple(x) {
#(x, 1)
@ -252,7 +257,7 @@ fn bit_string() {
fn return_fun(x) {
fn(y: Int) { x + y }
}
fn expression_group() {
fn block() {
{
1 + 1
"Hello, World!"
@ -264,6 +269,17 @@ fn foob(x, y) {
_else -> False
}
}
fn assert_assignment() {
let assert Ok(a) = Ok(1)
let assert x = {
1 + 1
"Hello, World!"
}
let assert y = x
let assert #(x, _) = #(1, 2)
let assert Ok(_) = Ok(10) as "This never fails"
let assert 10 = 11 as { "message1" <> "message2" }
}
fn assignment() {
let x = {
1 + 1
@ -272,13 +288,15 @@ fn assignment() {
let y = x
let #(x, _) = #(1, 2)
}
fn assertations() {
assert x = {
1 + 1
"Hello, World!"
}
assert y = x
assert #(x, _) = #(1, 2)
fn assertions() {
assert True && False
assert add(1, 2) == 3
assert !some_value
assert {
10
11
} != 10
assert result.is_ok(Ok(10))
}
fn update(x) {
Cat(..x, name: "Nubi", cuteness: 1000 + 1001)
@ -297,23 +315,27 @@ fn field_access(x) {
(function
name: (identifier)
parameters: (function_parameters)
body: (function_body
body: (block))
(function
name: (identifier)
parameters: (function_parameters)
body: (block
(string
(quoted_content))))
(function
name: (identifier)
parameters: (function_parameters)
body: (function_body
body: (block
(integer)))
(function
name: (identifier)
parameters: (function_parameters)
body: (function_body
body: (block
(float)))
(function
name: (identifier)
parameters: (function_parameters)
body: (function_body
body: (block
(record
name: (constructor_name)
arguments: (arguments
@ -324,7 +346,7 @@ fn field_access(x) {
(function
name: (identifier)
parameters: (function_parameters)
body: (function_body
body: (block
(record
name: (remote_constructor_name
module: (identifier)
@ -339,28 +361,36 @@ fn field_access(x) {
parameters: (function_parameters
(function_parameter
name: (identifier)))
body: (function_body
body: (block
(identifier)))
(function
name: (identifier)
parameters: (function_parameters)
body: (function_body
body: (block
(function_call
function: (identifier)
arguments: (arguments))))
(function
name: (identifier)
parameters: (function_parameters)
body: (function_body
body: (block
(todo
message: (string
(quoted_content)))))
(function
name: (identifier)
parameters: (function_parameters)
body: (block
(panic)
(panic
message: (string
(quoted_content)))))
(function
name: (identifier)
parameters: (function_parameters
(function_parameter
name: (identifier)))
body: (function_body
body: (block
(tuple
(identifier)
(integer))))
@ -369,7 +399,7 @@ fn field_access(x) {
parameters: (function_parameters
(function_parameter
name: (identifier)))
body: (function_body
body: (block
(list
(integer)
(integer)
@ -377,7 +407,7 @@ fn field_access(x) {
(function
name: (identifier)
parameters: (function_parameters)
body: (function_body
body: (block
(bit_string
(bit_string_segment
value: (integer)
@ -396,22 +426,22 @@ fn field_access(x) {
parameters: (function_parameters
(function_parameter
name: (identifier)))
body: (function_body
body: (block
(anonymous_function
parameters: (function_parameters
(function_parameter
name: (identifier)
type: (type
name: (type_identifier))))
body: (function_body
body: (block
(binary_expression
left: (identifier)
right: (identifier))))))
(function
name: (identifier)
parameters: (function_parameters)
body: (function_body
(expression_group
body: (block
(block
(binary_expression
left: (integer)
right: (integer))
@ -424,7 +454,7 @@ fn field_access(x) {
name: (identifier))
(function_parameter
name: (identifier)))
body: (function_body
body: (block
(case
subjects: (case_subjects
(identifier)
@ -449,53 +479,127 @@ fn field_access(x) {
(function
name: (identifier)
parameters: (function_parameters)
body: (function_body
(let
body: (block
(let_assert
pattern: (record_pattern
name: (constructor_name)
arguments: (record_pattern_arguments
(record_pattern_argument
pattern: (identifier))))
value: (record
name: (constructor_name)
arguments: (arguments
(argument
value: (integer)))))
(let_assert
pattern: (identifier)
value: (expression_group
value: (block
(binary_expression
left: (integer)
right: (integer))
(string
(quoted_content))))
(let
(let_assert
pattern: (identifier)
value: (identifier))
(let
(let_assert
pattern: (tuple_pattern
(identifier)
(discard))
value: (tuple
(integer)
(integer)))))
(integer)))
(let_assert
pattern: (record_pattern
name: (constructor_name)
arguments: (record_pattern_arguments
(record_pattern_argument
pattern: (discard))))
value: (record
name: (constructor_name)
arguments: (arguments
(argument
value: (integer))))
message: (string
(quoted_content)))
(let_assert
pattern: (integer)
value: (integer)
message: (block
(binary_expression
left: (string
(quoted_content))
right: (string
(quoted_content)))))))
(function
name: (identifier)
parameters: (function_parameters)
body: (function_body
(assert
body: (block
(let
pattern: (identifier)
value: (expression_group
value: (block
(binary_expression
left: (integer)
right: (integer))
(string
(quoted_content))))
(assert
(let
pattern: (identifier)
value: (identifier))
(assert
(let
pattern: (tuple_pattern
(identifier)
(discard))
value: (tuple
(integer)
(integer)))))
(function
name: (identifier)
parameters: (function_parameters)
body: (block
(assert
value: (binary_expression
left: (record
name: (constructor_name))
right: (record
name: (constructor_name))))
(assert
value: (binary_expression
left: (function_call
function: (identifier)
arguments: (arguments
(argument
value: (integer))
(argument
value: (integer))))
right: (integer)))
(assert
value: (boolean_negation
(identifier)))
(assert
value: (binary_expression
left: (block
(integer)
(integer))
right: (integer)))
(assert
value: (function_call
function: (field_access
record: (identifier)
field: (label))
arguments: (arguments
(argument
value: (record
name: (constructor_name)
arguments: (arguments
(argument
value: (integer))))))))))
(function
name: (identifier)
parameters: (function_parameters
(function_parameter
name: (identifier)))
body: (function_body
body: (block
(record_update
constructor: (constructor_name)
spread: (identifier)
@ -529,7 +633,7 @@ fn field_access(x) {
parameters: (function_parameters
(function_parameter
name: (identifier)))
body: (function_body
body: (block
(tuple_access
tuple: (identifier)
index: (integer))))
@ -538,7 +642,7 @@ fn field_access(x) {
parameters: (function_parameters
(function_parameter
name: (identifier)))
body: (function_body
body: (block
(field_access
record: (identifier)
field: (label)))))
@ -582,7 +686,7 @@ fn trial(x, y, z) {
name: (identifier))
(function_parameter
name: (identifier)))
body: (function_body
body: (block
(case
subjects: (case_subjects
(identifier))
@ -697,84 +801,34 @@ fn trial(x, y, z) {
name: (constructor_name))))))))
================================================================================
Try patterns
Let expressions
================================================================================
fn try_try_again(x, y) -> Int {
try int_x = todo
try _who_cares = todo
try file.IODevice() = todo
try Node = todo
try "hello" = todo
try 1 = todo
try 12.34 = todo
try #(a, b) = todo
try <<a:utf8, b:size(8)>> = todo
try [a, b] = todo
}
let foo: fn(Int) -> Int = fn(x) { x }
let fun_ref = float.to_string
--------------------------------------------------------------------------------
(source_file
(function
name: (identifier)
parameters: (function_parameters
(function_parameter
name: (identifier))
(function_parameter
name: (identifier)))
return_type: (type
name: (type_identifier))
body: (function_body
(try
pattern: (identifier)
value: (todo))
(try
pattern: (discard)
value: (todo))
(try
pattern: (record_pattern
name: (remote_constructor_name
module: (identifier)
name: (constructor_name))
arguments: (record_pattern_arguments))
value: (todo))
(try
pattern: (record_pattern
name: (constructor_name))
value: (todo))
(try
pattern: (string
(quoted_content))
value: (todo))
(try
pattern: (integer)
value: (todo))
(try
pattern: (float)
value: (todo))
(try
pattern: (tuple_pattern
(identifier)
(identifier))
value: (todo))
(try
pattern: (bit_string_pattern
(bit_string_segment
value: (identifier)
options: (bit_string_segment_options
(bit_string_segment_option)))
(bit_string_segment
value: (identifier)
options: (bit_string_segment_options
(bit_string_segment_option
(integer)))))
value: (todo))
(try
pattern: (list_pattern
(identifier)
(identifier))
value: (todo)))))
(let
pattern: (identifier)
type: (function_type
parameter_types: (function_parameter_types
(type
name: (type_identifier)))
return_type: (type
name: (type_identifier)))
value: (anonymous_function
parameters: (function_parameters
(function_parameter
name: (identifier)))
body: (block
(identifier))))
(let
pattern: (identifier)
value: (field_access
record: (identifier)
field: (label))))
================================================================================
Complex binary expressions
@ -794,7 +848,7 @@ fn complicated(x, y) {
name: (identifier))
(function_parameter
name: (identifier)))
body: (function_body
body: (block
(binary_expression
left: (binary_expression
left: (binary_expression
@ -838,7 +892,7 @@ fn complex_data_fun(x, y) {
name: (identifier))
(function_parameter
name: (identifier)))
body: (function_body
body: (block
(field_access
record: (tuple_access
tuple: (field_access
@ -906,7 +960,7 @@ fn weird(x) {
parameters: (function_parameters
(function_parameter
name: (identifier)))
body: (function_body
body: (block
(function_call
function: (identifier)
arguments: (arguments
@ -919,7 +973,7 @@ fn weird(x) {
parameters: (function_parameters
(function_parameter
name: (identifier)))
body: (function_body
body: (block
(binary_expression
left: (identifier)
right: (integer))))
@ -927,14 +981,14 @@ fn weird(x) {
(argument
value: (integer))))
(function_call
function: (expression_group
function: (block
(let
pattern: (identifier)
value: (anonymous_function
parameters: (function_parameters
(function_parameter
name: (identifier)))
body: (function_body
body: (block
(binary_expression
left: (identifier)
right: (integer))))))
@ -954,7 +1008,7 @@ fn weird(x) {
parameters: (function_parameters
(function_parameter
name: (identifier)))
body: (function_body
body: (block
(binary_expression
left: (identifier)
right: (integer)))))))
@ -990,7 +1044,7 @@ fn ignores(foo, _bar) {
name: (identifier))
(function_parameter
name: (discard)))
body: (function_body
body: (block
(let
pattern: (discard)
value: (integer))
@ -1021,7 +1075,7 @@ fn lists(x) {
parameters: (function_parameters
(function_parameter
name: (identifier)))
body: (function_body
body: (block
(list)
(list
(integer))
@ -1048,5 +1102,22 @@ io.println("// hello world!\n")
arguments: (arguments
(argument
value: (string
(quoted_content)
(escape_sequence))))))
(quoted_content)
(escape_sequence))))))
================================================================================
Call with label shorthand
================================================================================
wibble(arg1:, arg2:)
--------------------------------------------------------------------------------
(source_file
(function_call
(identifier)
(arguments
(argument
(label))
(argument
(label)))))

@ -0,0 +1,73 @@
================================================================================
Guard Expressions
================================================================================
case value {
n if n + 1 > 10 -> True
n if n / 2 < 5 -> False
}
case value {
n if n -. 1.0 <. 10.0 -> True
n if n *. 2 >. 5 -> False
}
--------------------------------------------------------------------------------
(source_file
(case
(case_subjects
(identifier))
(case_clauses
(case_clause
(case_clause_patterns
(case_clause_pattern
(identifier)))
(case_clause_guard
(binary_expression
(binary_expression
(identifier)
(integer))
(integer)))
(record
(constructor_name)))
(case_clause
(case_clause_patterns
(case_clause_pattern
(identifier)))
(case_clause_guard
(binary_expression
(binary_expression
(identifier)
(integer))
(integer)))
(record
(constructor_name)))))
(case
(case_subjects
(identifier))
(case_clauses
(case_clause
(case_clause_patterns
(case_clause_pattern
(identifier)))
(case_clause_guard
(binary_expression
(binary_expression
(identifier)
(float))
(float)))
(record
(constructor_name)))
(case_clause
(case_clause_patterns
(case_clause_pattern
(identifier)))
(case_clause_guard
(binary_expression
(binary_expression
(identifier)
(integer))
(integer)))
(record
(constructor_name))))))

@ -21,6 +21,7 @@ import a.{b}
import a/b.{c, d}
import a/b.{c as d, e}
import a/b.{c, D as E}
import a/b.{A as B, type C as D}
--------------------------------------------------------------------------------
@ -50,6 +51,15 @@ import a/b.{c, D as E}
imports: (unqualified_imports
(unqualified_import
name: (identifier))
(unqualified_import
name: (constructor_name)
alias: (constructor_name))))
(import
module: (module)
imports: (unqualified_imports
(unqualified_import
name: (constructor_name)
alias: (constructor_name))
(unqualified_import
name: (type_identifier)
alias: (type_identifier)))))
@ -61,6 +71,7 @@ Aliased imports
import a/b.{c as d} as e
import animal/cat as kitty
import animal.{Cat as Kitty} as a
import animal.{type Cat as Kitty} as a
import animal.{}
--------------------------------------------------------------------------------
@ -76,6 +87,13 @@ import animal.{}
(import
module: (module)
alias: (identifier))
(import
module: (module)
imports: (unqualified_imports
(unqualified_import
name: (constructor_name)
alias: (constructor_name)))
alias: (identifier))
(import
module: (module)
imports: (unqualified_imports
@ -86,3 +104,41 @@ import animal.{}
(import
module: (module)
imports: (unqualified_imports)))
================================================================================
Type imports
================================================================================
import a/b.{type C}
import animal.{type Cat as Kitty}
--------------------------------------------------------------------------------
(source_file
(import
module: (module)
imports: (unqualified_imports
(unqualified_import
name: (type_identifier))))
(import
module: (module)
imports: (unqualified_imports
(unqualified_import
name: (type_identifier)
alias: (type_identifier)))))
================================================================================
Discard module imports
================================================================================
import wibble.{wobble} as _
--------------------------------------------------------------------------------
(source_file
(import
(module)
(unqualified_imports
(unqualified_import
(identifier)))
(discard)))

@ -0,0 +1,59 @@
================================================================================
Use
================================================================================
use <- f()
use a <- f()
use a, b, c, d, e <- f()
use #(a, b) <- blah
use x : OfType <- f()
--------------------------------------------------------------------------------
(source_file
(use
(function_call
(identifier)
(arguments)))
(use
(use_assignments
(use_assignment
(identifier)))
(function_call
(identifier)
(arguments)))
(use
(use_assignments
(use_assignment
(identifier))
(use_assignment
(identifier))
(use_assignment
(identifier))
(use_assignment
(identifier))
(use_assignment
(identifier)))
(function_call
(identifier)
(arguments)))
(use
(use_assignments
(use_assignment
(tuple_pattern
(identifier)
(identifier))))
(identifier))
(use
(use_assignments
(use_assignment
(identifier)
(type
(type_identifier))))
(function_call
(identifier)
(arguments))))

@ -0,0 +1,38 @@
================================================================================
Escape sequences
================================================================================
"\t\t\r\nHello, CRLF!"
"¯\\_(ツ)_/¯"
"\"\""
"Hello, \e\f"
// 🏴‍☠️ is 🏴 and ☠️ joined with a zero-width joiner (U+200D)
"🏴‍☠️ == \u{1F3F4}\u{200D}\u{2620}\u{FE0F}"
--------------------------------------------------------------------------------
(source_file
(string
(escape_sequence)
(escape_sequence)
(escape_sequence)
(escape_sequence)
(quoted_content))
(string
(quoted_content)
(escape_sequence)
(quoted_content))
(string
(escape_sequence)
(escape_sequence))
(string
(quoted_content)
(escape_sequence)
(escape_sequence))
(comment)
(string
(quoted_content)
(escape_sequence)
(escape_sequence)
(escape_sequence)
(escape_sequence)))

@ -48,7 +48,7 @@ if javascript {
name: (type_identifier))))
return_type: (type
name: (type_identifier))
body: (function_body
body: (block
(let
pattern: (identifier)
value: (function_call
@ -161,7 +161,7 @@ pub fn negate(bool: Bool) -> Bool {
module: (module)
imports: (unqualified_imports
(unqualified_import
name: (type_identifier))))
name: (constructor_name))))
(statement_comment)
(statement_comment)
(statement_comment)
@ -184,7 +184,7 @@ pub fn negate(bool: Bool) -> Bool {
name: (type_identifier))))
return_type: (type
name: (type_identifier))
body: (function_body
body: (block
(case
subjects: (case_subjects
(identifier))
@ -234,7 +234,7 @@ fn foo(a,) {
(module)
(unqualified_imports
(unqualified_import
(type_identifier))))
(constructor_name))))
(constant
(identifier)
(tuple_type
@ -290,7 +290,7 @@ fn foo(a,) {
(function_parameters
(function_parameter
(identifier)))
(function_body
(block
(function_call
(identifier)
(arguments

@ -0,0 +1,11 @@
pub fn case_with_remainder() {
case todo {
_ if 1 % 2 == 0 -> todo
// ^ number
// ^ operator
// ^ number
// ^ operator
// ^ number
_ -> todo
}
}

@ -0,0 +1,10 @@
const f = 100.001e523
// ^ number
// ^ number
// ^ number
const s = "Hello, \e\t\n"
// ^ warning
// ^ warning
// ^ string.escape
// ^ string.escape

@ -2,8 +2,8 @@ fn case_case(x: Option(String)) {
// ^ variable.parameter
// ^ type
case #(x, x) {
// ^ variable.parameter
// ^ variable.parameter
// ^ variable.parameter
// ^ variable.parameter
#(None, None) -> None
// ^ constructor
// ^ constructor
@ -22,3 +22,11 @@ fn case_case(x: Option(String)) {
z.foo()
// <- module
}
fn shorthand_label_pattern_arg() {
case todo {
Wibble(arg1:, arg2:) -> todo
// ^ property
// ^ property
}
}

@ -0,0 +1,4 @@
pub fn main() {
echo 2
// ^ keyword
}

@ -0,0 +1,16 @@
case "12345" {
"123" <> rest -> rest
// <- string
// ^ operator
// ^ variable
_ -> ""
// ^ string
}
-x
// ^ operator
// ^ variable
panic as "aaah!"
// <- keyword
// ^ keyword
// ^ string

@ -108,3 +108,22 @@ fn comment_string_test() {
// ^ function
// ^ string
}
fn let_assert_test() {
let assert #(x, _) = #(1, 2)
// <- keyword
// ^ keyword
}
fn assert_test() {
assert x == add(1, 4)
// <- keyword
// ^ function
}
fn punned_call_arg_test() {
wibble(arg:, arg2:)
// ^ function
// ^ property
// ^ property
}

@ -6,6 +6,16 @@ import animal/cat as kitty
// ^ module
// ^ module
import animal/cat.{Cat, type Cat}
// ^ constructor
// ^ keyword
// ^ type
import wibble.{wobble} as _
// ^ module
// ^ function
// ^ comment.unused
pub fn main() {
io.println("hello world")
// <- module
@ -48,3 +58,27 @@ fn make_cat() -> kitty.Cat {
// ^ module
// ^ constructor
}
@target(erlang)
// <- attribute
// ^ attribute
// ^ constant
pub external fn display() -> Bool = "erlang" "display"
@target(erlang)
@external(erlang, "wobble", "main")
// <- attribute
// ^ attribute
// ^ constant
// ^ string
// string
pub fn main() -> Int
@deprecated(since: "1.2.0", replacement: wobble)
// <- attribute
// ^ attribute
// ^ property
// ^ string
// ^ property
// ^constant
pub fn wibble() { todo }

@ -12,3 +12,17 @@ pub fn new(name: String) {
// ^ property
// ^ variable.parameter
}
pub fn access() {
let config = Config()
config.connection.host
// ^ variable
// ^ property
// ^ property
}
pub fn record_update_shorthand_label() {
User(..user, name:)
// ^ constructor
// ^ property
}

@ -0,0 +1,14 @@
auto
// <- error
delegate
// <- error
derive
// <- error
else
// <- error
implement
// <- error
macro
// <- error
test
// <- error

@ -1,8 +1,8 @@
import gleam/option.{Option, Some, None}
import gleam/option.{type Option, Some, None}
// ^ reference.module
// ^ reference.type
// ^ reference.type
// ^ reference.type
// ^ reference.type
// ^ reference.constructor
// ^ reference.constructor
import gleam/bit_builder
// ^ reference.module