pull/230/head
Wilfred Hughes 2022-04-02 17:18:59 +07:00
commit 28c6e35943
51 changed files with 66239 additions and 0 deletions

@ -1,5 +1,9 @@
## 0.26 (unreleased)
### Parsing
Added Gleam support.
### Command Line Interface
Added the `--display` option to switch between `side-by-side`,

@ -104,6 +104,11 @@ fn main() {
src_dir: "vendor/tree-sitter-elixir-src",
extra_files: vec!["scanner.cc"],
},
TreeSitterParser {
name: "tree-sitter-gleam",
src_dir: "vendor/tree-sitter-gleam-src",
extra_files: vec![],
},
TreeSitterParser {
name: "tree-sitter-go",
src_dir: "vendor/tree-sitter-go-src",

@ -124,6 +124,9 @@ c2d6920b57a543172aef2adfb7aa2df5 -
sample_files/text_before.txt sample_files/text_after.txt
d5bc09cc5298b5216e5989a31238be0b -
sample_files/todomvc_before.gleam sample_files/todomvc_after.gleam
5f57fcf8406d38e26a8fbb1552cc7b63 -
sample_files/typing_before.ml sample_files/typing_after.ml
22eec08aa24c88b330061de913dfce7d -

@ -0,0 +1,52 @@
import todomvc/web/routes
import todomvc/log
import gleam/int
import gleam/string
import gleam/result
import gleam/erlang/os
import gleam/http/elli
import gleam/option
import gleam/pgo
pub fn main() {
log.configure_backend()
let port = load_port()
let application_secret = load_application_secret()
let db = start_database_connection_pool()
let web = routes.stack(application_secret, db)
let log_string =
string.concat(["Listening on localhost:", int.to_string(port), " ✨"])
log.info(log_string)
assert Ok(_) = elli.become(web, on_port: port)
}
pub fn start_database_connection_pool() -> pgo.Connection {
let config =
os.get_env("DATABASE_URL")
|> result.then(pgo.url_config)
|> result.lazy_unwrap(fn() {
pgo.Config(
..pgo.default_config(),
host: "0.0.0.0",
database: "gleam_todomvc_dev",
user: "postgres",
password: option.Some("postgres"),
)
})
pgo.connect(pgo.Config(..config, pool_size: 15))
}
fn load_application_secret() -> String {
os.get_env("APPLICATION_SECRET")
|> result.unwrap("27434b28994f498182d459335258fb6e")
}
fn load_port() -> Int {
os.get_env("PORT")
|> result.then(int.parse)
|> result.unwrap(8080)
}

@ -0,0 +1,51 @@
import todomvc/web/routes
import todomvc/log
import gleam/int
import gleam/string
import gleam/result
import gleam/erlang/os
import gleam/http/elli
import gleam/option
import gleam/pgo
pub fn main() {
log.configure_backend()
let port = load_port()
let application_secret = load_application_secret()
let db = start_database_connection_pool()
let web = routes.stack(application_secret, db)
string.concat(["Listening on localhost:", int.to_string(port), " ✨"])
|> log.info
assert Ok(_) = elli.become(web, on_port: port)
}
pub fn start_database_connection_pool() -> pgo.Connection {
let config =
os.get_env("DATABASE_URL")
|> result.then(pgo.url_config)
|> result.lazy_unwrap(fn() {
pgo.Config(
..pgo.default_config(),
host: "localhost",
database: "gleam_todomvc_dev",
user: "postgres",
password: option.Some("postgres"),
)
})
pgo.connect(pgo.Config(..config, pool_size: 15))
}
fn load_application_secret() -> String {
os.get_env("APPLICATION_SECRET")
|> result.unwrap("27434b28994f498182d459335258fb6e")
}
fn load_port() -> Int {
os.get_env("PORT")
|> result.then(int.parse)
|> result.unwrap(3000)
}

@ -28,6 +28,7 @@ pub enum Language {
Dart,
Elixir,
EmacsLisp,
Gleam,
Go,
Haskell,
JanetSimple,
@ -99,6 +100,7 @@ fn from_emacs_mode_header(src: &str) -> Option<Language> {
"c++" => Some(CPlusPlus),
"elixir" => Some(Elixir),
"emacs-lisp" => Some(EmacsLisp),
"gleam" => Some(Gleam),
"go" => Some(Go),
"haskell" => Some(Haskell),
"janet" => Some(JanetSimple),
@ -195,6 +197,7 @@ pub fn from_extension(extension: &OsStr) -> Option<Language> {
"dart" => Some(Dart),
"el" => Some(EmacsLisp),
"ex" | "exs" => Some(Elixir),
"gleam" => Some(Gleam),
"go" => Some(Go),
"hs" => Some(Haskell),
"janet" | "jdn" => Some(JanetSimple),

@ -53,6 +53,7 @@ extern "C" {
fn tree_sitter_dart() -> ts::Language;
fn tree_sitter_elisp() -> ts::Language;
fn tree_sitter_elixir() -> ts::Language;
fn tree_sitter_gleam() -> ts::Language;
fn tree_sitter_go() -> ts::Language;
fn tree_sitter_haskell() -> ts::Language;
fn tree_sitter_janet_simple() -> ts::Language;
@ -241,6 +242,20 @@ pub fn from_language(language: guess::Language) -> TreeSitterConfig {
.unwrap(),
}
}
Gleam => {
let language = unsafe { tree_sitter_gleam() };
TreeSitterConfig {
name: "Gleam",
language,
atom_nodes: ["string"].into(),
delimiter_tokens: vec![("(", ")"), ("[", "]"), ("{", "}")],
highlight_query: ts::Query::new(
language,
include_str!("../vendor/highlights/gleam.scm"),
)
.unwrap(),
}
}
Go => {
let language = unsafe { tree_sitter_go() };
TreeSitterConfig {

@ -0,0 +1 @@
../tree-sitter-gleam/queries/highlights.scm

@ -0,0 +1 @@
tree-sitter-gleam/src

@ -0,0 +1,2 @@
src/** linguist-generated
test/** linguist-documentation

@ -0,0 +1,37 @@
name: CI
on: [push, pull_request]
jobs:
bless:
name: Test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install Node
uses: actions/setup-node@v2
with:
node-version: "14.x"
- name: Cache npm dependencies
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install npm dependencies
run: npm ci
- name: Ensure generated parser files are up to date
run: npx tree-sitter generate
- name: Run tree-sitter tests
run: npx tree-sitter test
- name: Check formatting
run: npm run check-formatted

@ -0,0 +1,43 @@
# generates the parser with 'tree-sitter generate' if the parser is out of date
name: Generate Parser
on:
push:
branches:
- main
jobs:
generate:
name: Generate Parser
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install Node
uses: actions/setup-node@v2
with:
node-version: "14.x"
- name: Cache npm dependencies
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install npm dependencies
run: npm ci
- name: Generate parser files
run: npx tree-sitter generate
- name: Commit generated parser files
run: |
git config --local user.email "$(git log --format='%ae' HEAD^!)"
git config --local user.name "$(git log --format='%an' HEAD^!)"
git add src
git commit -m "Generate parser" || true
git push

@ -0,0 +1,58 @@
name: release
on:
push:
tags:
- "v*"
jobs:
build-release:
name: build-release
runs-on: ${{ matrix.os }}
strategy:
matrix:
os:
- macos-latest
- ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Install Node
uses: actions/setup-node@v2
with:
node-version: "14.x"
- name: Install npm dependencies
run: npm ci
- name: Ensure generated parser files are up to date
run: npx tree-sitter generate
- name: Ensure tests pass
run: npx tree-sitter test
- name: Compile library file
run: cc -shared -fPIC -g -O2 -I src src/parser.c -o tree-sitter-gleam.so
- name: Create archive
run: |
VERSION="${GITHUB_REF#refs/tags/}"
case ${{ matrix.os }} in
"ubuntu-latest") FAMILY="linux";;
"macos-latest") FAMILY="macos";;
esac
ARCHIVE="tree-sitter-gleam-$VERSION-$FAMILY.tar.gz"
tar -czf $ARCHIVE tree-sitter-gleam.so
echo "ASSET=$ARCHIVE" >> $GITHUB_ENV
- name: Upload release archive
uses: softprops/action-gh-release@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
draft: true
prerelease: false
fail_on_unmatched_files: true
files: |
${{ env.ASSET }}

@ -0,0 +1,5 @@
node_modules
target
build
*.wasm
log.html

@ -0,0 +1,59 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "cc"
version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "tree-sitter"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c36be3222512d85a112491ae0cc280a38076022414f00b64582da1b7565ffd82"
dependencies = [
"cc",
"regex",
]
[[package]]
name = "tree-sitter-gleam"
version = "0.20.1"
dependencies = [
"cc",
"tree-sitter",
]

@ -0,0 +1,26 @@
[package]
name = "tree-sitter-gleam"
description = "gleam grammar for the tree-sitter parsing library"
version = "0.20.1"
keywords = ["incremental", "parsing", "gleam"]
categories = ["parsing", "text-editors"]
repository = "https://github.com/tree-sitter/tree-sitter-gleam"
edition = "2018"
license = "Apache-2.0"
build = "bindings/rust/build.rs"
include = [
"bindings/rust/*",
"grammar.js",
"queries/*",
"src/*",
]
[lib]
path = "bindings/rust/lib.rs"
[dependencies]
tree-sitter = "~0.20"
[build-dependencies]
cc = "1.0"

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

@ -0,0 +1,78 @@
tree-sitter-gleam
=================
A [tree-sitter] grammar for the [Gleam programming language]
[tree-sitter]: https://tree-sitter.github.io
[Gleam programming language]: https://gleam.run
This grammar is able to parse the entire Gleam language. It is largely based
on the Gleam parser itself, and deviations from that are noted throughout the
codebase.
Usage
-----
tree-sitter-gleam, as with all tree-sitter grammars, is of limited utility on
its own. Instead, tree-sitter-gleam is best used as a Gleam parser that can be
embedded in other projects. An example of such a project is
[tree-sitter-gleam-rust-example].
However, [tree-sitter-cli] can be used with this grammar to show generated parse
trees and syntax highlighting for a given Gleam file.
1. [Install tree-sitter-cli]
2. Create a `tree-sitters` directory in your home directory.
3. Clone this repository (or symlink it) into the new `~/tree-sitters/` directory.
4. Run `tree-sitter parse path/to/file.gleam` to be shown the parse tree for the
file.
5. Run `tree-sitter highlight path/to/file.gleam` to be shown the file with
syntax highlighting applied.
[tree-sitter-gleam-rust-example]: https://github.com/J3RN/tree-sitter-gleam-rust-example
[tree-sitter-cli]: https://github.com/tree-sitter/tree-sitter/blob/master/cli/README.md
[Install tree-sitter-cli]: https://github.com/tree-sitter/tree-sitter/blob/master/cli/README.md#installation
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
includes parameters, e.g. `MyType(foo, bar)`.
- `type_identifier` :: Known in the parser as "UpName", this is what you would
intuitively think of as a type's name, such as `List` or `Result`.
- `function_call` :: The name is not confusing, but its structure may be. Since
Gleam supports first-class functions, the function being invoked could be a
variable, a field of a record, an element of a tuple, etc. Some of these are
ambiguous without context that tree-sitter does not have. e.g.
In `string.replace(x, y, z)`, `string` could be a record with a field
`replace` that is a function or it could be a module with a function `replace`
—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.
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
to add it to this list and send a PR! ✨
To-do List
----------
- [x] Add ability to parse all language constructs
- [x] Syntax highlighting queries
- [ ] Have an issue? Let me know! Please [open an issue] 💁
[open an issue]: https://github.com/J3RN/tree-sitter-gleam/issues/new
Style
-----
To prevent headaches from stylistic differences, I request that you please
follow these style suggestions. 🙏
- 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`

@ -0,0 +1,19 @@
{
"targets": [
{
"target_name": "tree_sitter_gleam_binding",
"include_dirs": [
"<!(node -e \"require('nan')\")",
"src"
],
"sources": [
"bindings/node/binding.cc",
"src/parser.c",
# If your language uses an external scanner, add it here.
],
"cflags_c": [
"-std=c99",
]
}
]
}

@ -0,0 +1,28 @@
#include "tree_sitter/parser.h"
#include <node.h>
#include "nan.h"
using namespace v8;
extern "C" TSLanguage * tree_sitter_gleam();
namespace {
NAN_METHOD(New) {}
void Init(Local<Object> exports, Local<Object> module) {
Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
tpl->SetClassName(Nan::New("Language").ToLocalChecked());
tpl->InstanceTemplate()->SetInternalFieldCount(1);
Local<Function> constructor = Nan::GetFunction(tpl).ToLocalChecked();
Local<Object> instance = constructor->NewInstance(Nan::GetCurrentContext()).ToLocalChecked();
Nan::SetInternalFieldPointer(instance, 0, tree_sitter_gleam());
Nan::Set(instance, Nan::New("name").ToLocalChecked(), Nan::New("gleam").ToLocalChecked());
Nan::Set(module, Nan::New("exports").ToLocalChecked(), instance);
}
NODE_MODULE(tree_sitter_gleam_binding, Init)
} // namespace

@ -0,0 +1,19 @@
try {
module.exports = require("../../build/Release/tree_sitter_gleam_binding");
} catch (error1) {
if (error1.code !== 'MODULE_NOT_FOUND') {
throw error1;
}
try {
module.exports = require("../../build/Debug/tree_sitter_gleam_binding");
} catch (error2) {
if (error2.code !== 'MODULE_NOT_FOUND') {
throw error2;
}
throw error1
}
}
try {
module.exports.nodeTypeInfo = require("../../src/node-types.json");
} catch (_) {}

@ -0,0 +1,40 @@
fn main() {
let src_dir = std::path::Path::new("src");
let mut c_config = cc::Build::new();
c_config.include(&src_dir);
c_config
.flag_if_supported("-Wno-unused-parameter")
.flag_if_supported("-Wno-unused-but-set-variable")
.flag_if_supported("-Wno-trigraphs");
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());
// If your language uses an external scanner written in C++,
// then include this block of code:
/*
let mut cpp_config = cc::Build::new();
cpp_config.cpp(true);
cpp_config.include(&src_dir);
cpp_config
.flag_if_supported("-Wno-unused-parameter")
.flag_if_supported("-Wno-unused-but-set-variable");
let scanner_path = src_dir.join("scanner.cc");
cpp_config.file(&scanner_path);
cpp_config.compile("scanner");
println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap());
*/
}

@ -0,0 +1,52 @@
//! 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");
//! let tree = parser.parse(code, None).unwrap();
//! ```
//!
//! [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html
//! [language func]: fn.language.html
//! [Parser]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Parser.html
//! [tree-sitter]: https://tree-sitter.github.io/
use tree_sitter::Language;
extern "C" {
fn tree_sitter_gleam() -> Language;
}
/// 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 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
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");
#[cfg(test)]
mod tests {
#[test]
fn test_can_load_grammar() {
let mut parser = tree_sitter::Parser::new();
parser
.set_language(super::language())
.expect("Error loading gleam language");
}
}

@ -0,0 +1,740 @@
const NEWLINE = /\r?\n/;
module.exports = grammar({
name: "gleam",
extras: ($) => [
";",
NEWLINE,
/\s/,
$.module_comment,
$.statement_comment,
$.comment,
],
conflicts: ($) => [
[$._maybe_record_expression, $._maybe_tuple_expression],
[
$._maybe_record_expression,
$._maybe_tuple_expression,
$.remote_type_identifier,
],
[$.case_subjects],
[$.source_file],
],
rules: {
/* General rules */
source_file: ($) =>
repeat(choice($.target_group, $._statement, $._expression_seq)),
_statement: ($) =>
choice(
$.import,
$.constant,
$.external_type,
$.external_function,
$.function,
$.type_definition,
$.type_alias
),
/* Comments */
module_comment: ($) => token(seq("////", /.*/)),
statement_comment: ($) => token(seq("///", /.*/)),
comment: ($) => token(seq("//", /.*/)),
/* Target groups */
target_group: ($) =>
seq("if", field("target", $.target), "{", repeat($._statement), "}"),
target: ($) => choice("erlang", "javascript"),
/* Import statements */
import: ($) =>
seq(
"import",
field("module", $.module),
optional(seq(".", field("imports", $.unqualified_imports))),
optional(seq("as", field("alias", $.identifier)))
),
module: ($) => seq($._name, repeat(seq("/", $._name))),
unqualified_imports: ($) =>
seq("{", optional(series_of($.unqualified_import, ",")), "}"),
unqualified_import: ($) =>
choice(
seq(
field("name", $.identifier),
optional(seq("as", field("alias", $.identifier)))
),
seq(
field("name", $.type_identifier),
optional(seq("as", field("alias", $.type_identifier)))
)
),
/* Constant statements */
constant: ($) =>
seq(
optional($.visibility_modifier),
"const",
field("name", $.identifier),
optional($._constant_type_annotation),
"=",
field("value", $._constant_value)
),
_constant_value: ($) =>
choice(
$.string,
$.float,
$.integer,
alias($.constant_tuple, $.tuple),
alias($.constant_list, $.list),
alias($._constant_bit_string, $.bit_string),
alias($.constant_record, $.record)
),
constant_tuple: ($) =>
seq("#", "(", optional(series_of($._constant_value, ",")), ")"),
constant_list: ($) =>
seq("[", optional(series_of($._constant_value, ",")), "]"),
...bit_string_rules("constant", "_constant_value", "integer"),
constant_record: ($) =>
seq(
field("name", choice($.type_identifier, $.remote_type_identifier)),
optional(
field("arguments", alias($.constant_record_arguments, $.arguments))
)
),
constant_record_arguments: ($) =>
seq(
"(",
optional(series_of(alias($.constant_record_argument, $.argument), ",")),
")"
),
constant_record_argument: ($) =>
seq(
optional(seq(field("label", $.label), ":")),
field("value", $._constant_value)
),
/* Special constant types */
// Versions of $._type, $._type_annotation, etc, that have constraints
// specific to constants.
_constant_type: ($) =>
choice(
$.type_hole,
alias($.constant_tuple_type, $.tuple_type),
alias($.constant_type, $.type)
),
_constant_type_annotation: ($) => seq(":", field("type", $._constant_type)),
constant_tuple_type: ($) =>
seq("#", "(", optional(series_of($._constant_type, ",")), ")"),
constant_type: ($) =>
seq(
field("name", choice($.type_identifier, $.remote_type_identifier)),
optional(
field("arguments", alias($.constant_type_arguments, $.type_arguments))
)
),
constant_type_arguments: ($) =>
seq(
"(",
optional(
series_of(alias($.constant_type_argument, $.type_argument), ",")
),
")"
),
constant_type_argument: ($) => $._constant_type,
external_type: ($) =>
seq(optional($.visibility_modifier), "external", "type", $.type_name),
/* External function */
external_function: ($) =>
seq(
optional($.visibility_modifier),
"external",
"fn",
field("name", $.identifier),
field(
"parameters",
alias($.external_function_parameters, $.function_parameters)
),
"->",
field("return_type", $._type),
"=",
field("body", $.external_function_body)
),
// Different from module function parameters in that module function
// parameters may be labeled whereas external function parameters cannot.
external_function_parameters: ($) =>
seq(
"(",
optional(
series_of(
alias($.external_function_parameter, $.function_parameter),
","
)
),
")"
),
external_function_parameter: ($) =>
seq(
optional(seq(field("name", $.identifier), ":")),
field("type", $._type)
),
external_function_body: ($) => seq($.string, $.string),
/* 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)),
"}"
),
function_parameters: ($) =>
seq("(", optional(series_of($.function_parameter, ",")), ")"),
function_parameter: ($) =>
seq(
choice(
$._labeled_discard_param,
$._discard_param,
$._labeled_name_param,
$._name_param
),
optional($._type_annotation)
),
_labeled_discard_param: ($) =>
seq(field("label", $.label), field("name", $.discard)),
_discard_param: ($) => field("name", $.discard),
_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)
),
_expression: ($) => choice($._expression_unit, $.binary_expression),
binary_expression: ($) =>
choice(
binaryExpr(prec.left, 1, "||", $._expression),
binaryExpr(prec.left, 2, "&&", $._expression),
binaryExpr(prec.left, 3, "==", $._expression),
binaryExpr(prec.left, 3, "!=", $._expression),
binaryExpr(prec.left, 4, "<", $._expression),
binaryExpr(prec.left, 4, "<=", $._expression),
binaryExpr(prec.left, 4, "<.", $._expression),
binaryExpr(prec.left, 4, "<=.", $._expression),
binaryExpr(prec.left, 4, ">", $._expression),
binaryExpr(prec.left, 4, ">=", $._expression),
binaryExpr(prec.left, 4, ">.", $._expression),
binaryExpr(prec.left, 4, ">=.", $._expression),
binaryExpr(prec.left, 5, "|>", $._expression),
binaryExpr(prec.left, 6, "+", $._expression),
binaryExpr(prec.left, 6, "+.", $._expression),
binaryExpr(prec.left, 6, "-", $._expression),
binaryExpr(prec.left, 6, "-.", $._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,
// then potentially parses tuple access, field access, record updates, or
// function calls as an extension of that base expression that was
// previously parsed. tree-sitter provides no facility to amend a node that
// was already successfully parsed. Therefore, tuple access, field access,
// record updates, and function calls must be parsed as alternatives to the
// expressions they build upon rather than extensions thereof.
_expression_unit: ($) =>
choice(
$.string,
$.integer,
$.float,
// If we decide that record constructors (value constructors) are
// actually functions, this will require a refactor.
$.record,
$.identifier,
$.todo,
$.tuple,
$.list,
alias($._expression_bit_string, $.bit_string),
$.anonymous_function,
$.expression_group,
$.case,
$.let,
$.assert,
$.record_update,
$.tuple_access,
$.field_access,
$.function_call
),
record: ($) =>
seq(
field("name", choice($.type_identifier, $.remote_type_identifier)),
optional(field("arguments", $.arguments))
),
todo: ($) =>
seq("todo", optional(seq("(", field("message", $.string), ")"))),
tuple: ($) => seq("#", "(", optional(series_of($._expression, ",")), ")"),
list: ($) =>
seq(
"[",
optional(
seq(
$._expression,
optional(repeat(seq(",", $._expression))),
optional(","),
optional(seq("..", field("spread", $._expression)))
)
),
"]"
),
...bit_string_rules("expression", "_expression_unit", "_expression"),
anonymous_function: ($) =>
seq(
"fn",
field("parameters", $.function_parameters),
optional(seq("->", field("return_type", $._type))),
"{",
field("body", alias($._expression_seq, $.function_body)),
"}"
),
expression_group: ($) => seq("{", $._expression_seq, "}"),
case: ($) =>
seq(
"case",
field("subjects", $.case_subjects),
"{",
field("clauses", $.case_clauses),
"}"
),
case_subjects: ($) => seq(series_of($._expression, ",")),
case_clauses: ($) => repeat1($.case_clause),
case_clause: ($) =>
seq(
field("patterns", $.case_clause_patterns),
optional(field("guard", $.case_clause_guard)),
"->",
field("value", $._expression)
),
// Technically the Gleam parser does support something like this:
// 1 | | 5 -> True
// However, that will cause an error further into the compiler. That format
// is not supported by this function.
case_clause_patterns: ($) => seq(series_of($.case_clause_pattern, "|")),
// The issue above comes from the fact that the parser equivalent of this
// function supports 0 patterns. This function does not.
case_clause_pattern: ($) => series_of($._pattern, ","),
case_clause_guard: ($) => seq("if", $._case_clause_guard_expression),
_case_clause_guard_expression: ($) =>
choice(
$._case_clause_guard_unit,
alias($._case_clause_guard_binary_expression, $.binary_expression)
),
_case_clause_guard_binary_expression: ($) =>
choice(
binaryExpr(prec.left, 1, "||", $._case_clause_guard_expression),
binaryExpr(prec.left, 2, "&&", $._case_clause_guard_expression),
binaryExpr(prec.left, 3, "==", $._case_clause_guard_expression),
binaryExpr(prec.left, 3, "!=", $._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, 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)
),
_case_clause_guard_unit: ($) =>
choice(
$.identifier,
prec(1, alias($._case_clause_tuple_access, $.tuple_access)),
seq("{", $._case_clause_guard_expression, "}"),
$._constant_value
),
_case_clause_tuple_access: ($) =>
seq(field("tuple", $.identifier), ".", field("index", $.integer)),
let: ($) => seq("let", $._assignment),
assert: ($) => seq("assert", $._assignment),
_assignment: ($) =>
seq(
field("pattern", $._pattern),
optional($._type_annotation),
"=",
field("value", $._expression)
),
record_update: ($) =>
seq(
field(
"constructor",
choice($.type_identifier, $.remote_type_identifier)
),
"(",
"..",
field("spread", $._expression),
",",
field("arguments", $.record_update_arguments),
")"
),
record_update_arguments: ($) => series_of($.record_update_argument, ","),
record_update_argument: ($) =>
seq(field("label", $.label), ":", field("value", $._expression)),
// 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
// expression whose return type could be a record, tuple, or function,
// respectively.
//
// `let` and `assert` are exempted because in order to parse correctly,
// they would have to be wrapped in an expression group anyways.
_maybe_tuple_expression: ($) =>
choice(
$.identifier,
$.function_call,
$.tuple,
$.expression_group,
$.case,
$.field_access,
$.tuple_access
),
tuple_access: ($) =>
prec.left(
seq(
field("tuple", $._maybe_tuple_expression),
".",
field("index", $.integer)
)
),
_maybe_record_expression: ($) =>
choice(
$.record,
$.identifier,
$.function_call,
$.expression_group,
$.case,
$.record_update,
$.field_access,
$.tuple_access
),
field_access: ($) =>
prec.left(
seq(
field("record", $._maybe_record_expression),
".",
field("field", $.label)
)
),
// Remote functions (e.g. int.to_string) is parsed as a field access
// (accessing field to_string on record int) as it is impossible for the
// parser to determine with int is a module (and thus to_string a member
// function) or a local variable containing a record (and thus to_string is
// a field of that record).
// Similarly, the function name in local function calls (e.g. foo(arg)) is
// parsed as an $.identifier which is used to refer to both local variables
// and module functions, embodying the same ambiguity.
_maybe_function_expression: ($) =>
choice(
$.identifier,
$.anonymous_function,
$.expression_group,
$.case,
$.tuple_access,
$.field_access,
$.function_call
),
// Interestingly, the code that parses function arguments also parses
// record arguments, hence the ambiguous name.
arguments: ($) => seq("(", optional(series_of($.argument, ",")), ")"),
argument: ($) =>
seq(
optional(seq(field("label", $.label), ":")),
field("value", choice($.hole, $._expression))
),
hole: ($) => $._discard_name,
function_call: ($) =>
seq(
field("function", $._maybe_function_expression),
field("arguments", $.arguments)
),
_pattern: ($) =>
seq(
choice(
$.identifier,
$.discard,
$.record_pattern,
$.string,
$.integer,
$.float,
$.tuple_pattern,
alias($._pattern_bit_string, $.bit_string_pattern),
$.list_pattern
),
optional(field("assign", seq("as", $.identifier)))
),
record_pattern: ($) =>
seq(
field("name", choice($.type_identifier, $.remote_type_identifier)),
optional(field("arguments", $.record_pattern_arguments))
),
record_pattern_arguments: ($) =>
seq(
"(",
optional(series_of($.record_pattern_argument, ",")),
optional($.pattern_spread),
")"
),
record_pattern_argument: ($) =>
seq(
optional(seq(field("label", $.label), ":")),
field("pattern", $._pattern)
),
pattern_spread: ($) => seq("..", optional(",")),
tuple_pattern: ($) =>
seq("#", "(", optional(series_of($._pattern, ",")), ")"),
// The Gleam parser has a special catch for nested bitstrings here, which
// is interesting as the same error does not exist on constants. Anyhow, I
// wasn't really sure how to implement that easily here, and so didn't.
...bit_string_rules(
"pattern",
"_pattern",
"_pattern_bit_string_segment_argument"
),
_pattern_bit_string_segment_argument: ($) =>
choice($.identifier, $.integer),
list_pattern: ($) =>
seq(
"[",
optional(series_of($._pattern, ",")),
optional($.list_pattern_tail),
"]"
),
list_pattern_tail: ($) =>
seq("..", optional(choice($.identifier, $.discard))),
visibility_modifier: ($) => "pub",
opacity_modifier: ($) => "opaque",
/* Custom type definitions */
type_definition: ($) =>
seq(
optional($.visibility_modifier),
optional($.opacity_modifier),
"type",
$.type_name,
"{",
$.data_constructors,
"}"
),
data_constructors: ($) => repeat1($.data_constructor),
data_constructor: ($) =>
seq(
field("name", $.type_identifier),
optional(field("arguments", $.data_constructor_arguments))
),
data_constructor_arguments: ($) =>
seq("(", optional(series_of($.data_constructor_argument, ",")), ")"),
data_constructor_argument: ($) =>
seq(optional(seq(field("label", $.label), ":")), field("value", $._type)),
/* Type aliases */
type_alias: ($) =>
seq(
optional($.visibility_modifier),
optional($.opacity_modifier),
"type",
$.type_name,
"=",
$._type
),
/* Literals */
string: ($) => seq('"', repeat($._string_part), '"'),
_string_part: ($) => choice($.escape_sequence, $.quoted_content),
escape_sequence: ($) => /\\[efnrt\"\\]/,
quoted_content: ($) => /(?:[^\\\"]|\\[^efnrt\"\\])+/,
float: ($) => /-?[0-9_]+\.[0-9_]+/,
integer: ($) =>
seq(optional("-"), choice($._hex, $._decimal, $._octal, $._binary)),
_hex: ($) => /0[xX][0-9a-fA-F_]+/,
_decimal: ($) => /[0-9][0-9_]*/,
_octal: ($) => /0[oO][0-7_]+/,
_binary: ($) => /0[bB][0-1_]+/,
_bit_string_segment_option: ($) =>
choice(
"binary",
"bytes",
"int",
"float",
"bit_string",
"bits",
"utf8",
"utf16",
"utf32",
"utf8_codepoint",
"utf16_codepoint",
"utf32_codepoint",
"signed",
"unsigned",
"big",
"little",
"native",
seq("unit", "(", $.integer, ")")
),
/* Types */
_type: ($) =>
choice($.type_hole, $.tuple_type, $.function_type, $.type, $.type_var),
_type_annotation: ($) => seq(":", field("type", $._type)),
type_hole: ($) => $._discard_name,
// If you're wondering why there isn't a `list_type` here, the answer is
// that the "type" form for lists is `List`, which is identical to
// user-defined types etc and thus is not parsed specially.
tuple_type: ($) => seq("#", "(", optional(series_of($._type, ",")), ")"),
function_type: ($) =>
seq(
"fn",
optional(field("parameter_types", $.function_parameter_types)),
"->",
field("return_type", $._type)
),
function_parameter_types: ($) =>
seq("(", optional(series_of($._type, ",")), ")"),
// "type" is a somewhat ambiguous name, but it refers to a concrete type
// such as `Bool` or `List(Int)` or even `result.Result(#(Int, Int), Nil)`.
type: ($) =>
seq(
field("name", choice($.type_identifier, $.remote_type_identifier)),
optional(field("arguments", $.type_arguments))
),
type_arguments: ($) =>
seq("(", optional(series_of($.type_argument, ",")), ")"),
type_argument: ($) => $._type,
type_var: ($) => $._name,
// "type_name" essentially refers to the declaration of a type. The type
// parameters are part of the "name." Bit odd, but 🤷
// e.g. MyType(a, b)
type_name: ($) =>
seq(
field("name", choice($.type_identifier, $.remote_type_identifier)),
optional(field("parameters", $.type_parameters))
),
type_parameters: ($) =>
seq("(", optional(series_of($.type_parameter, ",")), ")"),
type_parameter: ($) => $._name,
/* Shared AST nodes */
identifier: ($) => $._name,
label: ($) => $._name,
discard: ($) => $._discard_name,
type_identifier: ($) => $._upname,
remote_type_identifier: ($) =>
seq(field("module", $.identifier), ".", field("name", $.type_identifier)),
/* Reused types from the Gleam lexer */
_discard_name: ($) => /_[_0-9a-z]*/,
_name: ($) => /[_a-z][_0-9a-z]*/,
_upname: ($) => /[A-Z][0-9a-zA-Z]*/,
},
});
// This function and the following function are vaguely congruent with the
// `parse_bit_string_segment` function of the Gleam parser.
function bit_string_rules(name, value_parser, arg_parser) {
return {
[`_${name}_bit_string`]: ($) =>
seq(
"<<",
optional(
series_of(
alias($[`${name}_bit_string_segment`], $.bit_string_segment),
","
)
),
">>"
),
[`${name}_bit_string_segment`]: ($) =>
seq(
field("value", $[value_parser]),
optional(
field(
"options",
seq(
":",
alias(
$[`${name}_bit_string_segment_options`],
$.bit_string_segment_options
)
)
)
)
),
...bit_string_segment_options(name, arg_parser),
};
}
function bit_string_segment_options(name, arg_parser) {
return {
[`${name}_bit_string_segment_options`]: ($) =>
series_of($[`_${name}_bit_string_segment_option`], "-"),
[`_${name}_bit_string_segment_option`]: ($) =>
choice($[`_${name}_bit_string_named_segment_option`], $.integer),
[`_${name}_bit_string_named_segment_option`]: ($) =>
alias(
choice(
$._bit_string_segment_option,
$[`_${name}_bit_string_segment_option_size`]
),
$.bit_string_segment_option
),
[`_${name}_bit_string_segment_option_size`]: ($) =>
seq("size", "(", $[arg_parser], ")"),
};
}
// Shamelessly stolen "sep1" from tree-sitter-elixir, renamed to match a similar
// function in the Gleam parser.
// https://github.com/elixir-lang/tree-sitter-elixir/blob/de3ec57591aebf451e710fc9c984cf601258baf5/grammar.js#L817-L819
function series_of(rule, separator) {
return seq(rule, repeat(seq(separator, rule)), optional(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) {
return assoc(
precedence,
seq(field("left", expr), field("operator", operator), field("right", expr))
);
}

@ -0,0 +1,66 @@
{
"name": "tree-sitter-gleam",
"version": "0.20.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "tree-sitter-gleam",
"version": "0.20.1",
"license": "Apache-2.0",
"dependencies": {
"nan": "^2.15.0"
},
"devDependencies": {
"prettier": "^2.5.1",
"tree-sitter-cli": "^0.20.6"
}
},
"node_modules/nan": {
"version": "2.15.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
"integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ=="
},
"node_modules/prettier": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz",
"integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/tree-sitter-cli": {
"version": "0.20.6",
"resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.20.6.tgz",
"integrity": "sha512-tjbAeuGSMhco/EnsThjWkQbDIYMDmdkWsTPsa/NJAW7bjaki9P7oM9TkLxfdlnm4LXd1wR5wVSM2/RTLtZbm6A==",
"dev": true,
"hasInstallScript": true,
"bin": {
"tree-sitter": "cli.js"
}
}
},
"dependencies": {
"nan": {
"version": "2.15.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
"integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ=="
},
"prettier": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz",
"integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==",
"dev": true
},
"tree-sitter-cli": {
"version": "0.20.6",
"resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.20.6.tgz",
"integrity": "sha512-tjbAeuGSMhco/EnsThjWkQbDIYMDmdkWsTPsa/NJAW7bjaki9P7oM9TkLxfdlnm4LXd1wR5wVSM2/RTLtZbm6A==",
"dev": true
}
}
}

@ -0,0 +1,45 @@
{
"name": "tree-sitter-gleam",
"version": "0.20.1",
"description": "A tree-sitter grammar for the Gleam programming language",
"main": "bindings/node",
"scripts": {
"generate": "tree-sitter generate",
"test": "tree-sitter test",
"format": "prettier --write grammar.js",
"check-formatted": "prettier --check grammar.js",
"build-wasm": "tree-sitter build-wasm"
},
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/J3RN/tree-sitter-gleam.git"
},
"keywords": [
"gleam",
"parser",
"lexer",
"tree-sitter"
],
"author": "Jonathan Arnett",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/J3RN/tree-sitter-gleam/issues"
},
"homepage": "https://github.com/J3RN/tree-sitter-gleam#readme",
"dependencies": {
"nan": "^2.15.0"
},
"devDependencies": {
"prettier": "^2.5.1",
"tree-sitter-cli": "^0.20.6"
},
"tree-sitter": [
{
"scope": "source.gleam",
"file-types": [
"gleam"
],
"injection-regex": "^gleam$"
}
]
}

@ -0,0 +1,101 @@
; Comments
(module_comment) @comment
(statement_comment) @comment
(comment) @comment
; Constants
(constant
name: (identifier) @constant)
; Modules
(module) @module
(import alias: (identifier) @module)
(remote_type_identifier
module: (identifier) @module)
((field_access
record: (identifier) @module
field: (label) @function)
(#is-not? local))
; Functions
(unqualified_import (identifier) @function)
(function
name: (identifier) @function)
(external_function
name: (identifier) @function)
(function_parameter
name: (identifier) @variable.parameter)
((function_call
function: (identifier) @function)
(#is-not? local))
((binary_expression
operator: "|>"
right: (identifier) @function)
(#is-not? local))
; "Properties"
; Assumed to be intended to refer to a name for a field; something that comes
; before ":" or after "."
; e.g. record field names, tuple indices, names for named arguments, etc
(label) @property
(tuple_access
index: (integer) @property)
; Type names
(remote_type_identifier) @type
(type_identifier) @type
; Literals
(string) @string
(bit_string_segment_option) @function.builtin
(integer) @number
(float) @number
; Variables
(identifier) @variable
(discard) @comment.unused
; Operators
(binary_expression
operator: _ @operator)
; Keywords
[
(visibility_modifier) ; "pub"
(opacity_modifier) ; "opaque"
"as"
"assert"
"case"
"const"
"external"
"fn"
"if"
"import"
"let"
"todo"
"try"
"type"
] @keyword
; Punctuation
[
"("
")"
"["
"]"
"{"
"}"
"<<"
">>"
] @punctuation.bracket
[
"."
","
;; Controversial -- maybe some are operators?
":"
"#"
"="
"->"
".."
"-"
] @punctuation.delimiter

@ -0,0 +1,15 @@
; Scopes
(function_body) @local.scope
(case_clause) @local.scope
; Definitions
(let pattern: (identifier) @local.definition)
(function_parameter name: (identifier) @local.definition)
(list_pattern (identifier) @local.definition)
(list_pattern assign: (identifier) @local.definition)
(tuple_pattern (identifier) @local.definition)
(record_pattern_argument pattern: (identifier) @local.definition)
; References
(identifier) @local.reference

@ -0,0 +1,40 @@
; Modules
(module) @name @reference.module
(import alias: (identifier) @name) @reference.module
(remote_type_identifier
module: (identifier) @name) @reference.module
((field_access
record: (identifier) @name)
(#is-not? local)) @reference.module
; Functions
(function
name: (identifier) @name) @definition.function
(external_function
name: (identifier) @name) @definition.function
(unqualified_import (identifier) @name) @reference.function
((function_call
function: (identifier) @name) @reference.function
(#is-not? local))
((field_access
record: (identifier) @ignore
field: (label) @name)
(#is-not? local)) @reference.function
((binary_expression
operator: "|>"
right: (identifier) @name)
(#is-not? local)) @reference.function
; Types
(type_definition
(type_name
name: (type_identifier) @name)) @definition.type
(type_definition
(data_constructors
(data_constructor
name: (type_identifier) @name))) @definition.type
(external_type
(type_name
name: (type_identifier) @name)) @definition.type
(type_identifier) @name @reference.type

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

51512
vendor/tree-sitter-gleam/src/parser.c generated vendored

File diff suppressed because it is too large Load Diff

@ -0,0 +1,224 @@
#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
typedef uint16_t TSStateId;
#ifndef TREE_SITTER_API_H_
typedef uint16_t TSSymbol;
typedef uint16_t TSFieldId;
typedef struct TSLanguage TSLanguage;
#endif
typedef struct {
TSFieldId field_id;
uint8_t child_index;
bool inherited;
} TSFieldMapEntry;
typedef struct {
uint16_t index;
uint16_t length;
} TSFieldMapSlice;
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 *);
};
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 union {
TSParseAction action;
struct {
uint8_t count;
bool reusable;
} entry;
} TSParseActionEntry;
struct TSLanguage {
uint32_t 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 TSFieldMapSlice *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 TSLexMode *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;
};
/*
* Lexer Macros
*/
#define START_LEXER() \
bool result = false; \
bool skip = false; \
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 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_val, child_count_val, ...) \
{{ \
.reduce = { \
.type = TSParseActionTypeReduce, \
.symbol = symbol_val, \
.child_count = child_count_val, \
__VA_ARGS__ \
}, \
}}
#define RECOVER() \
{{ \
.type = TSParseActionTypeRecover \
}}
#define ACCEPT_INPUT() \
{{ \
.type = TSParseActionTypeAccept \
}}
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_PARSER_H_

@ -0,0 +1,382 @@
================================================================================
Constants
================================================================================
const a = "hello"
const a = "hello\nworld\!"
const a = 1_234
const a = 0b110
const a = 0o7224
const a = 0xBEef
const a: Int = 1234
const a: Float = -1_234.53__23
const a: #(Int, String) = #(1, "Hello!")
const a: #() = #()
const a: List(Int) = [1, 2]
const a: List(_) = []
const a = <<3>>
const a = <<0:4, 1:3, 1:1>>
const a = <<3:size(8)>>
const a = <<52:size(4)-unit(4)>>
const a = <<"Hello Gleam 💫":utf8>>
const a = Node
const a = Node()
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!")
--------------------------------------------------------------------------------
(source_file
(constant
name: (identifier)
value: (string
(quoted_content)))
(constant
name: (identifier)
value: (string
(quoted_content)
(escape_sequence)
(quoted_content)))
(constant
name: (identifier)
value: (integer))
(constant
name: (identifier)
value: (integer))
(constant
name: (identifier)
value: (integer))
(constant
name: (identifier)
value: (integer))
(constant
name: (identifier)
type: (type
name: (type_identifier))
value: (integer))
(constant
name: (identifier)
type: (type
name: (type_identifier))
value: (float))
(constant
name: (identifier)
type: (tuple_type
(type
name: (type_identifier))
(type
name: (type_identifier)))
value: (tuple
(integer)
(string
(quoted_content))))
(constant
name: (identifier)
type: (tuple_type)
value: (tuple))
(constant
name: (identifier)
type: (type
name: (type_identifier)
arguments: (type_arguments
(type_argument
(type
name: (type_identifier)))))
value: (list
(integer)
(integer)))
(constant
name: (identifier)
type: (type
name: (type_identifier)
arguments: (type_arguments
(type_argument
(type_hole))))
value: (list))
(constant
name: (identifier)
value: (bit_string
(bit_string_segment
value: (integer))))
(constant
name: (identifier)
value: (bit_string
(bit_string_segment
value: (integer)
options: (bit_string_segment_options
(integer)))
(bit_string_segment
value: (integer)
options: (bit_string_segment_options
(integer)))
(bit_string_segment
value: (integer)
options: (bit_string_segment_options
(integer)))))
(constant
name: (identifier)
value: (bit_string
(bit_string_segment
value: (integer)
options: (bit_string_segment_options
(bit_string_segment_option
(integer))))))
(constant
name: (identifier)
value: (bit_string
(bit_string_segment
value: (integer)
options: (bit_string_segment_options
(bit_string_segment_option
(integer))
(bit_string_segment_option
(integer))))))
(constant
name: (identifier)
value: (bit_string
(bit_string_segment
value: (string
(quoted_content))
options: (bit_string_segment_options
(bit_string_segment_option)))))
(constant
name: (identifier)
value: (record
name: (type_identifier)))
(constant
name: (identifier)
value: (record
name: (type_identifier)
arguments: (arguments)))
(constant
name: (identifier)
value: (record
name: (type_identifier)
arguments: (arguments
(argument
value: (string
(quoted_content)))
(argument
value: (integer)))))
(constant
name: (identifier)
value: (record
name: (type_identifier)
arguments: (arguments
(argument
label: (label)
value: (string
(quoted_content)))
(argument
label: (label)
value: (integer)))))
(constant
name: (identifier)
value: (record
name: (remote_type_identifier
module: (identifier)
name: (type_identifier))
arguments: (arguments
(argument
label: (label)
value: (string
(quoted_content))))))
(constant
name: (identifier)
type: (type
name: (remote_type_identifier
module: (identifier)
name: (type_identifier))
arguments: (type_arguments
(type_argument
(type
name: (type_identifier)))))
value: (record
name: (remote_type_identifier
module: (identifier)
name: (type_identifier))
arguments: (arguments
(argument
value: (string
(quoted_content)))))))
================================================================================
Public constants
================================================================================
pub const a = "hello"
pub const a: Int = 1234
pub const a: Float = -1_234.53__23
pub const a: #(Int, String) = #(1, "Hello!")
pub const a: #() = #()
pub const a: List(Int) = [1, 2]
pub const a: List(_) = []
pub const a = <<3>>
pub const a = <<0:4, 1:3, 1:1>>
pub const a = <<3:size(8)>>
pub const a = <<52:size(4)-unit(4)>>
pub const a = <<"Hello Gleam 💫":utf8>>
pub const a = Node
pub const a = Node()
pub const a = Cat("Ginny", 1950)
pub const a = Person(name: "Billy", age: 52)
pub const a = uri.Uri(host: "github.com")
--------------------------------------------------------------------------------
(source_file
(constant
(visibility_modifier)
name: (identifier)
value: (string
(quoted_content)))
(constant
(visibility_modifier)
name: (identifier)
type: (type
name: (type_identifier))
value: (integer))
(constant
(visibility_modifier)
name: (identifier)
type: (type
name: (type_identifier))
value: (float))
(constant
(visibility_modifier)
name: (identifier)
type: (tuple_type
(type
name: (type_identifier))
(type
name: (type_identifier)))
value: (tuple
(integer)
(string
(quoted_content))))
(constant
(visibility_modifier)
name: (identifier)
type: (tuple_type)
value: (tuple))
(constant
(visibility_modifier)
name: (identifier)
type: (type
name: (type_identifier)
arguments: (type_arguments
(type_argument
(type
name: (type_identifier)))))
value: (list
(integer)
(integer)))
(constant
(visibility_modifier)
name: (identifier)
type: (type
name: (type_identifier)
arguments: (type_arguments
(type_argument
(type_hole))))
value: (list))
(constant
(visibility_modifier)
name: (identifier)
value: (bit_string
(bit_string_segment
value: (integer))))
(constant
(visibility_modifier)
name: (identifier)
value: (bit_string
(bit_string_segment
value: (integer)
options: (bit_string_segment_options
(integer)))
(bit_string_segment
value: (integer)
options: (bit_string_segment_options
(integer)))
(bit_string_segment
value: (integer)
options: (bit_string_segment_options
(integer)))))
(constant
(visibility_modifier)
name: (identifier)
value: (bit_string
(bit_string_segment
value: (integer)
options: (bit_string_segment_options
(bit_string_segment_option
(integer))))))
(constant
(visibility_modifier)
name: (identifier)
value: (bit_string
(bit_string_segment
value: (integer)
options: (bit_string_segment_options
(bit_string_segment_option
(integer))
(bit_string_segment_option
(integer))))))
(constant
(visibility_modifier)
name: (identifier)
value: (bit_string
(bit_string_segment
value: (string
(quoted_content))
options: (bit_string_segment_options
(bit_string_segment_option)))))
(constant
(visibility_modifier)
name: (identifier)
value: (record
name: (type_identifier)))
(constant
(visibility_modifier)
name: (identifier)
value: (record
name: (type_identifier)
arguments: (arguments)))
(constant
(visibility_modifier)
name: (identifier)
value: (record
name: (type_identifier)
arguments: (arguments
(argument
value: (string
(quoted_content)))
(argument
value: (integer)))))
(constant
(visibility_modifier)
name: (identifier)
value: (record
name: (type_identifier)
arguments: (arguments
(argument
label: (label)
value: (string
(quoted_content)))
(argument
label: (label)
value: (integer)))))
(constant
(visibility_modifier)
name: (identifier)
value: (record
name: (remote_type_identifier
module: (identifier)
name: (type_identifier))
arguments: (arguments
(argument
label: (label)
value: (string
(quoted_content)))))))

@ -0,0 +1,249 @@
================================================================================
Parser example custom types
================================================================================
type A { A }
type A { A(String) }
type Box(inner_type) { Box(inner: inner_type) }
type NamedBox(inner_type) { Box(String, inner: inner_type) }
--------------------------------------------------------------------------------
(source_file
(type_definition
(type_name
name: (type_identifier))
(data_constructors
(data_constructor
name: (type_identifier))))
(type_definition
(type_name
name: (type_identifier))
(data_constructors
(data_constructor
name: (type_identifier)
arguments: (data_constructor_arguments
(data_constructor_argument
value: (type
name: (type_identifier)))))))
(type_definition
(type_name
name: (type_identifier)
parameters: (type_parameters
(type_parameter)))
(data_constructors
(data_constructor
name: (type_identifier)
arguments: (data_constructor_arguments
(data_constructor_argument
label: (label)
value: (type_var))))))
(type_definition
(type_name
name: (type_identifier)
parameters: (type_parameters
(type_parameter)))
(data_constructors
(data_constructor
name: (type_identifier)
arguments: (data_constructor_arguments
(data_constructor_argument
value: (type
name: (type_identifier)))
(data_constructor_argument
label: (label)
value: (type_var)))))))
================================================================================
Other custom type examples
================================================================================
type Cat {
Cat(name: String, cuteness: Int)
}
type Animal() {
Cat(name: String, cuteness: Int)
Dog(name: String, cuteness: Int)
}
type Result(success_type, failure_type) {
Ok(success_type)
Error(failure_type)
}
type Ord {
LT
EQ
GT
}
type Boring {
Boring
}
--------------------------------------------------------------------------------
(source_file
(type_definition
(type_name
name: (type_identifier))
(data_constructors
(data_constructor
name: (type_identifier)
arguments: (data_constructor_arguments
(data_constructor_argument
label: (label)
value: (type
name: (type_identifier)))
(data_constructor_argument
label: (label)
value: (type
name: (type_identifier)))))))
(type_definition
(type_name
name: (type_identifier)
parameters: (type_parameters))
(data_constructors
(data_constructor
name: (type_identifier)
arguments: (data_constructor_arguments
(data_constructor_argument
label: (label)
value: (type
name: (type_identifier)))
(data_constructor_argument
label: (label)
value: (type
name: (type_identifier)))))
(data_constructor
name: (type_identifier)
arguments: (data_constructor_arguments
(data_constructor_argument
label: (label)
value: (type
name: (type_identifier)))
(data_constructor_argument
label: (label)
value: (type
name: (type_identifier)))))))
(type_definition
(type_name
name: (type_identifier)
parameters: (type_parameters
(type_parameter)
(type_parameter)))
(data_constructors
(data_constructor
name: (type_identifier)
arguments: (data_constructor_arguments
(data_constructor_argument
value: (type_var))))
(data_constructor
name: (type_identifier)
arguments: (data_constructor_arguments
(data_constructor_argument
value: (type_var))))))
(type_definition
(type_name
name: (type_identifier))
(data_constructors
(data_constructor
name: (type_identifier))
(data_constructor
name: (type_identifier))
(data_constructor
name: (type_identifier))))
(type_definition
(type_name
name: (type_identifier))
(data_constructors
(data_constructor
name: (type_identifier)))))
================================================================================
Public custom type definitions
================================================================================
pub type Animal(name, cuteness) {
Cat(name: String, cuteness: Int)
Dog(name: String, cuteness: Int)
}
--------------------------------------------------------------------------------
(source_file
(type_definition
(visibility_modifier)
(type_name
name: (type_identifier)
parameters: (type_parameters
(type_parameter)
(type_parameter)))
(data_constructors
(data_constructor
name: (type_identifier)
arguments: (data_constructor_arguments
(data_constructor_argument
label: (label)
value: (type
name: (type_identifier)))
(data_constructor_argument
label: (label)
value: (type
name: (type_identifier)))))
(data_constructor
name: (type_identifier)
arguments: (data_constructor_arguments
(data_constructor_argument
label: (label)
value: (type
name: (type_identifier)))
(data_constructor_argument
label: (label)
value: (type
name: (type_identifier))))))))
================================================================================
Public opaque custom type definitions
================================================================================
pub opaque type Animal(name, cuteness) {
Cat(name: String, cuteness: Int)
Dog(name: String, cuteness: Int)
}
--------------------------------------------------------------------------------
(source_file
(type_definition
(visibility_modifier)
(opacity_modifier)
(type_name
name: (type_identifier)
parameters: (type_parameters
(type_parameter)
(type_parameter)))
(data_constructors
(data_constructor
name: (type_identifier)
arguments: (data_constructor_arguments
(data_constructor_argument
label: (label)
value: (type
name: (type_identifier)))
(data_constructor_argument
label: (label)
value: (type
name: (type_identifier)))))
(data_constructor
name: (type_identifier)
arguments: (data_constructor_arguments
(data_constructor_argument
label: (label)
value: (type
name: (type_identifier)))
(data_constructor_argument
label: (label)
value: (type
name: (type_identifier))))))))

@ -0,0 +1,50 @@
================================================================================
Case with spread
================================================================================
pub fn main() {
case xs {
[[]] -> io.println("one")
[[], ..] -> io.println("many")
}
}
--------------------------------------------------------------------------------
(source_file
(function
(visibility_modifier)
(identifier)
(function_parameters)
(function_body
(case
(case_subjects
(identifier))
(case_clauses
(case_clause
(case_clause_patterns
(case_clause_pattern
(list_pattern
(list_pattern))))
(function_call
(field_access
(identifier)
(label))
(arguments
(argument
(string
(quoted_content))))))
(case_clause
(case_clause_patterns
(case_clause_pattern
(list_pattern
(list_pattern)
(list_pattern_tail))))
(function_call
(field_access
(identifier)
(label))
(arguments
(argument
(string
(quoted_content)))))))))))

@ -0,0 +1,22 @@
================================================================================
Bit-string expression
================================================================================
<<code:int-size(8)-unit(2), reason:utf8>>
--------------------------------------------------------------------------------
(source_file
(bit_string
(bit_string_segment
(identifier)
(bit_string_segment_options
(bit_string_segment_option)
(bit_string_segment_option
(integer))
(bit_string_segment_option
(integer))))
(bit_string_segment
(identifier)
(bit_string_segment_options
(bit_string_segment_option)))))

@ -0,0 +1,138 @@
================================================================================
External functions
================================================================================
external fn read_file(String) -> Result(String, Reason) =
"file" "open"
external fn a(name: String) -> String = "x" "y"
external fn a() -> #(List(Int), fn(Int) -> String) = "x" "y"
--------------------------------------------------------------------------------
(source_file
(external_function
name: (identifier)
parameters: (function_parameters
(function_parameter
type: (type
name: (type_identifier))))
return_type: (type
name: (type_identifier)
arguments: (type_arguments
(type_argument
(type
name: (type_identifier)))
(type_argument
(type
name: (type_identifier)))))
body: (external_function_body
(string
(quoted_content))
(string
(quoted_content))))
(external_function
name: (identifier)
parameters: (function_parameters
(function_parameter
name: (identifier)
type: (type
name: (type_identifier))))
return_type: (type
name: (type_identifier))
body: (external_function_body
(string
(quoted_content))
(string
(quoted_content))))
(external_function
name: (identifier)
parameters: (function_parameters)
return_type: (tuple_type
(type
name: (type_identifier)
arguments: (type_arguments
(type_argument
(type
name: (type_identifier)))))
(function_type
parameter_types: (function_parameter_types
(type
name: (type_identifier)))
return_type: (type
name: (type_identifier))))
body: (external_function_body
(string
(quoted_content))
(string
(quoted_content)))))
================================================================================
Public external functions
================================================================================
pub external fn read_file(String) -> Result(String, Reason) =
"file" "open"
pub external fn a(name: String) -> String = "x" "y"
pub external fn a() -> #(List(Int), fn(Int) -> String) = "x" "y"
--------------------------------------------------------------------------------
(source_file
(external_function
(visibility_modifier)
name: (identifier)
parameters: (function_parameters
(function_parameter
type: (type
name: (type_identifier))))
return_type: (type
name: (type_identifier)
arguments: (type_arguments
(type_argument
(type
name: (type_identifier)))
(type_argument
(type
name: (type_identifier)))))
body: (external_function_body
(string
(quoted_content))
(string
(quoted_content))))
(external_function
(visibility_modifier)
name: (identifier)
parameters: (function_parameters
(function_parameter
name: (identifier)
type: (type
name: (type_identifier))))
return_type: (type
name: (type_identifier))
body: (external_function_body
(string
(quoted_content))
(string
(quoted_content))))
(external_function
(visibility_modifier)
name: (identifier)
parameters: (function_parameters)
return_type: (tuple_type
(type
name: (type_identifier)
arguments: (type_arguments
(type_argument
(type
name: (type_identifier)))))
(function_type
parameter_types: (function_parameter_types
(type
name: (type_identifier)))
return_type: (type
name: (type_identifier))))
body: (external_function_body
(string
(quoted_content))
(string
(quoted_content)))))

@ -0,0 +1,52 @@
================================================================================
External types
================================================================================
external type IODevice
external type IODevice()
external type Map(key, value)
--------------------------------------------------------------------------------
(source_file
(external_type
(type_name
name: (type_identifier)))
(external_type
(type_name
name: (type_identifier)
parameters: (type_parameters)))
(external_type
(type_name
name: (type_identifier)
parameters: (type_parameters
(type_parameter)
(type_parameter)))))
================================================================================
Public external types
================================================================================
pub external type IODevice
pub external type IODevice()
pub external type Map(key, value)
--------------------------------------------------------------------------------
(source_file
(external_type
(visibility_modifier)
(type_name
name: (type_identifier)))
(external_type
(visibility_modifier)
(type_name
name: (type_identifier)
parameters: (type_parameters)))
(external_type
(visibility_modifier)
(type_name
name: (type_identifier)
parameters: (type_parameters
(type_parameter)
(type_parameter)))))

File diff suppressed because it is too large Load Diff

@ -0,0 +1,88 @@
================================================================================
Imports
================================================================================
import a
import a/b
--------------------------------------------------------------------------------
(source_file
(import
module: (module))
(import
module: (module)))
================================================================================
Unqualified imports
================================================================================
import a.{b}
import a/b.{c, d}
import a/b.{c as d, e}
import a/b.{c, D as E}
--------------------------------------------------------------------------------
(source_file
(import
module: (module)
imports: (unqualified_imports
(unqualified_import
name: (identifier))))
(import
module: (module)
imports: (unqualified_imports
(unqualified_import
name: (identifier))
(unqualified_import
name: (identifier))))
(import
module: (module)
imports: (unqualified_imports
(unqualified_import
name: (identifier)
alias: (identifier))
(unqualified_import
name: (identifier))))
(import
module: (module)
imports: (unqualified_imports
(unqualified_import
name: (identifier))
(unqualified_import
name: (type_identifier)
alias: (type_identifier)))))
================================================================================
Aliased imports
================================================================================
import a/b.{c as d} as e
import animal/cat as kitty
import animal.{Cat as Kitty} as a
import animal.{}
--------------------------------------------------------------------------------
(source_file
(import
module: (module)
imports: (unqualified_imports
(unqualified_import
name: (identifier)
alias: (identifier)))
alias: (identifier))
(import
module: (module)
alias: (identifier))
(import
module: (module)
imports: (unqualified_imports
(unqualified_import
name: (type_identifier)
alias: (type_identifier)))
alias: (identifier))
(import
module: (module)
imports: (unqualified_imports)))

@ -0,0 +1,48 @@
==============
Target groups
==============
if erlang {
const a = 1
}
if javascript {
const a = 1
const b = "Hello"
}
---
(source_file
(target_group
target: (target)
(constant
name: (identifier)
value: (integer)))
(target_group
target: (target)
(constant
name: (identifier)
value: (integer))
(constant
name: (identifier)
value: (string
(quoted_content)))))
========================
Target group edge cases
========================
if erlang {}
if javascript {const a = 1}
---
(source_file
(target_group
target: (target))
(target_group
target: (target)
(constant
name: (identifier)
value: (integer))))

@ -0,0 +1,131 @@
================================================================================
Type aliases
================================================================================
type Headers =
List(#(String, String))
type Box(a) = #(a)
type NamedBox(a) = #(String, a)
--------------------------------------------------------------------------------
(source_file
(type_alias
(type_name
name: (type_identifier))
(type
name: (type_identifier)
arguments: (type_arguments
(type_argument
(tuple_type
(type
name: (type_identifier))
(type
name: (type_identifier)))))))
(type_alias
(type_name
name: (type_identifier)
parameters: (type_parameters
(type_parameter)))
(tuple_type
(type_var)))
(type_alias
(type_name
name: (type_identifier)
parameters: (type_parameters
(type_parameter)))
(tuple_type
(type
name: (type_identifier))
(type_var))))
================================================================================
Public type aliases
================================================================================
pub type Headers =
List(#(String, String))
pub type Box(a) = #(a)
pub type NamedBox(a) = #(String, a)
--------------------------------------------------------------------------------
(source_file
(type_alias
(visibility_modifier)
(type_name
name: (type_identifier))
(type
name: (type_identifier)
arguments: (type_arguments
(type_argument
(tuple_type
(type
name: (type_identifier))
(type
name: (type_identifier)))))))
(type_alias
(visibility_modifier)
(type_name
name: (type_identifier)
parameters: (type_parameters
(type_parameter)))
(tuple_type
(type_var)))
(type_alias
(visibility_modifier)
(type_name
name: (type_identifier)
parameters: (type_parameters
(type_parameter)))
(tuple_type
(type
name: (type_identifier))
(type_var))))
================================================================================
Public opaque type aliases
================================================================================
pub opaque type Headers =
List(#(String, String))
pub opaque type Box(a) = #(a)
pub opaque type NamedBox(a) = #(String, a)
--------------------------------------------------------------------------------
(source_file
(type_alias
(visibility_modifier)
(opacity_modifier)
(type_name
name: (type_identifier))
(type
name: (type_identifier)
arguments: (type_arguments
(type_argument
(tuple_type
(type
name: (type_identifier))
(type
name: (type_identifier)))))))
(type_alias
(visibility_modifier)
(opacity_modifier)
(type_name
name: (type_identifier)
parameters: (type_parameters
(type_parameter)))
(tuple_type
(type_var)))
(type_alias
(visibility_modifier)
(opacity_modifier)
(type_name
name: (type_identifier)
parameters: (type_parameters
(type_parameter)))
(tuple_type
(type
name: (type_identifier))
(type_var))))

@ -0,0 +1,323 @@
================================================================================
Excerpt from stdlib's base.gleam
================================================================================
import gleam/bit_string
import gleam/string
/// Encodes a BitString into a base 64 encoded string.
///
pub fn encode64(input: BitString, padding: Bool) -> String {
let encoded = do_encode64(input)
case padding {
True -> encoded
False -> string.replace(encoded, "=", "")
}
}
if erlang {
external fn do_encode64(BitString) -> String =
"base64" "encode"
}
if javascript {
external fn do_encode64(BitString) -> String =
"../gleam_stdlib.mjs" "encode64"
}
--------------------------------------------------------------------------------
(source_file
(import
module: (module))
(import
module: (module))
(statement_comment)
(statement_comment)
(function
(visibility_modifier)
name: (identifier)
parameters: (function_parameters
(function_parameter
name: (identifier)
type: (type
name: (type_identifier)))
(function_parameter
name: (identifier)
type: (type
name: (type_identifier))))
return_type: (type
name: (type_identifier))
body: (function_body
(let
pattern: (identifier)
value: (function_call
function: (identifier)
arguments: (arguments
(argument
value: (identifier)))))
(case
subjects: (case_subjects
(identifier))
clauses: (case_clauses
(case_clause
patterns: (case_clause_patterns
(case_clause_pattern
(record_pattern
name: (type_identifier))))
value: (identifier))
(case_clause
patterns: (case_clause_patterns
(case_clause_pattern
(record_pattern
name: (type_identifier))))
value: (function_call
function: (field_access
record: (identifier)
field: (label))
arguments: (arguments
(argument
value: (identifier))
(argument
value: (string
(quoted_content)))
(argument
value: (string)))))))))
(target_group
target: (target)
(external_function
name: (identifier)
parameters: (function_parameters
(function_parameter
type: (type
name: (type_identifier))))
return_type: (type
name: (type_identifier))
body: (external_function_body
(string
(quoted_content))
(string
(quoted_content)))))
(target_group
target: (target)
(external_function
name: (identifier)
parameters: (function_parameters
(function_parameter
type: (type
name: (type_identifier))))
return_type: (type
name: (type_identifier))
body: (external_function_body
(string
(quoted_content))
(string
(quoted_content))))))
================================================================================
Excerpt from stdlib's bool.gleam
================================================================================
//// A type with two possible values, `True` and `False`. Used to indicate whether
//// things are... true or false!
////
//// Often is it clearer and offers more type safety to define a custom type
//// than to use `Bool`. For example, rather than having a `is_teacher: Bool`
//// field consider having a `role: SchoolRole` field where `SchoolRole` is a custom
//// type that can be either `Student` or `Teacher`.
import gleam/order.{Order}
/// Returns the opposite bool value.
///
/// This is the same as the `!` or `not` operators in some other languages.
///
/// ## Examples
///
/// > negate(True)
/// False
///
/// > negate(False)
/// True
///
pub fn negate(bool: Bool) -> Bool {
case bool {
True -> False
False -> True
}
}
--------------------------------------------------------------------------------
(source_file
(module_comment)
(module_comment)
(module_comment)
(module_comment)
(module_comment)
(module_comment)
(module_comment)
(import
module: (module)
imports: (unqualified_imports
(unqualified_import
name: (type_identifier))))
(statement_comment)
(statement_comment)
(statement_comment)
(statement_comment)
(statement_comment)
(statement_comment)
(statement_comment)
(statement_comment)
(statement_comment)
(statement_comment)
(statement_comment)
(statement_comment)
(function
(visibility_modifier)
name: (identifier)
parameters: (function_parameters
(function_parameter
name: (identifier)
type: (type
name: (type_identifier))))
return_type: (type
name: (type_identifier))
body: (function_body
(case
subjects: (case_subjects
(identifier))
clauses: (case_clauses
(case_clause
patterns: (case_clause_patterns
(case_clause_pattern
(record_pattern
name: (type_identifier))))
value: (record
name: (type_identifier)))
(case_clause
patterns: (case_clause_patterns
(case_clause_pattern
(record_pattern
name: (type_identifier))))
value: (record
name: (type_identifier))))))))
================================================================================
Trailing commas
================================================================================
import animal.{Cat,}
const foo: #(Int,) = #(1,)
const bar = [1,]
const cat:Cat(String,) = Cat(name: "Nubi",)
type Thing {
First(name: String,)
}
external fn foo(String,) -> String = "foo" "bar"
fn foo(a,) {
myfun(a,)
let Cat(name: name,) = Cat(..a, name: "George",)
let #(a) = #(a,)
let [x,] = [1,]
}
--------------------------------------------------------------------------------
(source_file
(import
(module)
(unqualified_imports
(unqualified_import
(type_identifier))))
(constant
(identifier)
(tuple_type
(type
(type_identifier)))
(tuple
(integer)))
(constant
(identifier)
(list
(integer)))
(constant
(identifier)
(type
(type_identifier)
(type_arguments
(type_argument
(type
(type_identifier)))))
(record
(type_identifier)
(arguments
(argument
(label)
(string
(quoted_content))))))
(type_definition
(type_name
(type_identifier))
(data_constructors
(data_constructor
(type_identifier)
(data_constructor_arguments
(data_constructor_argument
(label)
(type
(type_identifier)))))))
(external_function
(identifier)
(function_parameters
(function_parameter
(type
(type_identifier))))
(type
(type_identifier))
(external_function_body
(string
(quoted_content))
(string
(quoted_content))))
(function
(identifier)
(function_parameters
(function_parameter
(identifier)))
(function_body
(function_call
(identifier)
(arguments
(argument
(identifier))))
(let
(record_pattern
(type_identifier)
(record_pattern_arguments
(record_pattern_argument
(label)
(identifier))))
(record_update
(type_identifier)
(identifier)
(record_update_arguments
(record_update_argument
(label)
(string
(quoted_content))))))
(let
(tuple_pattern
(identifier))
(tuple
(identifier)))
(let
(list_pattern
(identifier))
(list
(integer))))))

@ -0,0 +1,25 @@
<<3>>
// <- punctuation.bracket
//^ number
// ^ punctuation.bracket
<<3:8>>
//^ number
// ^ punctuation.delimiter
// ^ number
<<3:size(8)>>
//^ number
// ^ punctuation.delimiter
// ^ function.builtin
// ^ punctuation.bracket
// ^ number
// ^ punctuation.bracket
<<code:int-size(8)-unit(2), reason:utf8>>
// ^ variable
// ^ function.builtin
// ^ punctuation.delimiter
// ^ function.builtin
// ^ number
// ^ function.builtin
// ^ number
// ^ variable
// ^ function.builtin

@ -0,0 +1,24 @@
fn case_case(x: Option(String)) {
// ^ variable.parameter
// ^ type
case #(x, x) {
// ^ variable.parameter
// ^ variable.parameter
#(None, None) -> None
// ^ type
// ^ type
// ^ type
#(Some(y), Some(z)) -> #(y, z)
// ^ type
// ^ variable
// ^ type
// ^ variable
// ^ variable
// ^ variable
}
// this is bound above but no longer in scope,
// so it should be interpereted as a module
z.foo()
// <- module
}

@ -0,0 +1,89 @@
pub fn replace(
// <- keyword
// <- keyword
// ^ function
// ^ punctuation.bracket
in original: String,
// <- property
// ^ variable.parameter
// ^ type
// ^ punctuation.delimiter
each pattern: String,
// <- property
// ^ variable.parameter
// ^ type
// ^ punctuation.delimiter
with replacement: String,
// <- property
// ^ variable.parameter
// ^ type
// ^ punctuation.delimiter
) -> String {
// <- punctuation.delimiter
// ^ type
// ^ punctuation.bracket
string.replace(in: original, each: pattern, with: replacement)
// <- module
// ^ function
// ^ property
// ^ variable.parameter
// ^ property
// ^ variable.parameter
// ^ property
// ^ variable.parameter
original
// ^ variable.parameter
|> string.replace(pattern, replacement)
// <- operator
// ^ module
// ^ function
// ^ variable.parameter
// ^ variable.parameter
}
fn record_with_fun_field(record) {
let foo = Bar(baz: fn(x) { x + 1 })
foo.baz(41)
// <- variable
// ^ property
record.foobar("hello")
// ^ variable.parameter
// ^ property
string.replace("hello", "l", "o")
// ^ module
// ^ function
}
fn trial(uri) {
// ^ variable.parameter
case uri {
// ^ variable.parameter
Uri(scheme: None) -> True
// <- type
// ^ property
// ^ type
// ^ type
_ -> False
// <- comment.unused
}
}
fn myfun(argument) {
let local_fun = fn(x) { x + 1 }
// ^ variable
// ^ variable.parameter
// ^ variable.parameter
argument
// ^ variable.parameter
|> local_fun
// <- operator
// ^ variable
|> module_fun
// ^ function
module_fun(local_fun(argument))
// ^ function
// ^ variable
// ^ variable.parameter
}

@ -0,0 +1,42 @@
import gleam/io
// ^ module
// ^ module
// ^ module
import animal/cat as kitty
// ^ module
// ^ module
pub fn main() {
io.println("hello world")
// <- module
}
type MyType {
MyType(func: fn() -> Int)
}
fn record_access_case(param: MyType) {
let binding = MyType(func: fn() { 42 })
let _ = binding.func()
// ^ comment.unused
// ^ variable
let _ = param.func()
// ^ comment.unused
// ^ variable.parameter
}
fn pipe_operator_case(string: String) {
string
// <- variable.parameter
|> iodata.new
// ^ module
|> iodata.reverse
// ^ module
}
fn remote_type_case() {
gleam.Ok(1)
// <- module
// ^ punctuation.delimiter
// ^ type
}

@ -0,0 +1,13 @@
pub type User {
User(name: String)
// <- type
// ^ property
// ^ type
}
pub fn new(name: String) {
// ^ variable.parameter
User(name: name)
// ^ property
// ^ variable.parameter
}

@ -0,0 +1,75 @@
import gleam/option.{Option, Some, None}
// ^ reference.module
// ^ reference.type
// ^ reference.type
// ^ reference.type
import gleam/bit_builder
// ^ reference.module
pub type FrameData {
// ^ definition.type
Text(String)
// <- definition.type
// ^ reference.type
Binary(BitString)
Continuation(BitString)
Ping(BitString)
Pong(BitString)
Close(code: Option(Int), reason: Option(String))
// <- definition.type
// ^ reference.type
// ^ reference.type
// ^ reference.type
// ^ reference.type
}
pub type Frame {
// ^ definition.type
Frame(reserved: BitString, mask: Option(BitString), data: FrameData, fin: Bool)
}
fn encode_frame(frame: Frame) -> bit_builder.BitBuilder {
// ^ definition.function
// ^ reference.type
// ^ reference.module
// ^ reference.type
let fin =
case frame.fin {
True -> <<1:size(1)>>
False -> <<0:size(1)>>
}
let opcode =
case frame.data {
Continuation(_) -> <<0x0:size(1)>>
// <- reference.type
Text(_) -> <<0x1:size(1)>>
Binary(_) -> <<0x2:size(1)>>
// 0x3-7 reserved for future non-control frames
Close(..) -> <<0x8:size(1)>>
Pong(_) -> <<0x9:size(1)>>
Pong(_) -> <<0xA:size(1)>>
}
let is_masked_bit =
case frame.mask {
Some(_) -> <<1:size(1)>>
None -> <<0:size(1)>>
}
bit_builder.new()
// <- reference.module
// ^ reference.function
|> bit_builder.append(fin)
// ^ reference.function
|> bit_builder.append(frame.reserved)
|> bit_builder.append(opcode)
|> bit_builder.append(is_masked_bit)
|> bit_builder.append(option.unwrap(frame.mask, <<>>))
// ^ reference.module
// ^ reference.module
// ^ reference.function
|> bit_builder.append(mask_data(frame))
// ^ reference.function
}

@ -0,0 +1,10 @@
fn record_with_fun_field(record) {
let foo = Bar(baz: fn(x) { x + 1 })
// ^ reference.type
foo.baz(41)
record.foobar("hello")
string.replace("hello", "l", "o")
// ^ reference.module
// ^ reference.function
}