build(rust): vendor rnix parser crate
This commit was merged in pull request #57.
This commit is contained in:
1 parent
cbdf482422
commit
82ad65a3a4
492 files changed
+67785
-1
No files matched your search
@@ -0,0 +1 @@
|
||||
{"$comment":"This file only protects against accidental modifications. It is not a security mechanism and does not protect against malicious changes.","files":{".cargo_vcs_info.json":"fcdcfcbe69ba219427d7e4f0435343775127b3dc292fac94a5729ec7d5e5e804",".envrc":"1d26c74fd25a4c3dbb09e029fc609588da499fd4af2a41c88f6316c7f8c54cf1",".github/ISSUE_TEMPLATE/bug_report.md":"e029cf740ebfff7edd8ac7a71914cdd06d0dbb5d2c6a82c739aaf0f3d9fe7e90",".github/ISSUE_TEMPLATE/feature_request.md":"4589c7cf6d0d04b692ea0a4d99187488d9631bf1c9d4712aa41478c0907dea3f",".github/ISSUE_TEMPLATE/question.md":"e555a3b9fe9e67aeca3245a5a0162a9fe08516603989a80112b001a1c91b7b67",".github/PULL_REQUEST_TEMPLATE.md":"2eb53504c7972ee90a450a53f651694b73a0c1c9d1f68d1e09b9650e682b96e2",".github/dependabot.yml":"883b95e3be554fbadd92b315e4a8c6ea6ca688b64924e22dd61222b07f683507",".github/workflows/ci.yml":"565bb7e946d21cb3d1141d87422df88536ca5c84de0f9bbc7c79c623583e99d9","CHANGELOG.md":"e98de826b40a296bd75050d744b71a1a4aa6944e29f36ef1c2d38c3788152dd0","Cargo.lock":"ab7e6cdb7273dffd1189bdccea6721dbbf0cf4e6f16ac9807c3bd299efab4492","Cargo.toml":"1710fd19c321d58563c6a375e6c4fc26c94511049bcfb3024e426d16eb4df83a","Cargo.toml.orig":"663ac3c05de129104746eeaeef3b6a6821eb6af5b5fff4834cb53eb317829ac5","LICENSE":"6c54a9750f1e6da4ad6618c569f2541b1df31921e2b30651886fc8893dfc3e76","MIGRATING.md":"baff376c0509c119628e318ffedeed21703f8ea32db8de743014643b7e2104c4","README.md":"4a09818de05e456506d5ee150a73b8ff92aaf6897ec8def03d3c9688c82a91a2","benches/all-packages.nix":"935f239a576caa554e546072772039f6efcbde2f2e6568733b8857fe476c5604","benches/all-packages.rs":"e64ed71c76a13eba2c2444062423122f5ad8ced00ab6de1951e19e1806313926","default.nix":"d84fab688ba7f236a7ad9db1eedfa8dd68bfec1271c5aea81cc0ecdc84d3ea84","examples/dump-ast.rs":"b3759df9673065fc661258f32a50aaae25b5573faef986dc47658ae3f6c94cc5","examples/error-report.rs":"ab3f5357d3ff3816b2355ad526fcfc9a33a8c5dc5dd434cfcd82a4b951643e52","examples/from-stdin.rs":"77726afba4b0c79b46e7efd2a2dc940bedb0bc9e30dca37d7443b33e306433d1","examples/list-fns.rs":"01d3f433cbeebfedc1c5d8106db0f1e9965b587c30244e66b504b98828e6d433","examples/preserve.rs":"971a77999a84bc11b9a3afe179bd8e51f4de475009e776c42890ac02c2eaf030","examples/test-nixpkgs.rs":"0924d62302f5486a87d26597af6f78497a6ed2439452efe14b5262d3644a21be","flake.lock":"a71d5cc0ebeed8335099205ad2e6eece037f39faf609de2b2afd83c1d0b1c0fc","flake.nix":"bc94dbe872df56b1b16289dcfdd5db0a627fd6165331c2d11b5bae8b2481da57","rustfmt.toml":"f74204a6f92aa7422a16ecb2ffe2d5bae0f123b778d08b5db1a398a3c9ca4306","shell.nix":"6fd207b4354fc12c29f603ba106d4bc5a6becbc0f13de4f90846ee10b2054bb1","src/ast.rs":"0307dd2b362ce111a4b730318c7df75f1d62534acf7a4a295972ae5369e02141","src/ast/expr_ext.rs":"53afc86db8b404aa7fb1f2a7c47a925cb83bea95cd3b5e52b44963439892be3d","src/ast/interpol.rs":"7d9c8baa98fb47d691e55a1106f5e457c6c786dbc00d27e015acf6f67c3248e1","src/ast/nodes.rs":"950d9e7c0fe9320e4e5ce19aaa1c06aeab6fc3236199a7ef9f395180ae3eb423","src/ast/operators.rs":"51ae735d38623d0490efd21d7060806aadbe1f91589f5cde922c1b50a0941a98","src/ast/path_util.rs":"8dfe591cb762e630bc0271c2a1875be0b6c3b35876af118cfd4edc055e1cf34c","src/ast/str_util.rs":"448e5e2805552e0a386569e8f497ef10802c5b7d45360fa345b0dd1b36b82d3f","src/ast/tokens.rs":"7d6aec7ed91930cae7d472df800e6eccff4afe855084928e22f0460d1a0017a3","src/kinds.rs":"3a06fd766de635066eccabcca90f5698458d7bf868a9fb12410f90a8766a6d0e","src/lib.rs":"dd33d6ab292d2c1c2f303392fc5a34ebb96106b61b6a3ea32b97fcc4e6f007ca","src/macros.rs":"6f222ffebe7d2b7ba9d653e050ce680cb34f5ccad5046b1a74ca000e27cf10ed","src/parser.rs":"facbcf26a37d0702da21aab5ec65bb5e69e8b4fd70fd8b1fd33ad54610c5b56e","src/tests.rs":"4bbdf0c133806aab2404e6449af3c4669b4c9632206b10b60db2e2f4a2c05f96","src/token_set.rs":"5b2b9f7a8dc04a0b9334bf19b38bf51dc7237eea3c1d6d07df06f3e94b948079","src/tokenizer.rs":"90e0db5df66f75a6098277f695f79f98098b099534d23d9e8ef8547d5d690712","test_data/parser/error/dollar_ident_attrpath_issue174.expect":"01262a025ed92dadfbcc3ec6e4fa70020a2d79d42e85c330cff76b0e35eee4a9","test_data/parser/error/dollar_ident_attrpath_issue174.nix":"213745923795602e58733d53a142ccbf7a46f15820a3e8dd351fe03c387792dd","test_data/parser/error/error.expect":"22f46b02e4c35c07a42f68c9096d2c97e520e4411da6beaf5146f128210dde87","test_data/parser/error/error.nix":"ab96e082ceb9a8fd295b1b9770d1a092fecc957f9dfa9393a51572db005f6510","test_data/parser/error/extra_comma.expect":"83cea8dfcdccb17eebda361f9d858082012b2090f6ab50a9f8b7ec05a65e6140","test_data/parser/error/extra_comma.nix":"a67d8cf479d324f353bdbaff9e94772d6acc74807c07959fa0b51e83f431b258","test_data/parser/error/formals_double_bind.expect":"8659ab05254da887bebff84e60c9ddf9aead1fc10d12594455e83826de39db1b","test_data/parser/error/formals_double_bind.nix":"ad473bbab713d5fe1c2cbddebe176962d1fab8a03e76a348347f7587c7ab27d5","test_data/parser/error/inherit_from_late.expect":"fbda47c9f72a1b48f9d312c33f65dc6b054258a717d9c5fa4bac5cb158ef35d7","test_data/parser/error/inherit_from_late.niLine truncated
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"git": {
|
||||
"sha1": "0472081214c24b1ab4d34f7bf544284ed4e45ad3",
|
||||
"dirty": true
|
||||
},
|
||||
"path_in_vcs": ""
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: 'bug'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
<!--
|
||||
Feel free to remove paragraphs that don't apply to your bugreport.
|
||||
-->
|
||||
|
||||
### Describe the bug
|
||||
|
||||
<!-- A clear and concise description of what the bug is.-->
|
||||
|
||||
### Code Snippet to reproduce
|
||||
|
||||
<!--
|
||||
If applicable, a Nix expression that demonstrates the problem.
|
||||
-->
|
||||
|
||||
```nix
|
||||
|
||||
```
|
||||
|
||||
### Expected behavior
|
||||
|
||||
<!-- A clear and concise description of what you expected to happen.-->
|
||||
|
||||
### Additional context
|
||||
|
||||
<!--
|
||||
Anything else you consider relevant such as `rnix` version (mandatory), related issues etc.
|
||||
-->
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
<!--
|
||||
Feel free to remove paragraphs that don't apply to your request.
|
||||
-->
|
||||
|
||||
### Description
|
||||
|
||||
<!--
|
||||
Describe the feature / a possible solution you'd like to see.
|
||||
-->
|
||||
|
||||
### Considered alternatives
|
||||
|
||||
<!--
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
-->
|
||||
|
||||
### Additional context
|
||||
|
||||
<!--
|
||||
Add any context such as screenshots, related issues or whatever you consider relevant here.
|
||||
-->
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask a general question about the project
|
||||
title: ''
|
||||
labels: 'question'
|
||||
assignees: ''
|
||||
---
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
<!--
|
||||
Feel free to remove paragraphs that don't apply to your PR.
|
||||
-->
|
||||
|
||||
### Summary & Motivation
|
||||
|
||||
<!--
|
||||
Please summarize the changes you've made and the motivation behind it.
|
||||
-->
|
||||
|
||||
### Backwards-incompatible changes
|
||||
|
||||
<!--
|
||||
If applicable, please summarize which changes of your PR are backwards-incompatible
|
||||
to the latest release.
|
||||
-->
|
||||
|
||||
### Further context
|
||||
|
||||
<!--
|
||||
If applicable, please give further context such as related issues / PRs,
|
||||
an explanation of the problem you're aiming to solve or anything else you also
|
||||
consider relevant.
|
||||
-->
|
||||
@@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
@@ -0,0 +1,64 @@
|
||||
name: ci
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: test
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Cache Dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Compile
|
||||
run: cargo test --no-run --locked
|
||||
|
||||
- name: Test
|
||||
run: cargo test -- --nocapture
|
||||
|
||||
rustfmt:
|
||||
name: rustfmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt
|
||||
|
||||
- name: Check formatting
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
clippy:
|
||||
name: clippy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: clippy
|
||||
|
||||
- name: Run Clippy
|
||||
run: cargo clippy
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
###############################################################################
|
||||
# @generated
|
||||
# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To
|
||||
# regenerate this file, run the following:
|
||||
#
|
||||
# bazel run @@//third_party:vendor_crates
|
||||
###############################################################################
|
||||
|
||||
load("@rules_rust//cargo:defs.bzl", "cargo_toml_env_vars")
|
||||
load("@rules_rust//rust:defs.bzl", "rust_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
cargo_toml_env_vars(
|
||||
name = "cargo_toml_env_vars",
|
||||
src = "Cargo.toml",
|
||||
)
|
||||
|
||||
rust_library(
|
||||
name = "rnix",
|
||||
srcs = glob(
|
||||
include = ["**/*.rs"],
|
||||
allow_empty = False,
|
||||
),
|
||||
compile_data = glob(
|
||||
include = ["**"],
|
||||
allow_empty = True,
|
||||
exclude = [
|
||||
"**/* *",
|
||||
".tmp_git_root/**/*",
|
||||
"BUILD",
|
||||
"BUILD.bazel",
|
||||
"WORKSPACE",
|
||||
"WORKSPACE.bazel",
|
||||
],
|
||||
),
|
||||
crate_root = "src/lib.rs",
|
||||
edition = "2021",
|
||||
rustc_env_files = [
|
||||
":cargo_toml_env_vars",
|
||||
],
|
||||
rustc_flags = [
|
||||
"--cap-lints=allow",
|
||||
],
|
||||
tags = [
|
||||
"cargo-bazel",
|
||||
"crate-name=rnix",
|
||||
"manual",
|
||||
"noclippy",
|
||||
"norustfmt",
|
||||
],
|
||||
target_compatible_with = select({
|
||||
"@rules_rust//rust/platform:aarch64-apple-darwin": [],
|
||||
"@rules_rust//rust/platform:aarch64-apple-ios": [],
|
||||
"@rules_rust//rust/platform:aarch64-apple-ios-macabi": [],
|
||||
"@rules_rust//rust/platform:aarch64-apple-ios-sim": [],
|
||||
"@rules_rust//rust/platform:aarch64-linux-android": [],
|
||||
"@rules_rust//rust/platform:aarch64-pc-windows-msvc": [],
|
||||
"@rules_rust//rust/platform:aarch64-unknown-fuchsia": [],
|
||||
"@rules_rust//rust/platform:aarch64-unknown-linux-gnu": [],
|
||||
"@rules_rust//rust/platform:aarch64-unknown-nixos-gnu": [],
|
||||
"@rules_rust//rust/platform:aarch64-unknown-none": [],
|
||||
"@rules_rust//rust/platform:aarch64-unknown-nto-qnx710": [],
|
||||
"@rules_rust//rust/platform:aarch64-unknown-uefi": [],
|
||||
"@rules_rust//rust/platform:arm-unknown-linux-gnueabi": [],
|
||||
"@rules_rust//rust/platform:arm-unknown-linux-musleabi": [],
|
||||
"@rules_rust//rust/platform:armv7-linux-androideabi": [],
|
||||
"@rules_rust//rust/platform:armv7-unknown-linux-gnueabi": [],
|
||||
"@rules_rust//rust/platform:i686-apple-darwin": [],
|
||||
"@rules_rust//rust/platform:i686-linux-android": [],
|
||||
"@rules_rust//rust/platform:i686-pc-windows-msvc": [],
|
||||
"@rules_rust//rust/platform:i686-unknown-freebsd": [],
|
||||
"@rules_rust//rust/platform:i686-unknown-linux-gnu": [],
|
||||
"@rules_rust//rust/platform:loongarch64-unknown-linux-gnu": [],
|
||||
"@rules_rust//rust/platform:mips-unknown-linux-gnu": [],
|
||||
"@rules_rust//rust/platform:powerpc-unknown-linux-gnu": [],
|
||||
"@rules_rust//rust/platform:riscv32imac-unknown-none-elf": [],
|
||||
"@rules_rust//rust/platform:riscv32imc-unknown-none-elf": [],
|
||||
"@rules_rust//rust/platform:riscv64gc-unknown-linux-gnu": [],
|
||||
"@rules_rust//rust/platform:riscv64gc-unknown-none-elf": [],
|
||||
"@rules_rust//rust/platform:s390x-unknown-linux-gnu": [],
|
||||
"@rules_rust//rust/platform:sparc64-unknown-linux-gnu": [],
|
||||
"@rules_rust//rust/platform:sparc64-unknown-netbsd": [],
|
||||
"@rules_rust//rust/platform:sparc64-unknown-openbsd": [],
|
||||
"@rules_rust//rust/platform:thumbv6m-none-eabi": [],
|
||||
"@rules_rust//rust/platform:thumbv7em-none-eabi": [],
|
||||
"@rules_rust//rust/platform:thumbv7em-none-eabihf": [],
|
||||
"@rules_rust//rust/platform:thumbv7m-none-eabi": [],
|
||||
"@rules_rust//rust/platform:thumbv8m.main-none-eabi": [],
|
||||
"@rules_rust//rust/platform:thumbv8m.main-none-eabihf": [],
|
||||
"@rules_rust//rust/platform:wasm32-unknown-emscripten": [],
|
||||
"@rules_rust//rust/platform:wasm32-unknown-unknown": [],
|
||||
"@rules_rust//rust/platform:wasm32-wasip1": [],
|
||||
"@rules_rust//rust/platform:wasm32-wasip1-threads": [],
|
||||
"@rules_rust//rust/platform:wasm32-wasip2": [],
|
||||
"@rules_rust//rust/platform:x86_64-apple-darwin": [],
|
||||
"@rules_rust//rust/platform:x86_64-apple-ios": [],
|
||||
"@rules_rust//rust/platform:x86_64-apple-ios-macabi": [],
|
||||
"@rules_rust//rust/platform:x86_64-linux-android": [],
|
||||
"@rules_rust//rust/platform:x86_64-pc-windows-msvc": [],
|
||||
"@rules_rust//rust/platform:x86_64-unknown-freebsd": [],
|
||||
"@rules_rust//rust/platform:x86_64-unknown-fuchsia": [],
|
||||
"@rules_rust//rust/platform:x86_64-unknown-linux-gnu": [],
|
||||
"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu": [],
|
||||
"@rules_rust//rust/platform:x86_64-unknown-none": [],
|
||||
"@rules_rust//rust/platform:x86_64-unknown-uefi": [],
|
||||
"//conditions:default": ["@platforms//:incompatible"],
|
||||
}),
|
||||
version = "0.14.0",
|
||||
deps = [
|
||||
"//third_party/crates/rowan-0.16.1:rowan",
|
||||
],
|
||||
)
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes between releases will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v0.14.0] - 2026-02-16
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix absolute path interpolation starting with `/${` failing to parse
|
||||
([#182](https://github.com/nix-community/rnix-parser/issues/182),
|
||||
from [@imxyy1soope1](https://github.com/imxyy1soope1)).
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* **`__curPos` is now a distinct node type.** `__curPos` is a Nix keyword that evaluates to an
|
||||
attribute set containing the file, line, and column where it appears.
|
||||
[Lix documents this as a keyword.](https://docs.lix.systems/manual/lix/nightly/language/constructs.html#keywords-__curPos)
|
||||
It was previously represented as `NODE_IDENT`, but is now parsed as `NODE_CUR_POS` with a
|
||||
corresponding `TOKEN_CUR_POS` token kind (from [@3timeslazy](https://github.com/3timeslazy)).
|
||||
|
||||
## [v0.13.0] - 2026-01-29
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* **Path types are now distinguished at the type level.** `TOKEN_PATH` and `NODE_PATH` have been
|
||||
replaced with specific variants for each path type:
|
||||
- `TOKEN_PATH_ABS` / `NODE_PATH_ABS` - absolute paths (`/foo/bar`)
|
||||
- `TOKEN_PATH_REL` / `NODE_PATH_REL` - relative paths (`./foo`, `../bar`, `foo/bar`)
|
||||
- `TOKEN_PATH_HOME` / `NODE_PATH_HOME` - home-relative paths (`~/foo`)
|
||||
- `TOKEN_PATH_SEARCH` / `NODE_PATH_SEARCH` - search paths (`<nixpkgs>`)
|
||||
|
||||
The `ast::Expr` enum now has `PathAbs`, `PathRel`, `PathHome`, and `PathSearch` variants
|
||||
instead of a single `Path` variant. See `MIGRATING.md` for upgrade guidance.
|
||||
|
||||
* **Double slashes in paths are now syntax errors.** Paths like `foo//bar` now produce
|
||||
`TOKEN_ERROR` instead of being parsed.
|
||||
|
||||
* **The `parser` and `tokenizer` modules are now private.** Use `rnix::Root::parse()` and
|
||||
`rnix::tokenize()` instead.
|
||||
|
||||
### Added
|
||||
|
||||
* Implement `FromStr` for `SyntaxKind` (from [@abhillman](https://github.com/abhillman)).
|
||||
|
||||
* Add `MIGRATING.md` with upgrade guidance for breaking changes.
|
||||
|
||||
* Add `nix develop .#fuzz` devShell for running cargo-fuzz with nightly Rust.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix leading zero number parsing to match Nix behavior (from [@hsjobeki](https://github.com/hsjobeki)).
|
||||
|
||||
### Changed
|
||||
|
||||
* Update `rowan` to 0.16.
|
||||
* Update `criterion` to 0.8.
|
||||
* Update `expect-test` to 1.5.1.
|
||||
* Add Clippy to CI and resolve all warnings.
|
||||
|
||||
## [v0.12.0] - 2025-01-09
|
||||
|
||||
* Add support for pipe operators
|
||||
|
||||
## [v0.11.0] - 2022-11-11
|
||||
|
||||
* removes the `types` module and replaces it with `ast`
|
||||
|
||||
* the `ast` modules has better types and sum types
|
||||
|
||||
* removes `ParsedType` in favor of `Expr`
|
||||
|
||||
* removes the `value` module. methods on `Value` go on the individual types. For example, string related methods go on `ast::Str`
|
||||
|
||||
* `parse` changed to `Root::parse`
|
||||
|
||||
* `NODE_OR_DEFAULT` is removed
|
||||
|
||||
* `NODE_KEY` -> `NODE_ATTRPATH`, `NODE_KEY_VALUE` -> `NODE_ATTRPATH_VALUE`
|
||||
|
||||
* `BinOp::IsSet` is removed since it's actually not a normal binary operator.
|
||||
|
||||
* `NODE_SELECT` is flattened in favor of `NODE_ATTRPATH` which consists of multiple Attrs, including `a.b.c` and `a.b.c or expr`
|
||||
|
||||
* `a or b` is Apply, not "OrDefault", which matches the result of Nix. Fixes #23
|
||||
|
||||
* `${` is considered invalid at Expr places now, which matches the result of Nix.
|
||||
|
||||
* remove `TOKEN_DYNAMIC_START` and `TOKEN_DYNAMIC_END`
|
||||
|
||||
* rowan 15.0
|
||||
|
||||
* various other parsing fixes
|
||||
|
||||
## [v0.10.2] - 2022-06-14
|
||||
|
||||
* Correctly parse every possible case of path-interpolation in Nix code ([#85](https://github.com/nix-community/rnix-parser/issues/85)).
|
||||
|
||||
(from [@ncfavier](https://github.com/ncfavier)).
|
||||
|
||||
## [v0.10.1] - 2021-12-06
|
||||
|
||||
### Fixed
|
||||
|
||||
* Trivia tokens (i.e. ` `, `\r`, `\n` etc.) are no longer misplaced around path-expressions.
|
||||
This is a regression from `v0.10.0` which would've broken `nixpkgs-fmt`.
|
||||
|
||||
(from [@Ma27](https://github.com/Ma27)).
|
||||
|
||||
## [v0.10.0] - 2021-11-30
|
||||
|
||||
### Added
|
||||
|
||||
* Support for the new path-interpolation syntax from Nix 2.4 was added. The parser
|
||||
now correctly detects
|
||||
|
||||
```nix
|
||||
./foo/${bar}
|
||||
```
|
||||
|
||||
as valid code and parses it down to
|
||||
|
||||
```
|
||||
NODE_ROOT 0..13 {
|
||||
NODE_PATH_WITH_INTERPOL 0..12 {
|
||||
TOKEN_PATH("./foo/") 0..6
|
||||
NODE_STRING_INTERPOL 6..12 {
|
||||
TOKEN_INTERPOL_START("${") 6..8
|
||||
NODE_IDENT 8..11 {
|
||||
TOKEN_IDENT("bar") 8..11
|
||||
}
|
||||
TOKEN_INTERPOL_END("}") 11..12
|
||||
}
|
||||
}
|
||||
TOKEN_WHITESPACE("\n") 12..13
|
||||
}
|
||||
```
|
||||
|
||||
Paths without interpolation are still within `NODE_LITERAL` for backwards-compatibility.
|
||||
|
||||
(from [@Ma27](https://github.com/Ma27)).
|
||||
|
||||
* `rnix::types::ParsedTypeError` now implements the `Debug`, `Copy`, `Clone`, `fmt::Display` traits (from [@NerdyPepper](https://github.com/NerdyPepper)).
|
||||
|
||||
* During development, the parser can be tested like this:
|
||||
|
||||
```
|
||||
$ cargo run --example from-stdin <<< 'builtins.map (x: x * x) [ 1 2 3 ]'
|
||||
```
|
||||
|
||||
(from [@efx](https://github.com/efx)).
|
||||
|
||||
### Changed
|
||||
|
||||
* A few more examples of invalid `inherit`-expressions are correctly marked as such (from [@oberblastmeister](https://github.com/oberblastmeister)).
|
||||
|
||||
* `rnix::types::BinOp::operator()` now returns `Option<BinOpKind>` rather than unwrapping the
|
||||
value (from [@NerdyPepper](https://github.com/NerdyPepper)).
|
||||
|
||||
If your code worked fine before, it will be sufficient to add a `.unwrap()` since this was
|
||||
what happened before within `::operator()`.
|
||||
|
||||
* Duplicated arguments in the argument-pattern syntax are now marked as parser error. E.g.
|
||||
|
||||
```nix
|
||||
{ a, a }: a
|
||||
```
|
||||
|
||||
now produces an error (from [@Ma27](https://github.com/Ma27)).
|
||||
|
||||
* Floats without trailing zeroes (e.g. `.5`) are no longer marked as syntax error (from [@Ma27](https://github.com/Ma27)).
|
||||
|
||||
## [v0.9.1] - 2021-09-23
|
||||
|
||||
### Changed
|
||||
|
||||
* Fixed a memory leak while parsing `inherit`-expressions with invalid syntax (from [@Ma27](https://github.com/Ma27/)).
|
||||
|
||||
[Unreleased]: https://github.com/nix-community/rnix-parser/compare/v0.14.0...master
|
||||
[v0.14.0]: https://github.com/nix-community/rnix-parser/compare/v0.13.0...v0.14.0
|
||||
[v0.13.0]: https://github.com/nix-community/rnix-parser/compare/v0.12.0...v0.13.0
|
||||
[v0.12.0]: https://github.com/nix-community/rnix-parser/compare/v0.11.0...v0.12.0
|
||||
[v0.11.0]: https://github.com/nix-community/rnix-parser/compare/v0.10.2...v0.11.0
|
||||
[v0.10.2]: https://github.com/nix-community/rnix-parser/compare/v0.10.1...v0.10.2
|
||||
[v0.10.1]: https://github.com/nix-community/rnix-parser/compare/v0.10.0...v0.10.1
|
||||
[v0.10.0]: https://github.com/nix-community/rnix-parser/compare/v0.9.1...v0.10.0
|
||||
[v0.9.1]: https://github.com/nix-community/rnix-parser/compare/v0.9.0...v0.9.1
|
||||
+660
@@ -0,0 +1,660 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alloca"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anes"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "ciborium"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"ciborium-ll",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-io"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-ll"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"half",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e34525d5bbbd55da2bb745d34b36121baac88d07619a9a09cfcf4a6c0832785"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59a20016a20a3da95bef50ec7238dbd09baeef4311dcdd38ec15aba69812fb61"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
|
||||
|
||||
[[package]]
|
||||
name = "countme"
|
||||
version = "3.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d883447757bb0ee46f233e9dc22eb84d93a9508c9b868687b274fc431d886bf"
|
||||
dependencies = [
|
||||
"alloca",
|
||||
"anes",
|
||||
"cast",
|
||||
"ciborium",
|
||||
"clap",
|
||||
"criterion-plot",
|
||||
"itertools",
|
||||
"num-traits",
|
||||
"oorandom",
|
||||
"page_size",
|
||||
"plotters",
|
||||
"rayon",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tinytemplate",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion-plot"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed943f81ea2faa8dcecbbfa50164acf95d555afec96a27871663b300e387b2e4"
|
||||
dependencies = [
|
||||
"cast",
|
||||
"itertools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "dissimilar"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "expect-test"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63af43ff4431e848fb47472a920f14fa71c24de13255a5692e93d4e90302acb0"
|
||||
dependencies = [
|
||||
"dissimilar",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.180"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
version = "11.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
||||
|
||||
[[package]]
|
||||
name = "page_size"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"plotters-backend",
|
||||
"plotters-svg",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plotters-backend"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
|
||||
|
||||
[[package]]
|
||||
name = "plotters-svg"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
|
||||
dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||
|
||||
[[package]]
|
||||
name = "rnix"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"criterion",
|
||||
"expect-test",
|
||||
"rowan",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rowan"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "417a3a9f582e349834051b8a10c8d71ca88da4211e4093528e36b9845f6b5f21"
|
||||
dependencies = [
|
||||
"countme",
|
||||
"hashbrown",
|
||||
"rustc-hash",
|
||||
"text-size",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.149"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.114"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "text-size"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233"
|
||||
|
||||
[[package]]
|
||||
name = "tinytemplate"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdea86ddd5568519879b8187e1cf04e24fce28f7fe046ceecbce472ff19a2572"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c15e1b46eff7c6c91195752e0eeed8ef040e391cdece7c25376957d5f15df22"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439"
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "rnix"
|
||||
version = "0.14.0"
|
||||
authors = ["jD91mZM2 <me@krake.one>"]
|
||||
build = false
|
||||
autolib = false
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
description = "A Nix parser written in Rust"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/nix-community/rnix-parser"
|
||||
|
||||
[lib]
|
||||
name = "rnix"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[example]]
|
||||
name = "dump-ast"
|
||||
path = "examples/dump-ast.rs"
|
||||
|
||||
[[example]]
|
||||
name = "error-report"
|
||||
path = "examples/error-report.rs"
|
||||
|
||||
[[example]]
|
||||
name = "from-stdin"
|
||||
path = "examples/from-stdin.rs"
|
||||
|
||||
[[example]]
|
||||
name = "list-fns"
|
||||
path = "examples/list-fns.rs"
|
||||
|
||||
[[example]]
|
||||
name = "preserve"
|
||||
path = "examples/preserve.rs"
|
||||
|
||||
[[example]]
|
||||
name = "test-nixpkgs"
|
||||
path = "examples/test-nixpkgs.rs"
|
||||
|
||||
[[bench]]
|
||||
name = "all-packages"
|
||||
path = "benches/all-packages.rs"
|
||||
harness = false
|
||||
|
||||
[dependencies.rowan]
|
||||
version = "0.16"
|
||||
|
||||
[dev-dependencies.criterion]
|
||||
version = "0.8"
|
||||
|
||||
[dev-dependencies.expect-test]
|
||||
version = "1.5.1"
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
authors = ["jD91mZM2 <me@krake.one>"]
|
||||
description = "A Nix parser written in Rust"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "rnix"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/nix-community/rnix-parser"
|
||||
version = "0.14.0"
|
||||
|
||||
[[bench]]
|
||||
harness = false
|
||||
name = "all-packages"
|
||||
|
||||
[dependencies]
|
||||
rowan = "0.16"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.8"
|
||||
expect-test = "1.5.1"
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 jD91mZM2
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
# Migration Guide
|
||||
|
||||
## Migrating to 0.13.0
|
||||
|
||||
### Path types are now distinguished at the type level
|
||||
|
||||
Previously, all Nix paths were parsed into a single `TOKEN_PATH` / `NODE_PATH` type, requiring runtime string inspection to determine the path variant. Now, paths are distinguished at the type level:
|
||||
|
||||
| Path Type | Example | Token | Node |
|
||||
|-----------|---------|-------|------|
|
||||
| Absolute | `/foo/bar` | `TOKEN_PATH_ABS` | `NODE_PATH_ABS` |
|
||||
| Relative | `./foo`, `../bar`, `foo/bar` | `TOKEN_PATH_REL` | `NODE_PATH_REL` |
|
||||
| Home-relative | `~/foo` | `TOKEN_PATH_HOME` | `NODE_PATH_HOME` |
|
||||
| Search | `<nixpkgs>` | `TOKEN_PATH_SEARCH` | `NODE_PATH_SEARCH` |
|
||||
|
||||
#### Updating `SyntaxKind` matches
|
||||
|
||||
```rust
|
||||
// Before:
|
||||
match kind {
|
||||
SyntaxKind::TOKEN_PATH => { /* ... */ }
|
||||
SyntaxKind::NODE_PATH => { /* ... */ }
|
||||
}
|
||||
|
||||
// After:
|
||||
match kind {
|
||||
SyntaxKind::TOKEN_PATH_ABS
|
||||
| SyntaxKind::TOKEN_PATH_REL
|
||||
| SyntaxKind::TOKEN_PATH_HOME
|
||||
| SyntaxKind::TOKEN_PATH_SEARCH => { /* ... */ }
|
||||
|
||||
SyntaxKind::NODE_PATH_ABS
|
||||
| SyntaxKind::NODE_PATH_REL
|
||||
| SyntaxKind::NODE_PATH_HOME
|
||||
| SyntaxKind::NODE_PATH_SEARCH => { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
#### Updating `Expr` matches
|
||||
|
||||
The `Expr` enum no longer has a `Path` variant. Instead, there are four separate variants:
|
||||
|
||||
```rust
|
||||
// Before:
|
||||
match expr {
|
||||
ast::Expr::Path(p) => {
|
||||
// Had to inspect the string to determine path type
|
||||
}
|
||||
// ...
|
||||
}
|
||||
|
||||
// After:
|
||||
match expr {
|
||||
ast::Expr::PathAbs(p) => { /* absolute path like /foo/bar */ }
|
||||
ast::Expr::PathRel(p) => { /* relative path like ./foo */ }
|
||||
ast::Expr::PathHome(p) => { /* home path like ~/foo */ }
|
||||
ast::Expr::PathSearch(p) => { /* search path like <nixpkgs> */ }
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Using the `Path` enum
|
||||
|
||||
If you need to handle all path types uniformly, use the `ast::Path` enum:
|
||||
|
||||
```rust
|
||||
use rnix::ast::{AstNode, Path};
|
||||
|
||||
// Cast any path node to the Path enum
|
||||
if let Some(path) = Path::cast(node) {
|
||||
match path {
|
||||
Path::PathAbs(p) => { /* ... */ }
|
||||
Path::PathRel(p) => { /* ... */ }
|
||||
Path::PathHome(p) => { /* ... */ }
|
||||
Path::PathSearch(p) => { /* ... */ }
|
||||
}
|
||||
|
||||
// Or use helper methods
|
||||
if path.is_search() {
|
||||
// Search paths don't support interpolation
|
||||
}
|
||||
|
||||
// Get parts from any path type
|
||||
let parts = path.parts();
|
||||
}
|
||||
```
|
||||
|
||||
#### `Path::parts()` return type change
|
||||
|
||||
The `parts()` method now returns `Vec<InterpolPart<PathContent>>` instead of `impl Iterator<...>`:
|
||||
|
||||
```rust
|
||||
// Before:
|
||||
let mut parts = path.parts();
|
||||
let first = parts.next();
|
||||
|
||||
// After:
|
||||
let parts = path.parts();
|
||||
let first = parts.first();
|
||||
```
|
||||
|
||||
### Double slashes in paths are now syntax errors
|
||||
|
||||
Paths containing `//` (e.g., `foo//bar`) are now tokenized as `TOKEN_ERROR`. This matches Nix's behavior where such paths are invalid.
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
# rnix-parser [](http://crates.io/crates/rnix) [](https://matrix.to/#/#rnix-lsp:matrix.org)
|
||||
|
||||
rnix is a parser for the [Nix language](https://nixos.org/nix/) written in Rust.
|
||||
|
||||
This can be used to manipulate the Nix AST and can for example be used for:
|
||||
|
||||
- Interactively render Nix on a GUI
|
||||
- Formatting Nix code
|
||||
- Rename identifiers
|
||||
|
||||
and a lot more!
|
||||
|
||||
rnix nowadays uses [@matklad](https://github.com/matklad)'s
|
||||
[rowan](https://crates.io/crates/rowan) crate to ensure:
|
||||
|
||||
- all span information is preserved, meaning you can use the AST to for
|
||||
example apply highlighting
|
||||
- printing out the AST prints out 100% the original code. This is not an
|
||||
over-exaggeration, even completely invalid nix code such as this README can
|
||||
be intact after a parsing session (though the AST will mark errnous nodes)
|
||||
- easy ways to walk the tree without resorting to recursion
|
||||
|
||||
## Demo
|
||||
|
||||
Examples can be found in the `examples/` directory.
|
||||
|
||||
You may also want to see
|
||||
[nix-explorer](https://gitlab.com/jD91mZM2/nix-explorer): An example
|
||||
that highlights AST nodes in Nix code. This demonstrates how
|
||||
whitespaces and commands are preserved.
|
||||
|
||||
## Hacking
|
||||
|
||||
Tests can be run with `cargo test`.
|
||||
|
||||
In order to update all `.expect`-files to the currently expected results,
|
||||
you may run `UPDATE_TESTS=1 cargo test`.
|
||||
|
||||
You can parse Nix expressions from standard input using the `from-stdin` example.
|
||||
To try that, run the following in your shell:
|
||||
|
||||
```sh
|
||||
echo "[hello nix]" | cargo run --quiet --example from-stdin
|
||||
```
|
||||
|
||||
## Release Checklist
|
||||
|
||||
* Ensure that all PRs that were scheduled for the release are merged (or optionally move
|
||||
them to another milestone).
|
||||
* Close the milestone for the release (if any).
|
||||
* Run `cargo test` on `master` (or the branch to release from) with all changes being pulled in.
|
||||
* Apply the following patch to [nixpkgs-fmt](https://github.com/nix-community/nixpkgs-fmt):
|
||||
```diff
|
||||
diff --git a/Cargo.toml b/Cargo.toml
|
||||
index 0891350..edad471 100644
|
||||
--- a/Cargo.toml
|
||||
+++ b/Cargo.toml
|
||||
@@ -13,6 +13,9 @@ repository = "https://github.com/nix-community/nixpkgs-fmt"
|
||||
[workspace]
|
||||
members = [ "./wasm" ]
|
||||
|
||||
+[patch.crates-io]
|
||||
+rnix = { path = "/home/ma27/Projects/rnix-parser" }
|
||||
+
|
||||
[dependencies]
|
||||
rnix = "0.9.0"
|
||||
smol_str = "0.1.17"
|
||||
```
|
||||
|
||||
and run `cargo test`.
|
||||
|
||||
While it's planned to add [fuzzing to `rnix-parser` as well](https://github.com/nix-community/rnix-parser/issues/32),
|
||||
`nixpkgs-fmt` has a decent test-suite that would've helped to catch regressions in the past.
|
||||
|
||||
__Note:__ API changes are OK (and fixes should be contributed to `nixpkgs-fmt`), behavioral changes
|
||||
are not unless explicitly discussed before.
|
||||
* Update the [CHANGELOG.md](https://github.com/nix-community/rnix-parser/blob/master/CHANGELOG.md).
|
||||
* Bump the version number in [Cargo.toml](https://github.com/nix-community/rnix-parser/blob/master/Cargo.toml) & re-run `cargo build` to refresh the lockfile.
|
||||
* Commit & run `nix build`.
|
||||
* Tag the release and push everything.
|
||||
* As soon as the CI has completed, run `cargo publish`.
|
||||
|
||||
# RIP jd91mzm2
|
||||
|
||||
Sadly, the original author of this project, [@jD91mZM2 has passed
|
||||
away](https://www.redox-os.org/news/open-source-mental-health/). His online
|
||||
presence was anonymous and what we have left is his code. This is but one of
|
||||
his many repos that he contributed to.
|
||||
+24853
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,15 @@
|
||||
use criterion::{criterion_group, criterion_main, Criterion, Throughput};
|
||||
|
||||
fn all_packages(c: &mut Criterion) {
|
||||
let input = include_str!("all-packages.nix");
|
||||
let mut group = c.benchmark_group("all-packages");
|
||||
group.throughput(Throughput::Bytes(input.len() as u64));
|
||||
group.sample_size(30);
|
||||
group.bench_with_input("all-packages", input, move |b, input| {
|
||||
b.iter(|| rnix::Root::parse(input))
|
||||
});
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(benches, all_packages);
|
||||
criterion_main!(benches);
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
let
|
||||
flake-compat = builtins.fetchTarball {
|
||||
url = "https://github.com/edolstra/flake-compat/archive/12c64ca55c1014cdc1b16ed5a804aa8576601ff2.tar.gz";
|
||||
sha256 = "0jm6nzb83wa6ai17ly9fzpqc40wg1viib8klq8lby54agpl213w5";
|
||||
};
|
||||
in
|
||||
(import flake-compat {
|
||||
src = ./.;
|
||||
}).defaultNix.default
|
||||
@@ -0,0 +1,24 @@
|
||||
use std::{env, fs};
|
||||
|
||||
fn main() {
|
||||
let mut iter = env::args().skip(1).peekable();
|
||||
if iter.peek().is_none() {
|
||||
eprintln!("Usage: dump-ast <file>");
|
||||
return;
|
||||
}
|
||||
for file in iter {
|
||||
let content = match fs::read_to_string(file) {
|
||||
Ok(content) => content,
|
||||
Err(err) => {
|
||||
eprintln!("error reading file: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let parse = rnix::Root::parse(&content);
|
||||
|
||||
for error in parse.errors() {
|
||||
println!("error: {}", error);
|
||||
}
|
||||
println!("{:#?}", parse.tree());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
use rnix::ParseError;
|
||||
use std::{env, fs};
|
||||
|
||||
fn main() {
|
||||
let file = match env::args().nth(1) {
|
||||
Some(file) => file,
|
||||
None => {
|
||||
eprintln!("Usage: error-report <file>");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let content = match fs::read_to_string(&file) {
|
||||
Ok(content) => content,
|
||||
Err(err) => {
|
||||
eprintln!("error reading file: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let ast = rnix::Root::parse(&content);
|
||||
for error in ast.errors() {
|
||||
let range = match error {
|
||||
ParseError::Unexpected(range) => range,
|
||||
ParseError::UnexpectedExtra(range) => range,
|
||||
ParseError::UnexpectedWanted(_, range, _) => range,
|
||||
ParseError::UnexpectedDoubleBind(range) => range,
|
||||
ParseError::DuplicatedArgs(range, _) => range,
|
||||
err => {
|
||||
eprintln!("error: {}", err);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
eprintln!("----- ERROR -----");
|
||||
eprintln!("{}", error);
|
||||
let start = usize::from(range.start());
|
||||
let start_row = content[..start].lines().count() - 1;
|
||||
let start_line = content[..start].rfind('\n').map(|i| i + 1).unwrap_or(0);
|
||||
let start_col = content[start_line..start].chars().count();
|
||||
let end = usize::from(range.end());
|
||||
let end_row = content[..end].lines().count() - 1;
|
||||
let end_line = content[..end].rfind('\n').map(|i| i + 1).unwrap_or(0);
|
||||
let end_col = content[end_line..end].chars().count();
|
||||
|
||||
let mut line_len = 1;
|
||||
let mut line = end_row;
|
||||
while line >= 10 {
|
||||
line /= 10;
|
||||
line_len += 1;
|
||||
}
|
||||
|
||||
let i = start_row.saturating_sub(1);
|
||||
for (i, line) in content.lines().enumerate().skip(i).take(end_row - i + 1) {
|
||||
println!("{:line_len$} {}", i + 1, line, line_len = line_len);
|
||||
if i >= start_row && i <= end_row {
|
||||
print!("{:line_len$} ", "", line_len = line_len);
|
||||
let mut end_col = if i == end_row { end_col } else { line.chars().count() };
|
||||
if i == start_row {
|
||||
print!("{:indent$}", "", indent = start_col);
|
||||
end_col -= start_col;
|
||||
}
|
||||
for _ in 0..end_col {
|
||||
print!("^");
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
use std::{io, io::Read};
|
||||
|
||||
fn main() {
|
||||
let mut content = String::new();
|
||||
io::stdin().read_to_string(&mut content).expect("could not read nix from stdin");
|
||||
let ast = rnix::Root::parse(&content);
|
||||
|
||||
for error in ast.errors() {
|
||||
println!("error: {}", error);
|
||||
}
|
||||
|
||||
println!("{:#?}", ast.tree());
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
use std::{env, error::Error, fs};
|
||||
|
||||
use rnix::{
|
||||
ast::{self, AstToken, HasEntry},
|
||||
match_ast, NodeOrToken, SyntaxNode,
|
||||
};
|
||||
use rowan::ast::AstNode;
|
||||
|
||||
macro_rules! single_match {
|
||||
($expression:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )? => $captured:expr) => {
|
||||
match $expression {
|
||||
$( $pattern )|+ $( if $guard )? => Some($captured),
|
||||
_ => None
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let file = match env::args().nth(1) {
|
||||
Some(file) => file,
|
||||
None => {
|
||||
eprintln!("Usage: list-fns <file>");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
let content = fs::read_to_string(&file)?;
|
||||
let ast = rnix::Root::parse(&content).ok()?;
|
||||
let expr = ast.expr().unwrap();
|
||||
let set = match expr {
|
||||
ast::Expr::AttrSet(set) => set,
|
||||
_ => return Err("root isn't a set".into()),
|
||||
};
|
||||
for entry in set.entries() {
|
||||
if let ast::Entry::AttrpathValue(attrpath_value) = entry {
|
||||
if let Some(ast::Expr::Lambda(lambda)) = attrpath_value.value() {
|
||||
let attrpath = attrpath_value.attrpath().unwrap();
|
||||
let ident = attrpath.attrs().last().and_then(|attr| match attr {
|
||||
ast::Attr::Ident(ident) => Some(ident),
|
||||
_ => None,
|
||||
});
|
||||
let s = ident.as_ref().map_or_else(
|
||||
|| "error".to_string(),
|
||||
|ident| ident.ident_token().unwrap().text().to_string(),
|
||||
);
|
||||
println!("Function name: {}", s);
|
||||
{
|
||||
let comments = comments_before(attrpath_value.syntax());
|
||||
if !comments.is_empty() {
|
||||
println!("--> Doc: {comments}");
|
||||
}
|
||||
}
|
||||
|
||||
let mut value = Some(lambda);
|
||||
while let Some(lambda) = value {
|
||||
let s = lambda
|
||||
.param()
|
||||
.as_ref()
|
||||
.map_or_else(|| "error".to_string(), |param| param.to_string());
|
||||
println!("-> Param: {}", s);
|
||||
{
|
||||
let comments = comments_before(lambda.syntax());
|
||||
if !comments.is_empty() {
|
||||
println!("--> Doc: {comments}");
|
||||
}
|
||||
}
|
||||
value =
|
||||
single_match!(lambda.body().unwrap(), ast::Expr::Lambda(lambda) => lambda);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn comments_before(node: &SyntaxNode) -> String {
|
||||
node.siblings_with_tokens(rowan::Direction::Prev)
|
||||
// rowan always returns the first node for some reason
|
||||
.skip(1)
|
||||
.map_while(|element| match element {
|
||||
NodeOrToken::Token(token) => match_ast! {
|
||||
match token {
|
||||
ast::Comment(it) => Some(Some(it)),
|
||||
ast::Whitespace(_) => Some(None),
|
||||
_ => None,
|
||||
}
|
||||
},
|
||||
_ => None,
|
||||
})
|
||||
.flatten()
|
||||
.map(|s| s.text().trim().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n ")
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
use std::{env, fs};
|
||||
|
||||
fn main() {
|
||||
let mut iter = env::args().skip(1).peekable();
|
||||
if iter.peek().is_none() {
|
||||
eprintln!("Usage: preserve <file>");
|
||||
return;
|
||||
}
|
||||
for file in iter {
|
||||
let content = match fs::read_to_string(file) {
|
||||
Ok(content) => content,
|
||||
Err(err) => {
|
||||
eprintln!("error reading file: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
print!("{}", rnix::Root::parse(&content).tree());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
use std::{env, error::Error, fs, path::Path};
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let path = env::var("NIX_PATH")?;
|
||||
let nixpkgs =
|
||||
path.split(':').find(|s| s.starts_with("nixpkgs=")).ok_or("no store path found")?;
|
||||
|
||||
println!("Nix store path: {}", nixpkgs);
|
||||
|
||||
recurse(Path::new(&nixpkgs["nixpkgs=".len()..]))
|
||||
}
|
||||
fn recurse(path: &Path) -> Result<(), Box<dyn Error>> {
|
||||
if path.metadata()?.is_file() {
|
||||
if path.extension().and_then(|s| s.to_str()) != Some("nix") {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
println!("Checking {}", path.display());
|
||||
let original = fs::read_to_string(path)?;
|
||||
if original.trim().is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let parsed = rnix::Root::parse(&original).tree().to_string();
|
||||
if original != parsed {
|
||||
eprintln!("Original input does not match parsed output!");
|
||||
println!("Input:");
|
||||
println!("----------");
|
||||
println!("{}", original);
|
||||
println!("----------");
|
||||
println!("Output:");
|
||||
println!("----------");
|
||||
println!("{}", parsed);
|
||||
println!("----------");
|
||||
return Err("parsing error".into());
|
||||
}
|
||||
return Ok(());
|
||||
} else {
|
||||
for entry in path.read_dir()? {
|
||||
let entry = entry?;
|
||||
if entry.file_type()?.is_symlink() {
|
||||
continue;
|
||||
}
|
||||
recurse(&entry.path())?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"nodes": {
|
||||
"fenix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1769670296,
|
||||
"narHash": "sha256-0OzQzsufPwHUTJMAgVf+TXPFG+8x5xrYwz1/M/kI+7o=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "b00052920f71c43987b44c804196c083eb0fa1bf",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"import-cargo": {
|
||||
"locked": {
|
||||
"lastModified": 1594305518,
|
||||
"narHash": "sha256-frtArgN42rSaEcEOYWg8sVPMUK+Zgch3c+wejcpX3DY=",
|
||||
"owner": "edolstra",
|
||||
"repo": "import-cargo",
|
||||
"rev": "25d40be4a73d40a2572e0cc233b83253554f06c5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "import-cargo",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1769461804,
|
||||
"narHash": "sha256-msG8SU5WsBUfVVa/9RPLaymvi5bI8edTavbIq3vRlhI=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "bfc1b8a4574108ceef22f02bafcf6611380c100d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"fenix": "fenix",
|
||||
"import-cargo": "import-cargo",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"utils": "utils"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1769526882,
|
||||
"narHash": "sha256-64CZX2vKsfSLKnTCZ0pbc1jPvixWWwuT382g3QHBz5c=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "40cda2f4bd585c1cb99ae4eb025968b8dc2eec42",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "rust-lang",
|
||||
"ref": "nightly",
|
||||
"repo": "rust-analyzer",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
{
|
||||
description = "Rust-based parser for nix files";
|
||||
|
||||
inputs = {
|
||||
utils.url = "github:numtide/flake-utils";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
import-cargo.url = "github:edolstra/import-cargo";
|
||||
fenix = {
|
||||
url = "github:nix-community/fenix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, utils, import-cargo, fenix }:
|
||||
{
|
||||
overlay = final: prev: let
|
||||
target = final.stdenv.hostPlatform.rust.rustcTarget;
|
||||
flags = "--release --offline --target ${target}";
|
||||
inherit (import-cargo.builders) importCargo;
|
||||
in {
|
||||
rnix-parser = final.stdenv.mkDerivation {
|
||||
pname = "rnix-parser";
|
||||
version = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.version;
|
||||
src = final.lib.cleanSource ./.;
|
||||
nativeBuildInputs = with final; [
|
||||
rustc cargo
|
||||
(importCargo { lockFile = ./Cargo.lock; inherit (final) pkgs; }).cargoHome
|
||||
];
|
||||
|
||||
outputs = [ "out" "doc" ];
|
||||
doCheck = true;
|
||||
|
||||
buildPhase = ''
|
||||
cargo build ${flags}
|
||||
cargo doc ${flags}
|
||||
'';
|
||||
|
||||
checkPhase = ''
|
||||
cargo test ${flags}
|
||||
cargo bench
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out/lib
|
||||
mkdir -p $doc/share/doc/rnix
|
||||
|
||||
cp -r ./target/${target}/doc $doc/share/doc/rnix
|
||||
cp ./target/${target}/release/librnix.rlib $out/lib/
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
// utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; overlays = [ self.overlay ]; };
|
||||
fenixPkgs = fenix.packages.${system};
|
||||
nightlyToolchain = fenixPkgs.combine [
|
||||
fenixPkgs.minimal.cargo
|
||||
fenixPkgs.minimal.rustc
|
||||
];
|
||||
in
|
||||
rec {
|
||||
# `nix develop`
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [ rustfmt rustc cargo clippy ];
|
||||
};
|
||||
|
||||
# `nix develop .#fuzz` - for running cargo-fuzz
|
||||
devShells.fuzz = pkgs.mkShell {
|
||||
buildInputs = [
|
||||
nightlyToolchain
|
||||
pkgs.cargo-fuzz
|
||||
];
|
||||
# libFuzzer needs libstdc++
|
||||
LD_LIBRARY_PATH = "${pkgs.stdenv.cc.cc.lib}/lib";
|
||||
};
|
||||
|
||||
# Backwards compat
|
||||
devShell = devShells.default;
|
||||
|
||||
packages.rnix-parser = pkgs.rnix-parser;
|
||||
defaultPackage = packages.rnix-parser;
|
||||
}
|
||||
);
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
use_small_heuristics = "Max"
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
let
|
||||
flake-compat = builtins.fetchTarball {
|
||||
url = "https://github.com/edolstra/flake-compat/archive/99f1c2157fba4bfe6211a321fd0ee43199025dbf.tar.gz";
|
||||
sha256 = "0x2jn3vrawwv9xp15674wjz9pixwjyj3j771izayl962zziivbx2";
|
||||
};
|
||||
in
|
||||
(import flake-compat {
|
||||
src = ./.;
|
||||
}).shellNix.default
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
//! Provides a type system for the AST, in some sense
|
||||
|
||||
mod expr_ext;
|
||||
mod interpol;
|
||||
mod nodes;
|
||||
mod operators;
|
||||
mod path_util;
|
||||
|
||||
pub use path_util::Path;
|
||||
mod str_util;
|
||||
mod tokens;
|
||||
|
||||
use crate::{NixLanguage, SyntaxKind, SyntaxToken};
|
||||
|
||||
pub use expr_ext::LiteralKind;
|
||||
pub use interpol::*;
|
||||
pub use nodes::*;
|
||||
pub use operators::{BinOpKind, UnaryOpKind};
|
||||
pub use tokens::*;
|
||||
|
||||
pub trait AstNode: rowan::ast::AstNode<Language = NixLanguage> {}
|
||||
|
||||
impl<T> AstNode for T where T: rowan::ast::AstNode<Language = NixLanguage> {}
|
||||
|
||||
pub trait AstToken {
|
||||
fn can_cast(from: SyntaxKind) -> bool
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Cast an untyped token into this strongly-typed token. This will return
|
||||
/// None if the type was not correct.
|
||||
fn cast(from: SyntaxToken) -> Option<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn syntax(&self) -> &SyntaxToken;
|
||||
}
|
||||
|
||||
mod support {
|
||||
use rowan::ast::AstChildren;
|
||||
|
||||
use super::{AstNode, AstToken};
|
||||
use crate::{SyntaxElement, SyntaxKind, SyntaxToken};
|
||||
|
||||
pub(super) fn nth<N: AstNode, NN: AstNode>(parent: &N, n: usize) -> Option<NN> {
|
||||
parent.syntax().children().flat_map(NN::cast).nth(n)
|
||||
}
|
||||
|
||||
pub(super) fn children<N: AstNode, NN: AstNode>(parent: &N) -> AstChildren<NN> {
|
||||
rowan::ast::support::children(parent.syntax())
|
||||
}
|
||||
|
||||
pub(super) fn token<N: AstNode, T: AstToken>(parent: &N) -> Option<T> {
|
||||
children_tokens(parent).next()
|
||||
}
|
||||
|
||||
/// Token untyped
|
||||
pub(super) fn token_u<N: AstNode>(parent: &N, kind: SyntaxKind) -> Option<SyntaxToken> {
|
||||
children_tokens_u(parent).find(|it| it.kind() == kind)
|
||||
}
|
||||
|
||||
pub(super) fn children_tokens<N: AstNode, T: AstToken>(parent: &N) -> impl Iterator<Item = T> {
|
||||
parent
|
||||
.syntax()
|
||||
.children_with_tokens()
|
||||
.filter_map(SyntaxElement::into_token)
|
||||
.filter_map(T::cast)
|
||||
}
|
||||
|
||||
pub(super) fn children_tokens_u<N: AstNode>(parent: &N) -> impl Iterator<Item = SyntaxToken> {
|
||||
parent.syntax().children_with_tokens().filter_map(SyntaxElement::into_token)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
use crate::ast::{self, support::*};
|
||||
|
||||
// this is a separate type because it mixes tokens and nodes
|
||||
// for example, a Str is a node because it can contain nested subexpressions but an Integer is a token.
|
||||
// This means that we have to write it out manually instead of using the macro to create the type for us.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum LiteralKind {
|
||||
Float(ast::Float),
|
||||
Integer(ast::Integer),
|
||||
Uri(ast::Uri),
|
||||
}
|
||||
|
||||
impl ast::Literal {
|
||||
pub fn kind(&self) -> LiteralKind {
|
||||
if let Some(it) = token(self) {
|
||||
return LiteralKind::Float(it);
|
||||
}
|
||||
|
||||
if let Some(it) = token(self) {
|
||||
return LiteralKind::Integer(it);
|
||||
}
|
||||
|
||||
if let Some(it) = token(self) {
|
||||
return LiteralKind::Uri(it);
|
||||
}
|
||||
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum InterpolPart<T> {
|
||||
Literal(T),
|
||||
Interpolation(super::Interpol),
|
||||
}
|
||||
+464
@@ -0,0 +1,464 @@
|
||||
use crate::{NixLanguage, SyntaxKind, SyntaxKind::*, SyntaxNode, SyntaxToken};
|
||||
|
||||
use super::{operators::BinOpKind, support::*, AstNode, UnaryOpKind};
|
||||
use rowan::ast::{AstChildren, AstNode as OtherAstNode};
|
||||
|
||||
pub trait HasEntry: AstNode {
|
||||
fn entries(&self) -> AstChildren<Entry>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
children(self)
|
||||
}
|
||||
|
||||
fn attrpath_values(&self) -> AstChildren<AttrpathValue>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
children(self)
|
||||
}
|
||||
|
||||
fn inherits(&self) -> AstChildren<Inherit>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
children(self)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! node {
|
||||
(
|
||||
#[from($kind:ident)]
|
||||
$(#[$meta:meta])*
|
||||
struct $name:ident;
|
||||
) => {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
$(#[$meta])*
|
||||
pub struct $name(pub(super) SyntaxNode);
|
||||
|
||||
impl std::fmt::Display for $name {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(self.syntax(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl rowan::ast::AstNode for $name {
|
||||
type Language = crate::NixLanguage;
|
||||
|
||||
fn can_cast(kind: crate::SyntaxKind) -> bool {
|
||||
kind == $kind
|
||||
}
|
||||
|
||||
fn cast(from: SyntaxNode) -> Option<Self> {
|
||||
if Self::can_cast(from.kind()) {
|
||||
Some(Self(from))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn syntax(&self) -> &SyntaxNode {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl $name {
|
||||
pub const KIND: SyntaxKind = $kind;
|
||||
}
|
||||
};
|
||||
(
|
||||
#[from($($variant:ident),* $(,)?)]
|
||||
$(#[$meta:meta])*
|
||||
enum $name:ident;
|
||||
) => {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
$(#[$meta])*
|
||||
pub enum $name {
|
||||
$(
|
||||
$variant($variant),
|
||||
)*
|
||||
}
|
||||
|
||||
impl std::fmt::Display for $name {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(self.syntax(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl rowan::ast::AstNode for $name {
|
||||
type Language = NixLanguage;
|
||||
|
||||
fn can_cast(kind: SyntaxKind) -> bool {
|
||||
matches!(kind, $($variant::KIND)|*)
|
||||
}
|
||||
|
||||
fn cast(syntax: SyntaxNode) -> Option<Self> {
|
||||
let res = match syntax.kind() {
|
||||
$(
|
||||
$variant::KIND => $name::$variant($variant(syntax))
|
||||
),*,
|
||||
_ => return None,
|
||||
};
|
||||
Some(res)
|
||||
}
|
||||
|
||||
fn syntax(&self) -> &SyntaxNode {
|
||||
match self {
|
||||
$(
|
||||
$name::$variant(it) => &it.0,
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$(
|
||||
impl From<$variant> for $name {
|
||||
fn from(node: $variant) -> $name { $name::$variant(node) }
|
||||
}
|
||||
|
||||
impl TryFrom<$name> for $variant {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(node: $name) -> Result<$variant, ()> {
|
||||
match node {
|
||||
$name::$variant(it) => Ok(it),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! tg {
|
||||
(
|
||||
$(#[doc=$doc:tt])?
|
||||
$name:ident,
|
||||
$token:tt
|
||||
) => {
|
||||
$(#[doc=$doc])?
|
||||
pub fn $name(&self) -> Option<SyntaxToken> {
|
||||
token_u(self, T![$token])
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! ng {
|
||||
(
|
||||
$(#[$meta:meta])*
|
||||
$name:ident,
|
||||
$ty:ty,
|
||||
$i:expr
|
||||
) => {
|
||||
$(#[$meta])*
|
||||
pub fn $name(&self) -> Option<$ty> {
|
||||
nth(self, $i)
|
||||
}
|
||||
};
|
||||
(
|
||||
$(#[$meta:meta])*
|
||||
$name:ident,
|
||||
[$ty:ty]
|
||||
) => {
|
||||
$(#[$meta])*
|
||||
pub fn $name(&self) -> AstChildren<$ty> {
|
||||
children(self)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
node! { #[from(NODE_LITERAL)] struct Literal; }
|
||||
|
||||
node! {
|
||||
#[from(
|
||||
Apply,
|
||||
Assert,
|
||||
Error,
|
||||
IfElse,
|
||||
Select,
|
||||
Str,
|
||||
PathAbs,
|
||||
PathRel,
|
||||
PathHome,
|
||||
PathSearch,
|
||||
Literal,
|
||||
Lambda,
|
||||
LegacyLet,
|
||||
LetIn,
|
||||
List,
|
||||
BinOp,
|
||||
Paren,
|
||||
Root,
|
||||
AttrSet,
|
||||
UnaryOp,
|
||||
Ident,
|
||||
With,
|
||||
HasAttr,
|
||||
CurPos,
|
||||
)]
|
||||
/// An expression. The fundamental nix ast type.
|
||||
enum Expr;
|
||||
}
|
||||
|
||||
node! {
|
||||
#[from(
|
||||
Ident,
|
||||
Dynamic,
|
||||
Str,
|
||||
)]
|
||||
enum Attr;
|
||||
}
|
||||
|
||||
node! { #[from(NODE_IDENT)] struct Ident; }
|
||||
|
||||
impl Ident {
|
||||
// A NODE_IDENT may either have a nested TOKEN_OR if the identifier was "or" or TOKEN_IDENT for everything else
|
||||
tg! { ident_token, TOKEN_IDENT }
|
||||
}
|
||||
|
||||
node! { #[from(NODE_APPLY)] struct Apply; }
|
||||
|
||||
impl Apply {
|
||||
ng! { lambda, Expr, 0 }
|
||||
ng! { argument, Expr, 1 }
|
||||
}
|
||||
|
||||
node! { #[from(NODE_ASSERT)] struct Assert; }
|
||||
|
||||
impl Assert {
|
||||
tg! { assert_token, assert }
|
||||
ng! { condition, Expr, 0 }
|
||||
ng! { body, Expr, 1 }
|
||||
}
|
||||
|
||||
node! { #[from(NODE_ATTRPATH)] struct Attrpath; }
|
||||
|
||||
impl Attrpath {
|
||||
ng! { attrs, [Attr] }
|
||||
}
|
||||
|
||||
node! { #[from(NODE_DYNAMIC)] struct Dynamic; }
|
||||
|
||||
impl Dynamic {
|
||||
tg! { interpol_start_token, TOKEN_INTERPOL_START }
|
||||
ng! { expr, Expr, 0 }
|
||||
tg! { interpol_end_token, TOKEN_INTERPOL_END }
|
||||
}
|
||||
|
||||
node! { #[from(NODE_ERROR)] struct Error; }
|
||||
|
||||
node! { #[from(NODE_IF_ELSE)] struct IfElse; }
|
||||
|
||||
impl IfElse {
|
||||
tg! { if_token, if }
|
||||
ng! { condition, Expr, 0 }
|
||||
tg! { then_token, then }
|
||||
ng! { body, Expr, 1 }
|
||||
tg! { else_token, else }
|
||||
ng! { else_body, Expr, 2 }
|
||||
}
|
||||
|
||||
node! { #[from(NODE_SELECT)] struct Select; }
|
||||
|
||||
impl Select {
|
||||
ng! { expr, Expr, 0 }
|
||||
tg! { dot_token, . }
|
||||
ng! { attrpath, Attrpath, 0 }
|
||||
tg! { or_token, or }
|
||||
ng! { default_expr, Expr, 1 }
|
||||
}
|
||||
|
||||
node! { #[from(NODE_INHERIT)] struct Inherit; }
|
||||
|
||||
impl Inherit {
|
||||
tg! { inherit_token, inherit }
|
||||
ng! { from, InheritFrom, 0 }
|
||||
ng! { attrs, [Attr] }
|
||||
}
|
||||
|
||||
node! { #[from(NODE_INHERIT_FROM)] struct InheritFrom; }
|
||||
|
||||
impl InheritFrom {
|
||||
tg! { l_paren_token, '(' }
|
||||
ng! { expr, Expr, 0 }
|
||||
tg! { r_paren_token, ')' }
|
||||
}
|
||||
|
||||
node! { #[from(NODE_PATH_ABS)] struct PathAbs; }
|
||||
node! { #[from(NODE_PATH_REL)] struct PathRel; }
|
||||
node! { #[from(NODE_PATH_HOME)] struct PathHome; }
|
||||
node! { #[from(NODE_PATH_SEARCH)] struct PathSearch; }
|
||||
|
||||
node! {
|
||||
#[from(
|
||||
PathAbs,
|
||||
PathRel,
|
||||
PathHome,
|
||||
PathSearch,
|
||||
)]
|
||||
enum Path;
|
||||
}
|
||||
|
||||
node! { #[from(NODE_STRING)] struct Str; }
|
||||
|
||||
node! { #[from(NODE_INTERPOL)] struct Interpol; }
|
||||
|
||||
impl Interpol {
|
||||
ng! { expr, Expr, 0 }
|
||||
}
|
||||
|
||||
node! { #[from(NODE_LAMBDA)] struct Lambda; }
|
||||
|
||||
impl Lambda {
|
||||
ng! { param, Param, 0 }
|
||||
tg! { token_colon, : }
|
||||
ng! { body, Expr, 0 }
|
||||
}
|
||||
|
||||
node! { #[from(NODE_LEGACY_LET)] struct LegacyLet; }
|
||||
|
||||
impl HasEntry for LegacyLet {}
|
||||
|
||||
impl LegacyLet {
|
||||
tg! { let_token, let }
|
||||
tg! { curly_open_token, '{' }
|
||||
tg! { curly_close_token, '}' }
|
||||
}
|
||||
|
||||
node! { #[from(NODE_LET_IN)] struct LetIn; }
|
||||
|
||||
impl HasEntry for LetIn {}
|
||||
|
||||
impl LetIn {
|
||||
tg! { let_token, let }
|
||||
tg! { in_token, in }
|
||||
ng! { body, Expr, 0 }
|
||||
}
|
||||
|
||||
node! { #[from(NODE_LIST)] struct List; }
|
||||
|
||||
impl List {
|
||||
tg! { l_brack_token, '[' }
|
||||
ng! { items, [Expr] }
|
||||
tg! { r_brack_token, ']' }
|
||||
}
|
||||
|
||||
node! { #[from(NODE_BIN_OP)] struct BinOp; }
|
||||
|
||||
impl BinOp {
|
||||
ng! { lhs, Expr, 0 }
|
||||
|
||||
pub fn operator(&self) -> Option<BinOpKind> {
|
||||
children_tokens_u(self).find_map(|t| BinOpKind::from_kind(t.kind()))
|
||||
}
|
||||
|
||||
ng! { rhs, Expr, 1 }
|
||||
}
|
||||
|
||||
node! { #[from(NODE_PAREN)] struct Paren; }
|
||||
|
||||
impl Paren {
|
||||
tg! { l_paren_token, '(' }
|
||||
ng! { expr, Expr, 0 }
|
||||
tg! { r_paren_token, ')' }
|
||||
}
|
||||
|
||||
node! { #[from(NODE_PAT_BIND)] struct PatBind; }
|
||||
|
||||
impl PatBind {
|
||||
ng! { ident, Ident, 0 }
|
||||
}
|
||||
|
||||
node! { #[from(NODE_PAT_ENTRY)] struct PatEntry; }
|
||||
|
||||
impl PatEntry {
|
||||
ng! { ident, Ident, 0 }
|
||||
tg! { question_token, ? }
|
||||
ng! { default, Expr, 1 }
|
||||
}
|
||||
|
||||
node! { #[from(NODE_IDENT_PARAM)] struct IdentParam; }
|
||||
|
||||
impl IdentParam {
|
||||
ng! { ident, Ident, 0 }
|
||||
}
|
||||
|
||||
node! {
|
||||
#[from(
|
||||
Pattern,
|
||||
IdentParam,
|
||||
)]
|
||||
enum Param;
|
||||
}
|
||||
|
||||
node! { #[from(NODE_PATTERN)] struct Pattern; }
|
||||
|
||||
impl Pattern {
|
||||
tg! { at_token, @ }
|
||||
ng! { pat_entries, [PatEntry] }
|
||||
tg! { ellipsis_token, ... }
|
||||
ng! { pat_bind, PatBind, 0 }
|
||||
}
|
||||
|
||||
node! { #[from(NODE_ROOT)] struct Root; }
|
||||
|
||||
impl Root {
|
||||
ng! { expr, Expr, 0 }
|
||||
}
|
||||
|
||||
node! { #[from(NODE_ATTR_SET)] struct AttrSet; }
|
||||
|
||||
impl HasEntry for AttrSet {}
|
||||
|
||||
impl AttrSet {
|
||||
tg! { rec_token, rec }
|
||||
tg! { l_curly_token, '{' }
|
||||
tg! { r_curly_token, '}' }
|
||||
}
|
||||
|
||||
node! {
|
||||
#[from(
|
||||
Inherit,
|
||||
AttrpathValue,
|
||||
)]
|
||||
enum Entry;
|
||||
}
|
||||
|
||||
node! { #[from(NODE_ATTRPATH_VALUE)] struct AttrpathValue; }
|
||||
|
||||
impl AttrpathValue {
|
||||
ng! { attrpath, Attrpath, 0 }
|
||||
tg! { assign_token, = }
|
||||
ng! { value, Expr, 0 }
|
||||
}
|
||||
|
||||
node! { #[from(NODE_UNARY_OP)] struct UnaryOp; }
|
||||
|
||||
impl UnaryOp {
|
||||
pub fn operator(&self) -> Option<UnaryOpKind> {
|
||||
children_tokens_u(self).find_map(|t| UnaryOpKind::from_kind(t.kind()))
|
||||
}
|
||||
ng! { expr, Expr, 0 }
|
||||
}
|
||||
|
||||
node! { #[from(NODE_WITH)] struct With; }
|
||||
|
||||
impl With {
|
||||
tg! { with_token, with }
|
||||
ng! { namespace, Expr, 0 }
|
||||
tg! { semicolon_token, ; }
|
||||
ng! { body, Expr, 1 }
|
||||
}
|
||||
|
||||
node! { #[from(NODE_HAS_ATTR)] struct HasAttr; }
|
||||
|
||||
impl HasAttr {
|
||||
ng! { expr, Expr, 0 }
|
||||
tg! { question_token, ? }
|
||||
ng! { attrpath, Attrpath, 0 }
|
||||
}
|
||||
|
||||
node! { #[from(NODE_CUR_POS)] struct CurPos; }
|
||||
|
||||
impl CurPos {
|
||||
tg! { cur_pos_token, __curPos }
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
use crate::SyntaxKind::{self, *};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum BinOpKind {
|
||||
Concat,
|
||||
Update,
|
||||
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
|
||||
And,
|
||||
Equal,
|
||||
Implication,
|
||||
Less,
|
||||
LessOrEq,
|
||||
More,
|
||||
MoreOrEq,
|
||||
NotEqual,
|
||||
Or,
|
||||
PipeRight,
|
||||
PipeLeft,
|
||||
}
|
||||
|
||||
impl BinOpKind {
|
||||
/// Get the operation kind from a SyntaxKind in the AST
|
||||
pub fn from_kind(token: SyntaxKind) -> Option<Self> {
|
||||
match token {
|
||||
TOKEN_CONCAT => Some(BinOpKind::Concat),
|
||||
TOKEN_UPDATE => Some(BinOpKind::Update),
|
||||
|
||||
TOKEN_ADD => Some(BinOpKind::Add),
|
||||
TOKEN_SUB => Some(BinOpKind::Sub),
|
||||
TOKEN_MUL => Some(BinOpKind::Mul),
|
||||
TOKEN_DIV => Some(BinOpKind::Div),
|
||||
|
||||
TOKEN_AND_AND => Some(BinOpKind::And),
|
||||
TOKEN_EQUAL => Some(BinOpKind::Equal),
|
||||
TOKEN_IMPLICATION => Some(BinOpKind::Implication),
|
||||
TOKEN_LESS => Some(BinOpKind::Less),
|
||||
TOKEN_LESS_OR_EQ => Some(BinOpKind::LessOrEq),
|
||||
TOKEN_MORE => Some(BinOpKind::More),
|
||||
TOKEN_MORE_OR_EQ => Some(BinOpKind::MoreOrEq),
|
||||
TOKEN_NOT_EQUAL => Some(BinOpKind::NotEqual),
|
||||
TOKEN_OR_OR => Some(BinOpKind::Or),
|
||||
|
||||
TOKEN_PIPE_RIGHT => Some(BinOpKind::PipeRight),
|
||||
TOKEN_PIPE_LEFT => Some(BinOpKind::PipeLeft),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum UnaryOpKind {
|
||||
Invert,
|
||||
Negate,
|
||||
}
|
||||
|
||||
impl UnaryOpKind {
|
||||
/// Get the operation kind from a token in the AST
|
||||
pub fn from_kind(kind: SyntaxKind) -> Option<Self> {
|
||||
match kind {
|
||||
TOKEN_INVERT => Some(UnaryOpKind::Invert),
|
||||
TOKEN_SUB => Some(UnaryOpKind::Negate),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
use crate::kinds::SyntaxKind::{TOKEN_PATH_ABS, TOKEN_PATH_HOME, TOKEN_PATH_REL};
|
||||
|
||||
use rowan::{ast::AstNode as OtherAstNode, NodeOrToken};
|
||||
|
||||
pub use super::nodes::Path;
|
||||
use super::{
|
||||
nodes::{PathAbs, PathHome, PathRel, PathSearch},
|
||||
AstToken, InterpolPart, PathContent,
|
||||
};
|
||||
use crate::ast;
|
||||
|
||||
fn extract_path_parts<T: ast::AstNode>(node: &T) -> Vec<InterpolPart<PathContent>> {
|
||||
node.syntax()
|
||||
.children_with_tokens()
|
||||
.map(|child| match child {
|
||||
NodeOrToken::Token(token) => {
|
||||
debug_assert!(matches!(
|
||||
token.kind(),
|
||||
TOKEN_PATH_ABS | TOKEN_PATH_REL | TOKEN_PATH_HOME
|
||||
));
|
||||
InterpolPart::Literal(PathContent::cast(token).unwrap())
|
||||
}
|
||||
NodeOrToken::Node(node) => {
|
||||
InterpolPart::Interpolation(ast::Interpol::cast(node).unwrap())
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
// Direct methods for interpolatable path types
|
||||
impl PathAbs {
|
||||
pub fn parts(&self) -> Vec<InterpolPart<PathContent>> {
|
||||
extract_path_parts(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl PathRel {
|
||||
pub fn parts(&self) -> Vec<InterpolPart<PathContent>> {
|
||||
extract_path_parts(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl PathHome {
|
||||
pub fn parts(&self) -> Vec<InterpolPart<PathContent>> {
|
||||
extract_path_parts(self)
|
||||
}
|
||||
}
|
||||
|
||||
// Direct methods for search path
|
||||
impl PathSearch {
|
||||
/// Get the content of a search path
|
||||
pub fn content(&self) -> Option<PathContent> {
|
||||
self.syntax()
|
||||
.children_with_tokens()
|
||||
.filter_map(|child| child.into_token().and_then(PathContent::cast))
|
||||
.next()
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension methods for the Path enum
|
||||
impl Path {
|
||||
/// Get parts from any path type in a unified way
|
||||
pub fn parts(&self) -> Vec<InterpolPart<PathContent>> {
|
||||
match self {
|
||||
// For interpolatable paths, get their parts
|
||||
Path::PathAbs(p) => p.parts(),
|
||||
Path::PathRel(p) => p.parts(),
|
||||
Path::PathHome(p) => p.parts(),
|
||||
// For search paths, return a single literal component if content exists
|
||||
Path::PathSearch(p) => {
|
||||
if let Some(content) = p.content() {
|
||||
vec![InterpolPart::Literal(content)]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_search(&self) -> bool {
|
||||
matches!(self, Path::PathSearch(_))
|
||||
}
|
||||
|
||||
pub fn is_interpolatable(&self) -> bool {
|
||||
!self.is_search()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rowan::ast::AstNode;
|
||||
|
||||
use super::InterpolPart;
|
||||
use crate::{
|
||||
ast::{self, Path},
|
||||
Root,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_path_types() {
|
||||
// Absolute path
|
||||
let inp = "/foo/bar";
|
||||
let expr = Root::parse(inp).ok().unwrap().expr().unwrap();
|
||||
if let ast::Expr::PathAbs(p) = expr {
|
||||
let path = Path::cast(p.syntax().clone()).unwrap();
|
||||
assert!(path.is_interpolatable());
|
||||
assert!(!path.is_search());
|
||||
}
|
||||
|
||||
// Search path
|
||||
let inp = "<nixpkgs>";
|
||||
let expr = Root::parse(inp).ok().unwrap().expr().unwrap();
|
||||
if let ast::Expr::PathSearch(p) = expr {
|
||||
let path = Path::cast(p.syntax().clone()).unwrap();
|
||||
assert!(!path.is_interpolatable());
|
||||
assert!(path.is_search());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parts() {
|
||||
// Test parts with absolute path
|
||||
let inp = "/foo/bar";
|
||||
let expr = Root::parse(inp).ok().unwrap().expr().unwrap();
|
||||
if let ast::Expr::PathAbs(p) = expr {
|
||||
let path = Path::cast(p.syntax().clone()).unwrap();
|
||||
|
||||
let parts = path.parts();
|
||||
assert_eq!(parts.len(), 1);
|
||||
|
||||
match &parts[0] {
|
||||
InterpolPart::Literal(content) => {
|
||||
assert_eq!(content.text(), "/foo/bar");
|
||||
}
|
||||
_ => panic!("Expected literal part"),
|
||||
}
|
||||
}
|
||||
|
||||
// Test parts with interpolated path
|
||||
let inp = r#"./a/${"hello"}"#;
|
||||
let expr = Root::parse(inp).ok().unwrap().expr().unwrap();
|
||||
if let ast::Expr::PathRel(p) = expr {
|
||||
let path = Path::cast(p.syntax().clone()).unwrap();
|
||||
|
||||
let parts = path.parts();
|
||||
assert_eq!(parts.len(), 2);
|
||||
|
||||
match &parts[0] {
|
||||
InterpolPart::Literal(content) => {
|
||||
assert_eq!(content.text(), "./a/");
|
||||
}
|
||||
_ => panic!("Expected literal part"),
|
||||
}
|
||||
|
||||
match &parts[1] {
|
||||
InterpolPart::Interpolation(_) => {} // Success
|
||||
_ => panic!("Expected interpolation part"),
|
||||
}
|
||||
}
|
||||
|
||||
// Test parts with search path
|
||||
let inp = "<nixpkgs>";
|
||||
let expr = Root::parse(inp).ok().unwrap().expr().unwrap();
|
||||
if let ast::Expr::PathSearch(p) = expr {
|
||||
let path = Path::cast(p.syntax().clone()).unwrap();
|
||||
|
||||
let parts = path.parts();
|
||||
assert_eq!(parts.len(), 1);
|
||||
|
||||
match &parts[0] {
|
||||
InterpolPart::Literal(content) => {
|
||||
assert_eq!(content.text(), "<nixpkgs>");
|
||||
}
|
||||
_ => panic!("Expected literal part"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn direct_method_usage() {
|
||||
// Test direct parts() method on PathAbs
|
||||
let inp = "/foo/bar";
|
||||
let expr = Root::parse(inp).ok().unwrap().expr().unwrap();
|
||||
if let ast::Expr::PathAbs(p) = expr {
|
||||
let parts = p.parts();
|
||||
assert_eq!(parts.len(), 1);
|
||||
|
||||
match &parts[0] {
|
||||
InterpolPart::Literal(content) => {
|
||||
assert_eq!(content.text(), "/foo/bar");
|
||||
}
|
||||
_ => panic!("Expected literal part"),
|
||||
}
|
||||
}
|
||||
|
||||
// Test direct content() method on PathSearch
|
||||
let inp = "<nixpkgs>";
|
||||
let expr = Root::parse(inp).ok().unwrap().expr().unwrap();
|
||||
if let ast::Expr::PathSearch(p) = expr {
|
||||
let content = p.content().expect("Expected content");
|
||||
assert_eq!(content.text(), "<nixpkgs>");
|
||||
}
|
||||
}
|
||||
}
|
||||
+363
@@ -0,0 +1,363 @@
|
||||
use crate::kinds::SyntaxKind::*;
|
||||
use rowan::{ast::AstNode as OtherAstNode, NodeOrToken};
|
||||
|
||||
use crate::ast;
|
||||
|
||||
use super::{support::children_tokens_u, AstToken, InterpolPart, StrContent};
|
||||
|
||||
impl ast::Str {
|
||||
pub fn parts(&self) -> impl Iterator<Item = InterpolPart<StrContent>> {
|
||||
self.syntax().children_with_tokens().filter_map(|child| match child {
|
||||
NodeOrToken::Token(token) if token.kind() == TOKEN_STRING_CONTENT => {
|
||||
Some(InterpolPart::Literal(StrContent::cast(token).unwrap()))
|
||||
}
|
||||
NodeOrToken::Token(token) => {
|
||||
assert!(token.kind() == TOKEN_STRING_START || token.kind() == TOKEN_STRING_END);
|
||||
None
|
||||
}
|
||||
NodeOrToken::Node(node) => {
|
||||
assert_eq!(node.kind(), NODE_INTERPOL);
|
||||
Some(InterpolPart::Interpolation(ast::Interpol::cast(node).unwrap()))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn normalized_parts(&self) -> Vec<InterpolPart<String>> {
|
||||
let multiline = children_tokens_u(self).next().is_some_and(|t| t.text() == "''");
|
||||
let mut is_first_literal = true;
|
||||
let mut at_start_of_line = true;
|
||||
let mut min_indent = 1000000;
|
||||
let mut cur_indent = 0;
|
||||
let mut n = 0;
|
||||
let mut first_is_literal = false;
|
||||
|
||||
let parts: Vec<InterpolPart<StrContent>> = self.parts().collect();
|
||||
|
||||
if multiline {
|
||||
for part in &parts {
|
||||
match part {
|
||||
InterpolPart::Interpolation(_) => {
|
||||
if at_start_of_line {
|
||||
at_start_of_line = false;
|
||||
min_indent = min_indent.min(cur_indent);
|
||||
}
|
||||
n += 1;
|
||||
}
|
||||
InterpolPart::Literal(literal) => {
|
||||
let mut token_text = literal.syntax().text();
|
||||
|
||||
if n == 0 {
|
||||
first_is_literal = true;
|
||||
}
|
||||
|
||||
if is_first_literal && first_is_literal {
|
||||
is_first_literal = false;
|
||||
if let Some(p) = token_text.find('\n') {
|
||||
if token_text[0..p].chars().all(|c| c == ' ') {
|
||||
token_text = &token_text[p + 1..]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for c in token_text.chars() {
|
||||
if at_start_of_line {
|
||||
if c == ' ' {
|
||||
cur_indent += 1;
|
||||
} else if c == '\n' {
|
||||
cur_indent = 0;
|
||||
} else {
|
||||
at_start_of_line = false;
|
||||
min_indent = min_indent.min(cur_indent);
|
||||
}
|
||||
} else if c == '\n' {
|
||||
at_start_of_line = true;
|
||||
cur_indent = 0;
|
||||
}
|
||||
}
|
||||
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut normalized_parts = Vec::new();
|
||||
let mut cur_dropped = 0;
|
||||
let mut i = 0;
|
||||
is_first_literal = true;
|
||||
at_start_of_line = true;
|
||||
|
||||
for part in parts {
|
||||
match part {
|
||||
InterpolPart::Interpolation(interpol) => {
|
||||
at_start_of_line = false;
|
||||
cur_dropped = 0;
|
||||
normalized_parts.push(InterpolPart::Interpolation(interpol));
|
||||
i += 1;
|
||||
}
|
||||
InterpolPart::Literal(literal) => {
|
||||
let mut token_text = literal.syntax().text();
|
||||
|
||||
if multiline {
|
||||
if is_first_literal && first_is_literal {
|
||||
is_first_literal = false;
|
||||
if let Some(p) = token_text.find('\n') {
|
||||
if token_text[0..p].chars().all(|c| c == ' ') {
|
||||
token_text = &token_text[p + 1..];
|
||||
if token_text.is_empty() {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut str = String::new();
|
||||
for c in token_text.chars() {
|
||||
if at_start_of_line {
|
||||
if c == ' ' {
|
||||
if cur_dropped >= min_indent {
|
||||
str.push(c);
|
||||
}
|
||||
cur_dropped += 1;
|
||||
} else if c == '\n' {
|
||||
cur_dropped = 0;
|
||||
str.push(c);
|
||||
} else {
|
||||
at_start_of_line = false;
|
||||
cur_dropped = 0;
|
||||
str.push(c);
|
||||
}
|
||||
} else {
|
||||
str.push(c);
|
||||
if c == '\n' {
|
||||
at_start_of_line = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if i == n - 1 {
|
||||
if let Some(p) = str.rfind('\n') {
|
||||
if str[p + 1..].chars().all(|c| c == ' ') {
|
||||
str.truncate(p + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
normalized_parts.push(InterpolPart::Literal(unescape(&str, multiline)));
|
||||
i += 1;
|
||||
} else {
|
||||
normalized_parts
|
||||
.push(InterpolPart::Literal(unescape(token_text, multiline)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
normalized_parts
|
||||
}
|
||||
}
|
||||
|
||||
/// Interpret escape sequences in the nix string and return the converted value
|
||||
pub fn unescape(input: &str, multiline: bool) -> String {
|
||||
let mut output = String::new();
|
||||
let mut input = input.chars().peekable();
|
||||
loop {
|
||||
match input.next() {
|
||||
None => break,
|
||||
Some('"') if !multiline => break,
|
||||
Some('\\') if !multiline => match input.next() {
|
||||
None => break,
|
||||
Some('n') => output.push('\n'),
|
||||
Some('r') => output.push('\r'),
|
||||
Some('t') => output.push('\t'),
|
||||
Some(c) => output.push(c),
|
||||
},
|
||||
Some('\'') if multiline => match input.next() {
|
||||
None => {
|
||||
output.push('\'');
|
||||
}
|
||||
Some('\'') => match input.peek() {
|
||||
Some('\'') => {
|
||||
input.next().unwrap();
|
||||
output.push_str("''");
|
||||
}
|
||||
Some('$') => {
|
||||
input.next().unwrap();
|
||||
output.push('$');
|
||||
}
|
||||
Some('\\') => {
|
||||
input.next().unwrap();
|
||||
match input.next() {
|
||||
None => break,
|
||||
Some('n') => output.push('\n'),
|
||||
Some('r') => output.push('\r'),
|
||||
Some('t') => output.push('\t'),
|
||||
Some(c) => output.push(c),
|
||||
}
|
||||
}
|
||||
_ => break,
|
||||
},
|
||||
Some(c) => {
|
||||
output.push('\'');
|
||||
output.push(c);
|
||||
}
|
||||
},
|
||||
Some(c) => output.push(c),
|
||||
}
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::Root;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn string_unescapes() {
|
||||
assert_eq!(unescape(r#"Hello\n\"World\" :D"#, false), "Hello\n\"World\" :D");
|
||||
assert_eq!(unescape(r#"\"Hello\""#, false), "\"Hello\"");
|
||||
|
||||
assert_eq!(unescape(r#"Hello''\n'''World''' :D"#, true), "Hello\n''World'' :D");
|
||||
assert_eq!(unescape(r#""Hello""#, true), "\"Hello\"");
|
||||
}
|
||||
#[test]
|
||||
fn parts_leading_ws() {
|
||||
let inp = "''\n hello\n world''";
|
||||
let expr = Root::parse(inp).ok().unwrap().expr().unwrap();
|
||||
match expr {
|
||||
ast::Expr::Str(str) => {
|
||||
assert_eq!(
|
||||
str.normalized_parts(),
|
||||
vec![InterpolPart::Literal("hello\nworld".to_string())]
|
||||
)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn parts_trailing_ws_single_line() {
|
||||
let inp = "''hello ''";
|
||||
let expr = Root::parse(inp).ok().unwrap().expr().unwrap();
|
||||
match expr {
|
||||
ast::Expr::Str(str) => {
|
||||
assert_eq!(
|
||||
str.normalized_parts(),
|
||||
vec![InterpolPart::Literal("hello ".to_string())]
|
||||
)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn parts_trailing_ws_multiline() {
|
||||
let inp = "''hello\n ''";
|
||||
let expr = Root::parse(inp).ok().unwrap().expr().unwrap();
|
||||
match expr {
|
||||
ast::Expr::Str(str) => {
|
||||
assert_eq!(
|
||||
str.normalized_parts(),
|
||||
vec![InterpolPart::Literal("hello\n".to_string())]
|
||||
)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn parts() {
|
||||
use crate::{NixLanguage, SyntaxNode};
|
||||
use rowan::{GreenNodeBuilder, Language};
|
||||
|
||||
fn string_node(content: &str) -> ast::Str {
|
||||
let mut builder = GreenNodeBuilder::new();
|
||||
builder.start_node(NixLanguage::kind_to_raw(NODE_STRING));
|
||||
builder.token(NixLanguage::kind_to_raw(TOKEN_STRING_START), "''");
|
||||
builder.token(NixLanguage::kind_to_raw(TOKEN_STRING_CONTENT), content);
|
||||
builder.token(NixLanguage::kind_to_raw(TOKEN_STRING_END), "''");
|
||||
builder.finish_node();
|
||||
|
||||
ast::Str::cast(SyntaxNode::new_root(builder.finish())).unwrap()
|
||||
}
|
||||
|
||||
let txtin = r#"
|
||||
|trailing-whitespace
|
||||
|trailing-whitespace
|
||||
This is a multiline string :D
|
||||
indented by two
|
||||
\'\'\'\'\
|
||||
''${ interpolation was escaped }
|
||||
two single quotes: '''
|
||||
three single quotes: ''''
|
||||
"#
|
||||
.replace("|trailing-whitespace", "");
|
||||
|
||||
if let [InterpolPart::Literal(lit)] =
|
||||
&ast::Str::normalized_parts(&string_node(txtin.as_str()))[..]
|
||||
{
|
||||
assert_eq!(lit,
|
||||
// Get the below with nix repl
|
||||
" \n \nThis is a multiline string :D\n indented by two\n\\'\\'\\'\\'\\\n${ interpolation was escaped }\ntwo single quotes: ''\nthree single quotes: '''\n"
|
||||
);
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parts_ast() {
|
||||
fn assert_eq_ast_ctn(it: &mut dyn Iterator<Item = InterpolPart<String>>, x: &str) {
|
||||
let tmp = it.next().expect("unexpected EOF");
|
||||
if let InterpolPart::Interpolation(astn) = tmp {
|
||||
assert_eq!(astn.expr().unwrap().syntax().to_string(), x);
|
||||
} else {
|
||||
unreachable!("unexpected literal {:?}", tmp);
|
||||
}
|
||||
}
|
||||
|
||||
let inp = r#"''
|
||||
|
||||
This version of Nixpkgs requires Nix >= ${requiredVersion}, please upgrade:
|
||||
|
||||
- If you are running NixOS, `nixos-rebuild' can be used to upgrade your system.
|
||||
|
||||
- Alternatively, with Nix > 2.0 `nix upgrade-nix' can be used to imperatively
|
||||
upgrade Nix. You may use `nix-env --version' to check which version you have.
|
||||
|
||||
- If you installed Nix using the install script (https://nixos.org/nix/install),
|
||||
it is safe to upgrade by running it again:
|
||||
|
||||
curl -L https://nixos.org/nix/install | sh
|
||||
|
||||
For more information, please see the NixOS release notes at
|
||||
https://nixos.org/nixos/manual or locally at
|
||||
${toString ./nixos/doc/manual/release-notes}.
|
||||
|
||||
If you need further help, see https://nixos.org/nixos/support.html
|
||||
''"#;
|
||||
let expr = Root::parse(inp).ok().unwrap().expr().unwrap();
|
||||
match expr {
|
||||
ast::Expr::Str(s) => {
|
||||
let mut it = s.normalized_parts().into_iter();
|
||||
assert_eq!(
|
||||
it.next().unwrap(),
|
||||
InterpolPart::Literal("\nThis version of Nixpkgs requires Nix >= ".to_string())
|
||||
);
|
||||
assert_eq_ast_ctn(&mut it, "requiredVersion");
|
||||
assert_eq!(it.next().unwrap(), InterpolPart::Literal(
|
||||
", please upgrade:\n\n- If you are running NixOS, `nixos-rebuild' can be used to upgrade your system.\n\n- Alternatively, with Nix > 2.0 `nix upgrade-nix' can be used to imperatively\n upgrade Nix. You may use `nix-env --version' to check which version you have.\n\n- If you installed Nix using the install script (https://nixos.org/nix/install),\n it is safe to upgrade by running it again:\n\n curl -L https://nixos.org/nix/install | sh\n\nFor more information, please see the NixOS release notes at\nhttps://nixos.org/nixos/manual or locally at\n".to_string()
|
||||
));
|
||||
assert_eq_ast_ctn(&mut it, "toString ./nixos/doc/manual/release-notes");
|
||||
assert_eq!(
|
||||
it.next().unwrap(),
|
||||
InterpolPart::Literal(
|
||||
".\n\nIf you need further help, see https://nixos.org/nixos/support.html\n"
|
||||
.to_string()
|
||||
)
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
use core::num;
|
||||
|
||||
use crate::{
|
||||
ast::AstToken,
|
||||
SyntaxKind::{self, *},
|
||||
SyntaxToken,
|
||||
};
|
||||
|
||||
macro_rules! token {
|
||||
(
|
||||
#[from($kind:ident)]
|
||||
$(#[$meta:meta])*
|
||||
struct $name:ident;
|
||||
) => {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
$(#[$meta])*
|
||||
pub struct $name(pub(super) SyntaxToken);
|
||||
|
||||
impl std::fmt::Display for $name {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(self.syntax(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl AstToken for $name {
|
||||
fn can_cast(kind: SyntaxKind) -> bool {
|
||||
$kind == kind
|
||||
}
|
||||
|
||||
fn cast(from: SyntaxToken) -> Option<Self> {
|
||||
if from.kind() == $kind {
|
||||
Some(Self(from))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn syntax(&self) -> &SyntaxToken {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
token! { #[from(TOKEN_WHITESPACE)] struct Whitespace; }
|
||||
|
||||
token! { #[from(TOKEN_COMMENT)] struct Comment; }
|
||||
|
||||
impl Comment {
|
||||
pub fn text(&self) -> &str {
|
||||
let text = self.syntax().text();
|
||||
// Handle both "#..." and "/*...*/" comments.
|
||||
match text.strip_prefix('#') {
|
||||
Some(s) => s,
|
||||
None => text.strip_prefix(r#"/*"#).unwrap().strip_suffix(r#"*/"#).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::ast;
|
||||
use crate::match_ast;
|
||||
use crate::Root;
|
||||
|
||||
use super::*;
|
||||
use rowan::ast::AstNode;
|
||||
|
||||
#[test]
|
||||
fn comment() {
|
||||
let s = "# comment bruh
|
||||
/* this is a multiline comment wow
|
||||
asdfasdf
|
||||
asdfasdf */
|
||||
1 + 1
|
||||
/* last one */
|
||||
";
|
||||
let comments: Vec<String> = Root::parse(s)
|
||||
.ok()
|
||||
.unwrap()
|
||||
.syntax()
|
||||
.children_with_tokens()
|
||||
.filter_map(|e| match e {
|
||||
rowan::NodeOrToken::Token(token) => match_ast! { match token {
|
||||
ast::Comment(it) => Some(it.text().to_string()),
|
||||
_ => None,
|
||||
}},
|
||||
rowan::NodeOrToken::Node(_) => None,
|
||||
})
|
||||
.collect();
|
||||
let expected = vec![
|
||||
" comment bruh",
|
||||
" this is a multiline comment wow\nasdfasdf\nasdfasdf ",
|
||||
" last one ",
|
||||
];
|
||||
|
||||
assert_eq!(comments, expected);
|
||||
}
|
||||
}
|
||||
|
||||
token! { #[from(TOKEN_FLOAT)] struct Float; }
|
||||
|
||||
impl Float {
|
||||
pub fn value(&self) -> Result<f64, num::ParseFloatError> {
|
||||
self.syntax().text().parse()
|
||||
}
|
||||
}
|
||||
|
||||
token! { #[from(TOKEN_INTEGER)] struct Integer; }
|
||||
|
||||
impl Integer {
|
||||
pub fn value(&self) -> Result<i64, num::ParseIntError> {
|
||||
self.syntax().text().parse()
|
||||
}
|
||||
}
|
||||
|
||||
// A literal part of a path (absolute / relative / home / search) without
|
||||
// distinguishing which concrete flavour it was taken from.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct PathContent(pub(super) SyntaxToken);
|
||||
|
||||
impl PathContent {
|
||||
/// Returns the path as a string
|
||||
pub fn text(&self) -> &str {
|
||||
self.syntax().text()
|
||||
}
|
||||
|
||||
/// Returns the kind of path token
|
||||
pub fn path_kind(&self) -> SyntaxKind {
|
||||
self.syntax().kind()
|
||||
}
|
||||
|
||||
/// Returns true if this is an absolute path
|
||||
pub fn is_absolute(&self) -> bool {
|
||||
self.path_kind() == TOKEN_PATH_ABS
|
||||
}
|
||||
|
||||
/// Returns true if this is a relative path
|
||||
pub fn is_relative(&self) -> bool {
|
||||
self.path_kind() == TOKEN_PATH_REL
|
||||
}
|
||||
|
||||
/// Returns true if this is a home-relative path
|
||||
pub fn is_home(&self) -> bool {
|
||||
self.path_kind() == TOKEN_PATH_HOME
|
||||
}
|
||||
|
||||
/// Returns true if this is a search path
|
||||
pub fn is_search(&self) -> bool {
|
||||
self.path_kind() == TOKEN_PATH_SEARCH
|
||||
}
|
||||
}
|
||||
|
||||
impl AstToken for PathContent {
|
||||
fn can_cast(kind: SyntaxKind) -> bool {
|
||||
matches!(kind, TOKEN_PATH_ABS | TOKEN_PATH_REL | TOKEN_PATH_HOME | TOKEN_PATH_SEARCH)
|
||||
}
|
||||
|
||||
fn cast(from: SyntaxToken) -> Option<Self> {
|
||||
if Self::can_cast(from.kind()) {
|
||||
Some(Self(from))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn syntax(&self) -> &SyntaxToken {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
token! { #[from(TOKEN_STRING_CONTENT)] struct StrContent; }
|
||||
|
||||
token! { #[from(TOKEN_URI)] struct Uri; }
|
||||
+285
@@ -0,0 +1,285 @@
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(u16)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[non_exhaustive]
|
||||
pub enum SyntaxKind {
|
||||
TOKEN_ADD,
|
||||
TOKEN_AND_AND,
|
||||
TOKEN_ASSIGN,
|
||||
TOKEN_ASSERT,
|
||||
TOKEN_AT,
|
||||
TOKEN_COLON,
|
||||
TOKEN_COMMA,
|
||||
TOKEN_COMMENT,
|
||||
TOKEN_CONCAT,
|
||||
TOKEN_CUR_POS,
|
||||
TOKEN_DIV,
|
||||
TOKEN_DOT,
|
||||
TOKEN_ELLIPSIS,
|
||||
TOKEN_ELSE,
|
||||
TOKEN_EQUAL,
|
||||
TOKEN_ERROR,
|
||||
TOKEN_FLOAT,
|
||||
TOKEN_IDENT,
|
||||
TOKEN_IF,
|
||||
TOKEN_IMPLICATION,
|
||||
TOKEN_IN,
|
||||
TOKEN_INHERIT,
|
||||
TOKEN_INTEGER,
|
||||
TOKEN_INTERPOL_END,
|
||||
TOKEN_INTERPOL_START,
|
||||
TOKEN_INVERT,
|
||||
TOKEN_L_BRACE,
|
||||
TOKEN_L_BRACK,
|
||||
TOKEN_L_PAREN,
|
||||
TOKEN_LESS,
|
||||
TOKEN_LESS_OR_EQ,
|
||||
TOKEN_LET,
|
||||
TOKEN_MORE,
|
||||
TOKEN_MORE_OR_EQ,
|
||||
TOKEN_MUL,
|
||||
TOKEN_NOT_EQUAL,
|
||||
TOKEN_OR,
|
||||
TOKEN_OR_OR,
|
||||
TOKEN_PATH_ABS,
|
||||
TOKEN_PATH_HOME,
|
||||
TOKEN_PATH_REL,
|
||||
TOKEN_PATH_SEARCH,
|
||||
TOKEN_PIPE_LEFT,
|
||||
TOKEN_PIPE_RIGHT,
|
||||
TOKEN_QUESTION,
|
||||
TOKEN_R_BRACE,
|
||||
TOKEN_R_BRACK,
|
||||
TOKEN_R_PAREN,
|
||||
TOKEN_REC,
|
||||
TOKEN_SEMICOLON,
|
||||
TOKEN_STRING_CONTENT,
|
||||
TOKEN_STRING_END,
|
||||
TOKEN_STRING_START,
|
||||
TOKEN_SUB,
|
||||
TOKEN_THEN,
|
||||
TOKEN_UPDATE,
|
||||
TOKEN_URI,
|
||||
TOKEN_WHITESPACE,
|
||||
TOKEN_WITH,
|
||||
|
||||
NODE_APPLY,
|
||||
NODE_ASSERT,
|
||||
NODE_ATTR_SET,
|
||||
NODE_ATTRPATH,
|
||||
NODE_ATTRPATH_VALUE,
|
||||
NODE_BIN_OP,
|
||||
NODE_CUR_POS,
|
||||
NODE_DYNAMIC,
|
||||
NODE_ERROR,
|
||||
// Attrpath existence check: foo ? bar.${baz}."bux"
|
||||
NODE_HAS_ATTR,
|
||||
NODE_IDENT,
|
||||
NODE_IDENT_PARAM,
|
||||
NODE_IF_ELSE,
|
||||
NODE_INHERIT,
|
||||
NODE_INHERIT_FROM,
|
||||
NODE_INTERPOL,
|
||||
NODE_LAMBDA,
|
||||
// An old let { x = 92; body = x; } syntax
|
||||
NODE_LEGACY_LET,
|
||||
NODE_LET_IN,
|
||||
NODE_LIST,
|
||||
NODE_LITERAL,
|
||||
NODE_PAREN,
|
||||
NODE_PAT_BIND,
|
||||
NODE_PAT_ENTRY,
|
||||
NODE_PATTERN,
|
||||
NODE_PATH_ABS,
|
||||
NODE_PATH_HOME,
|
||||
NODE_PATH_REL,
|
||||
NODE_PATH_SEARCH,
|
||||
NODE_ROOT,
|
||||
NODE_SELECT,
|
||||
NODE_STRING,
|
||||
NODE_UNARY_OP,
|
||||
NODE_WITH,
|
||||
}
|
||||
use SyntaxKind::*;
|
||||
|
||||
/// Maximum valid discriminant value for SyntaxKind
|
||||
pub(crate) const SYNTAX_KIND_MAX: u16 = NODE_WITH as u16;
|
||||
|
||||
/// Error returned when parsing a string into a `SyntaxKind` fails.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ParseSyntaxKindError;
|
||||
|
||||
impl std::fmt::Display for ParseSyntaxKindError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "invalid SyntaxKind")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ParseSyntaxKindError {}
|
||||
|
||||
impl std::str::FromStr for SyntaxKind {
|
||||
type Err = ParseSyntaxKindError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"TOKEN_ADD" => Ok(TOKEN_ADD),
|
||||
"TOKEN_AND_AND" => Ok(TOKEN_AND_AND),
|
||||
"TOKEN_ASSIGN" => Ok(TOKEN_ASSIGN),
|
||||
"TOKEN_ASSERT" => Ok(TOKEN_ASSERT),
|
||||
"TOKEN_AT" => Ok(TOKEN_AT),
|
||||
"TOKEN_COLON" => Ok(TOKEN_COLON),
|
||||
"TOKEN_COMMA" => Ok(TOKEN_COMMA),
|
||||
"TOKEN_COMMENT" => Ok(TOKEN_COMMENT),
|
||||
"TOKEN_CONCAT" => Ok(TOKEN_CONCAT),
|
||||
"TOKEN_CUR_POS" => Ok(TOKEN_CUR_POS),
|
||||
"TOKEN_DIV" => Ok(TOKEN_DIV),
|
||||
"TOKEN_DOT" => Ok(TOKEN_DOT),
|
||||
"TOKEN_ELLIPSIS" => Ok(TOKEN_ELLIPSIS),
|
||||
"TOKEN_ELSE" => Ok(TOKEN_ELSE),
|
||||
"TOKEN_EQUAL" => Ok(TOKEN_EQUAL),
|
||||
"TOKEN_ERROR" => Ok(TOKEN_ERROR),
|
||||
"TOKEN_FLOAT" => Ok(TOKEN_FLOAT),
|
||||
"TOKEN_IDENT" => Ok(TOKEN_IDENT),
|
||||
"TOKEN_IF" => Ok(TOKEN_IF),
|
||||
"TOKEN_IMPLICATION" => Ok(TOKEN_IMPLICATION),
|
||||
"TOKEN_IN" => Ok(TOKEN_IN),
|
||||
"TOKEN_INHERIT" => Ok(TOKEN_INHERIT),
|
||||
"TOKEN_INTEGER" => Ok(TOKEN_INTEGER),
|
||||
"TOKEN_INTERPOL_END" => Ok(TOKEN_INTERPOL_END),
|
||||
"TOKEN_INTERPOL_START" => Ok(TOKEN_INTERPOL_START),
|
||||
"TOKEN_INVERT" => Ok(TOKEN_INVERT),
|
||||
"TOKEN_L_BRACE" => Ok(TOKEN_L_BRACE),
|
||||
"TOKEN_L_BRACK" => Ok(TOKEN_L_BRACK),
|
||||
"TOKEN_L_PAREN" => Ok(TOKEN_L_PAREN),
|
||||
"TOKEN_LESS" => Ok(TOKEN_LESS),
|
||||
"TOKEN_LESS_OR_EQ" => Ok(TOKEN_LESS_OR_EQ),
|
||||
"TOKEN_LET" => Ok(TOKEN_LET),
|
||||
"TOKEN_MORE" => Ok(TOKEN_MORE),
|
||||
"TOKEN_MORE_OR_EQ" => Ok(TOKEN_MORE_OR_EQ),
|
||||
"TOKEN_MUL" => Ok(TOKEN_MUL),
|
||||
"TOKEN_NOT_EQUAL" => Ok(TOKEN_NOT_EQUAL),
|
||||
"TOKEN_OR" => Ok(TOKEN_OR),
|
||||
"TOKEN_OR_OR" => Ok(TOKEN_OR_OR),
|
||||
"TOKEN_PATH_ABS" => Ok(TOKEN_PATH_ABS),
|
||||
"TOKEN_PATH_HOME" => Ok(TOKEN_PATH_HOME),
|
||||
"TOKEN_PATH_REL" => Ok(TOKEN_PATH_REL),
|
||||
"TOKEN_PATH_SEARCH" => Ok(TOKEN_PATH_SEARCH),
|
||||
"TOKEN_PIPE_LEFT" => Ok(TOKEN_PIPE_LEFT),
|
||||
"TOKEN_PIPE_RIGHT" => Ok(TOKEN_PIPE_RIGHT),
|
||||
"TOKEN_QUESTION" => Ok(TOKEN_QUESTION),
|
||||
"TOKEN_R_BRACE" => Ok(TOKEN_R_BRACE),
|
||||
"TOKEN_R_BRACK" => Ok(TOKEN_R_BRACK),
|
||||
"TOKEN_R_PAREN" => Ok(TOKEN_R_PAREN),
|
||||
"TOKEN_REC" => Ok(TOKEN_REC),
|
||||
"TOKEN_SEMICOLON" => Ok(TOKEN_SEMICOLON),
|
||||
"TOKEN_STRING_CONTENT" => Ok(TOKEN_STRING_CONTENT),
|
||||
"TOKEN_STRING_END" => Ok(TOKEN_STRING_END),
|
||||
"TOKEN_STRING_START" => Ok(TOKEN_STRING_START),
|
||||
"TOKEN_SUB" => Ok(TOKEN_SUB),
|
||||
"TOKEN_THEN" => Ok(TOKEN_THEN),
|
||||
"TOKEN_UPDATE" => Ok(TOKEN_UPDATE),
|
||||
"TOKEN_URI" => Ok(TOKEN_URI),
|
||||
"TOKEN_WHITESPACE" => Ok(TOKEN_WHITESPACE),
|
||||
"TOKEN_WITH" => Ok(TOKEN_WITH),
|
||||
|
||||
"NODE_APPLY" => Ok(NODE_APPLY),
|
||||
"NODE_ASSERT" => Ok(NODE_ASSERT),
|
||||
"NODE_ATTR_SET" => Ok(NODE_ATTR_SET),
|
||||
"NODE_ATTRPATH" => Ok(NODE_ATTRPATH),
|
||||
"NODE_ATTRPATH_VALUE" => Ok(NODE_ATTRPATH_VALUE),
|
||||
"NODE_BIN_OP" => Ok(NODE_BIN_OP),
|
||||
"NODE_CUR_POS" => Ok(NODE_CUR_POS),
|
||||
"NODE_DYNAMIC" => Ok(NODE_DYNAMIC),
|
||||
"NODE_ERROR" => Ok(NODE_ERROR),
|
||||
"NODE_HAS_ATTR" => Ok(NODE_HAS_ATTR),
|
||||
"NODE_IDENT" => Ok(NODE_IDENT),
|
||||
"NODE_IDENT_PARAM" => Ok(NODE_IDENT_PARAM),
|
||||
"NODE_IF_ELSE" => Ok(NODE_IF_ELSE),
|
||||
"NODE_INHERIT" => Ok(NODE_INHERIT),
|
||||
"NODE_INHERIT_FROM" => Ok(NODE_INHERIT_FROM),
|
||||
"NODE_INTERPOL" => Ok(NODE_INTERPOL),
|
||||
"NODE_LAMBDA" => Ok(NODE_LAMBDA),
|
||||
"NODE_LEGACY_LET" => Ok(NODE_LEGACY_LET),
|
||||
"NODE_LET_IN" => Ok(NODE_LET_IN),
|
||||
"NODE_LIST" => Ok(NODE_LIST),
|
||||
"NODE_LITERAL" => Ok(NODE_LITERAL),
|
||||
"NODE_PAREN" => Ok(NODE_PAREN),
|
||||
"NODE_PAT_BIND" => Ok(NODE_PAT_BIND),
|
||||
"NODE_PAT_ENTRY" => Ok(NODE_PAT_ENTRY),
|
||||
"NODE_PATTERN" => Ok(NODE_PATTERN),
|
||||
"NODE_PATH_ABS" => Ok(NODE_PATH_ABS),
|
||||
"NODE_PATH_HOME" => Ok(NODE_PATH_HOME),
|
||||
"NODE_PATH_REL" => Ok(NODE_PATH_REL),
|
||||
"NODE_PATH_SEARCH" => Ok(NODE_PATH_SEARCH),
|
||||
"NODE_ROOT" => Ok(NODE_ROOT),
|
||||
"NODE_SELECT" => Ok(NODE_SELECT),
|
||||
"NODE_STRING" => Ok(NODE_STRING),
|
||||
"NODE_UNARY_OP" => Ok(NODE_UNARY_OP),
|
||||
"NODE_WITH" => Ok(NODE_WITH),
|
||||
|
||||
_ => Err(ParseSyntaxKindError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SyntaxKind {
|
||||
/// Returns true if this token is a literal, such as an integer or a string
|
||||
pub fn is_literal(self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
TOKEN_FLOAT
|
||||
| TOKEN_INTEGER
|
||||
| TOKEN_PATH_ABS
|
||||
| TOKEN_PATH_REL
|
||||
| TOKEN_PATH_HOME
|
||||
| TOKEN_PATH_SEARCH
|
||||
| TOKEN_URI
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns true if this token should be used as a function argument.
|
||||
/// ```ignore
|
||||
/// Example:
|
||||
/// add 1 2 + 3
|
||||
/// ^ ^ ^ ^
|
||||
/// | | | +- false
|
||||
/// | | +- true
|
||||
/// | +- true
|
||||
/// +- true
|
||||
/// ```
|
||||
pub fn is_fn_arg(self) -> bool {
|
||||
match self {
|
||||
// NB: the tokenizer always emits TOKEN_IDENT for __curPos;
|
||||
// TOKEN_CUR_POS is included for completeness but cannot
|
||||
// currently be produced by the tokenizer.
|
||||
TOKEN_REC | TOKEN_L_BRACE | TOKEN_L_BRACK | TOKEN_L_PAREN | TOKEN_STRING_START
|
||||
| TOKEN_IDENT | TOKEN_CUR_POS => true,
|
||||
_ => self.is_literal(),
|
||||
}
|
||||
}
|
||||
/// Returns true if this token is a comment, whitespace, or similar, and
|
||||
/// should be skipped over by the parser.
|
||||
pub fn is_trivia(self) -> bool {
|
||||
matches!(self, TOKEN_COMMENT | TOKEN_WHITESPACE)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn from_str_valid() {
|
||||
assert_eq!(SyntaxKind::from_str("TOKEN_COMMENT").unwrap(), TOKEN_COMMENT);
|
||||
assert_eq!(SyntaxKind::from_str("NODE_ROOT").unwrap(), NODE_ROOT);
|
||||
assert_eq!(SyntaxKind::from_str("NODE_HAS_ATTR").unwrap(), NODE_HAS_ATTR);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_str_invalid() {
|
||||
assert!(SyntaxKind::from_str("INVALID").is_err());
|
||||
assert!(SyntaxKind::from_str("").is_err());
|
||||
assert!(SyntaxKind::from_str("token_comment").is_err()); // case sensitive
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
pub mod ast;
|
||||
mod kinds;
|
||||
mod parser;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
mod token_set;
|
||||
mod tokenizer;
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use self::kinds::SYNTAX_KIND_MAX;
|
||||
pub use self::{kinds::ParseSyntaxKindError, kinds::SyntaxKind, tokenizer::tokenize};
|
||||
|
||||
use ast::AstNode;
|
||||
pub use parser::ParseError;
|
||||
use rowan::GreenNode;
|
||||
pub use rowan::{NodeOrToken, TextRange, TextSize, TokenAtOffset, WalkEvent};
|
||||
pub(crate) use token_set::TokenSet;
|
||||
pub use tokenizer::Token;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum NixLanguage {}
|
||||
|
||||
impl rowan::Language for NixLanguage {
|
||||
type Kind = SyntaxKind;
|
||||
fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
|
||||
let discriminant: u16 = raw.0;
|
||||
assert!(discriminant <= SYNTAX_KIND_MAX);
|
||||
unsafe { std::mem::transmute::<u16, SyntaxKind>(discriminant) }
|
||||
}
|
||||
fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
|
||||
rowan::SyntaxKind(kind as u16)
|
||||
}
|
||||
}
|
||||
|
||||
pub type SyntaxNode = rowan::SyntaxNode<NixLanguage>;
|
||||
pub type SyntaxToken = rowan::SyntaxToken<NixLanguage>;
|
||||
pub type SyntaxElement = rowan::NodeOrToken<SyntaxNode, SyntaxToken>;
|
||||
pub type SyntaxElementChildren = rowan::SyntaxElementChildren<NixLanguage>;
|
||||
pub type SyntaxNodeChildren = rowan::SyntaxNodeChildren<NixLanguage>;
|
||||
|
||||
pub use ast::Root;
|
||||
|
||||
impl Root {
|
||||
pub fn parse(s: &str) -> Parse<Root> {
|
||||
let (green, errors) = parser::parse(tokenize(s));
|
||||
Parse { green, errors, _ty: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of a parse
|
||||
#[derive(Clone)]
|
||||
pub struct Parse<T> {
|
||||
green: GreenNode,
|
||||
errors: Vec<ParseError>,
|
||||
_ty: PhantomData<fn() -> T>,
|
||||
}
|
||||
|
||||
impl<T> Parse<T> {
|
||||
pub fn syntax(&self) -> SyntaxNode {
|
||||
SyntaxNode::new_root(self.green.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AstNode> Parse<T> {
|
||||
pub fn tree(&self) -> T {
|
||||
T::cast(self.syntax()).unwrap()
|
||||
}
|
||||
|
||||
/// Return all errors in the tree, if any
|
||||
pub fn errors(&self) -> &[ParseError] {
|
||||
&self.errors
|
||||
}
|
||||
|
||||
/// Either return the first error in the tree, or if there are none return self
|
||||
pub fn ok(self) -> Result<T, ParseError> {
|
||||
if let Some(err) = self.errors().first() {
|
||||
return Err(err.clone());
|
||||
}
|
||||
Ok(self.tree())
|
||||
}
|
||||
}
|
||||
|
||||
/// Matches a `SyntaxNode` against an `ast` type.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```ignore
|
||||
/// match_ast! {
|
||||
/// match node {
|
||||
/// ast::Apply(it) => { ... },
|
||||
/// ast::Assert(it) => { ... },
|
||||
/// ast::IfElse(it) => { ... },
|
||||
/// _ => None,
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! match_ast {
|
||||
(match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
|
||||
|
||||
(match ($node:expr) {
|
||||
$( ast::$ast:ident($it:pat) => $res:expr, )*
|
||||
_ => $catch_all:expr $(,)?
|
||||
}) => {{
|
||||
$( if let Some($it) = ast::$ast::cast($node.clone()) { $res } else )*
|
||||
{ $catch_all }
|
||||
}};
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
#[macro_export]
|
||||
#[rustfmt::skip]
|
||||
macro_rules! T {
|
||||
(assert) => ($crate::SyntaxKind::TOKEN_ASSERT);
|
||||
(else) => ($crate::SyntaxKind::TOKEN_ELSE);
|
||||
(if) => ($crate::SyntaxKind::TOKEN_IF);
|
||||
(in) => ($crate::SyntaxKind::TOKEN_IN);
|
||||
(inherit) => ($crate::SyntaxKind::TOKEN_INHERIT);
|
||||
(let) => ($crate::SyntaxKind::TOKEN_LET);
|
||||
(or) => ($crate::SyntaxKind::TOKEN_OR);
|
||||
(rec) => ($crate::SyntaxKind::TOKEN_REC);
|
||||
(then) => ($crate::SyntaxKind::TOKEN_THEN);
|
||||
(with) => ($crate::SyntaxKind::TOKEN_WITH);
|
||||
(__curPos) => ($crate::SyntaxKind::TOKEN_CUR_POS);
|
||||
|
||||
('{') => ($crate::SyntaxKind::TOKEN_L_BRACE);
|
||||
('}') => ($crate::SyntaxKind::TOKEN_R_BRACE);
|
||||
('[') => ($crate::SyntaxKind::TOKEN_L_BRACK);
|
||||
(']') => ($crate::SyntaxKind::TOKEN_R_BRACK);
|
||||
('(') => ($crate::SyntaxKind::TOKEN_L_PAREN);
|
||||
(')') => ($crate::SyntaxKind::TOKEN_R_PAREN);
|
||||
|
||||
(=) => ($crate::SyntaxKind::TOKEN_ASSIGN);
|
||||
(@) => ($crate::SyntaxKind::TOKEN_AT);
|
||||
(:) => ($crate::SyntaxKind::TOKEN_COLON);
|
||||
(,) => ($crate::SyntaxKind::TOKEN_COMMA);
|
||||
(.) => ($crate::SyntaxKind::TOKEN_DOT);
|
||||
(...) => ($crate::SyntaxKind::TOKEN_ELLIPSIS);
|
||||
(?) => ($crate::SyntaxKind::TOKEN_QUESTION);
|
||||
(;) => ($crate::SyntaxKind::TOKEN_SEMICOLON);
|
||||
(++) => ($crate::SyntaxKind::TOKEN_CONCAT);
|
||||
(!) => ($crate::SyntaxKind::TOKEN_INVERT);
|
||||
("//") => ($crate::SyntaxKind::TOKEN_UPDATE);
|
||||
|
||||
(+) => ($crate::SyntaxKind::TOKEN_ADD);
|
||||
(-) => ($crate::SyntaxKind::TOKEN_SUB);
|
||||
(*) => ($crate::SyntaxKind::TOKEN_MUL);
|
||||
(/) => ($crate::SyntaxKind::TOKEN_DIV);
|
||||
|
||||
(&&) => ($crate::SyntaxKind::TOKEN_AND_AND);
|
||||
(==) => ($crate::SyntaxKind::TOKEN_EQUAL);
|
||||
(->) => ($crate::SyntaxKind::TOKEN_IMPLICATION);
|
||||
(<) => ($crate::SyntaxKind::TOKEN_LESS);
|
||||
(<=) => ($crate::SyntaxKind::TOKEN_LESS_OR_EQ);
|
||||
(>) => ($crate::SyntaxKind::TOKEN_MORE);
|
||||
(>=) => ($crate::SyntaxKind::TOKEN_MORE_OR_EQ);
|
||||
(!=) => ($crate::SyntaxKind::TOKEN_NOT_EQUAL);
|
||||
(||) => ($crate::SyntaxKind::TOKEN_OR_OR);
|
||||
("|>") => ($crate::SyntaxKind::TOKEN_PIPE_RIGHT);
|
||||
("<|") => ($crate::SyntaxKind::TOKEN_PIPE_LEFT);
|
||||
($kind:ident) => ($crate::SyntaxKind::$kind);
|
||||
}
|
||||
+834
@@ -0,0 +1,834 @@
|
||||
//! The parser: turns a series of tokens into an AST
|
||||
|
||||
use std::{collections::VecDeque, fmt};
|
||||
|
||||
use rowan::{Checkpoint, GreenNode, GreenNodeBuilder, Language, TextRange, TextSize};
|
||||
|
||||
use crate::{
|
||||
tokenizer::Token,
|
||||
NixLanguage,
|
||||
SyntaxKind::{self, *},
|
||||
TokenSet,
|
||||
};
|
||||
|
||||
/// An error that occurred during parsing
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum ParseError {
|
||||
/// Unexpected is used when the cause cannot be specified further
|
||||
Unexpected(TextRange),
|
||||
/// UnexpectedExtra is used when there are additional tokens to the root in the tree
|
||||
UnexpectedExtra(TextRange),
|
||||
/// UnexpectedWanted is used when specific tokens are expected, but different one is found
|
||||
UnexpectedWanted(SyntaxKind, TextRange, Box<[SyntaxKind]>),
|
||||
/// UnexpectedDoubleBind is used when a pattern is bound twice
|
||||
UnexpectedDoubleBind(TextRange),
|
||||
/// UnexpectedEOF is used when the end of file is reached, while tokens are still expected
|
||||
UnexpectedEOF,
|
||||
/// UnexpectedEOFWanted is used when specific tokens are expected, but the end of file is reached
|
||||
UnexpectedEOFWanted(Box<[SyntaxKind]>),
|
||||
/// DuplicatedArgs is used when formal arguments are duplicated, e.g. `{ a, a }`
|
||||
DuplicatedArgs(TextRange, String),
|
||||
/// RecursionLimitExceeded is used when we're unable to parse further due to likely being close to
|
||||
/// a stack overflow.
|
||||
RecursionLimitExceeded,
|
||||
}
|
||||
|
||||
impl fmt::Display for ParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ParseError::Unexpected(range) => {
|
||||
write!(
|
||||
f,
|
||||
"error node at {}..{}",
|
||||
usize::from(range.start()),
|
||||
usize::from(range.end())
|
||||
)
|
||||
}
|
||||
ParseError::UnexpectedExtra(range) => {
|
||||
write!(
|
||||
f,
|
||||
"unexpected token at {}..{}",
|
||||
usize::from(range.start()),
|
||||
usize::from(range.end())
|
||||
)
|
||||
}
|
||||
ParseError::UnexpectedWanted(got, range, kinds) => write!(
|
||||
f,
|
||||
"unexpected {:?} at {}..{}, wanted any of {:?}",
|
||||
got,
|
||||
usize::from(range.start()),
|
||||
usize::from(range.end()),
|
||||
kinds
|
||||
),
|
||||
ParseError::UnexpectedDoubleBind(range) => {
|
||||
write!(
|
||||
f,
|
||||
"unexpected double bind at {}..{}",
|
||||
usize::from(range.start()),
|
||||
usize::from(range.end())
|
||||
)
|
||||
}
|
||||
ParseError::UnexpectedEOF => write!(f, "unexpected end of file"),
|
||||
ParseError::UnexpectedEOFWanted(kinds) => {
|
||||
write!(f, "unexpected end of file, wanted any of {:?}", kinds)
|
||||
}
|
||||
ParseError::DuplicatedArgs(range, ident) => {
|
||||
write!(
|
||||
f,
|
||||
"argument `{}` is duplicated in {}..{}",
|
||||
ident,
|
||||
usize::from(range.start()),
|
||||
usize::from(range.end())
|
||||
)
|
||||
}
|
||||
ParseError::RecursionLimitExceeded => write!(f, "recursion limit exceeded"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ParseError {}
|
||||
|
||||
struct Parser<'a, I>
|
||||
where
|
||||
I: Iterator<Item = Token<'a>>,
|
||||
{
|
||||
builder: GreenNodeBuilder<'static>,
|
||||
errors: Vec<ParseError>,
|
||||
|
||||
trivia_buffer: Vec<Token<'a>>,
|
||||
buffer: VecDeque<Token<'a>>,
|
||||
iter: I,
|
||||
consumed: TextSize,
|
||||
|
||||
// Recursion depth, used for avoiding stack overflows. This may be incremented
|
||||
// by any method as long as it is decremented when that method returns.
|
||||
depth: u32,
|
||||
}
|
||||
impl<'a, I> Parser<'a, I>
|
||||
where
|
||||
I: Iterator<Item = Token<'a>>,
|
||||
{
|
||||
fn new(iter: I) -> Self {
|
||||
Self {
|
||||
builder: GreenNodeBuilder::new(),
|
||||
errors: Vec::new(),
|
||||
|
||||
trivia_buffer: Vec::with_capacity(1),
|
||||
buffer: VecDeque::with_capacity(1),
|
||||
iter,
|
||||
consumed: TextSize::from(0),
|
||||
|
||||
depth: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_text_position(&self) -> TextSize {
|
||||
self.consumed
|
||||
}
|
||||
|
||||
fn peek_raw(&mut self) -> Option<&Token<'a>> {
|
||||
if self.buffer.is_empty() {
|
||||
if let Some(token) = self.iter.next() {
|
||||
self.buffer.push_back(token);
|
||||
}
|
||||
}
|
||||
self.buffer.front()
|
||||
}
|
||||
fn drain_trivia_buffer(&mut self) {
|
||||
for (t, s) in self.trivia_buffer.drain(..) {
|
||||
self.consumed += TextSize::of(s);
|
||||
self.builder.token(NixLanguage::kind_to_raw(t), s);
|
||||
}
|
||||
}
|
||||
fn eat_trivia(&mut self) {
|
||||
self.peek();
|
||||
self.drain_trivia_buffer();
|
||||
}
|
||||
fn start_node(&mut self, kind: SyntaxKind) {
|
||||
self.eat_trivia();
|
||||
self.builder.start_node(NixLanguage::kind_to_raw(kind));
|
||||
}
|
||||
fn checkpoint(&mut self) -> Checkpoint {
|
||||
self.eat_trivia();
|
||||
self.builder.checkpoint()
|
||||
}
|
||||
fn start_node_at(&mut self, checkpoint: Checkpoint, kind: SyntaxKind) {
|
||||
self.builder.start_node_at(checkpoint, NixLanguage::kind_to_raw(kind));
|
||||
}
|
||||
fn finish_node(&mut self) {
|
||||
self.builder.finish_node();
|
||||
}
|
||||
fn start_error_node(&mut self) -> TextSize {
|
||||
self.start_node(NODE_ERROR);
|
||||
self.get_text_position()
|
||||
}
|
||||
fn finish_error_node(&mut self) -> TextSize {
|
||||
self.finish_node();
|
||||
self.get_text_position()
|
||||
}
|
||||
fn bump(&mut self) {
|
||||
match self.try_next() {
|
||||
Some((token, s)) => {
|
||||
if token.is_trivia() {
|
||||
self.trivia_buffer.push((token, s))
|
||||
} else {
|
||||
self.drain_trivia_buffer();
|
||||
self.manual_bump(s, token);
|
||||
}
|
||||
}
|
||||
None => self.errors.push(ParseError::UnexpectedEOF),
|
||||
}
|
||||
}
|
||||
fn try_next(&mut self) -> Option<Token<'a>> {
|
||||
self.buffer.pop_front().or_else(|| self.iter.next())
|
||||
}
|
||||
fn manual_bump(&mut self, s: &str, token: SyntaxKind) {
|
||||
self.consumed += TextSize::of(s);
|
||||
self.builder.token(NixLanguage::kind_to_raw(token), s)
|
||||
}
|
||||
fn peek(&mut self) -> Option<SyntaxKind> {
|
||||
while self.peek_raw().map(|&(t, _)| t.is_trivia()).unwrap_or(false) {
|
||||
self.bump();
|
||||
}
|
||||
self.peek_ahead(0)
|
||||
}
|
||||
fn peek_ahead(&mut self, n: usize) -> Option<SyntaxKind> {
|
||||
let mut non_trivia_seen = 0;
|
||||
let mut cursor = 0;
|
||||
loop {
|
||||
if self.buffer.len() <= cursor {
|
||||
self.buffer.push_back(self.iter.next()?);
|
||||
}
|
||||
let kind = self.buffer[cursor].0;
|
||||
cursor += 1;
|
||||
if !kind.is_trivia() {
|
||||
if non_trivia_seen == n {
|
||||
return Some(kind);
|
||||
}
|
||||
non_trivia_seen += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
fn expect_peek_any(&mut self, allowed_slice: &[SyntaxKind]) -> Option<SyntaxKind> {
|
||||
let allowed = TokenSet::from_slice(allowed_slice);
|
||||
|
||||
let next = match self.peek() {
|
||||
None => None,
|
||||
Some(kind) if allowed.contains(kind) => Some(kind),
|
||||
Some(kind) => {
|
||||
let start = self.start_error_node();
|
||||
loop {
|
||||
self.bump();
|
||||
if self.peek().map(|kind| allowed.contains(kind)).unwrap_or(true) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let end = self.finish_error_node();
|
||||
self.errors.push(ParseError::UnexpectedWanted(
|
||||
kind,
|
||||
TextRange::new(start, end),
|
||||
allowed_slice.to_vec().into_boxed_slice(),
|
||||
));
|
||||
|
||||
self.peek()
|
||||
}
|
||||
};
|
||||
if next.is_none() {
|
||||
self.errors
|
||||
.push(ParseError::UnexpectedEOFWanted(allowed_slice.to_vec().into_boxed_slice()));
|
||||
}
|
||||
next
|
||||
}
|
||||
fn expect(&mut self, expected: SyntaxKind) {
|
||||
if self.expect_peek_any(&[expected]).is_some() {
|
||||
self.bump();
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_ident(&mut self) {
|
||||
if self.expect_peek_any(&[TOKEN_IDENT]).is_some() {
|
||||
self.start_node(NODE_IDENT);
|
||||
self.bump();
|
||||
self.finish_node()
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_dynamic(&mut self) {
|
||||
self.start_node(NODE_DYNAMIC);
|
||||
self.bump();
|
||||
while self.peek().map(|t| t != TOKEN_INTERPOL_END).unwrap_or(false) {
|
||||
self.parse_expr();
|
||||
}
|
||||
self.bump();
|
||||
self.finish_node();
|
||||
}
|
||||
|
||||
fn parse_string(&mut self) {
|
||||
self.start_node(NODE_STRING);
|
||||
self.expect(TOKEN_STRING_START);
|
||||
|
||||
loop {
|
||||
match self.expect_peek_any(&[
|
||||
TOKEN_STRING_END,
|
||||
TOKEN_STRING_CONTENT,
|
||||
TOKEN_INTERPOL_START,
|
||||
]) {
|
||||
Some(TOKEN_STRING_CONTENT) => self.bump(),
|
||||
Some(TOKEN_INTERPOL_START) => {
|
||||
self.start_node(NODE_INTERPOL);
|
||||
self.bump();
|
||||
self.parse_expr();
|
||||
self.expect(TOKEN_INTERPOL_END);
|
||||
self.finish_node();
|
||||
}
|
||||
// handled by expect_peek_any
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
self.expect(TOKEN_STRING_END);
|
||||
|
||||
self.finish_node();
|
||||
}
|
||||
fn parse_attr(&mut self) {
|
||||
match self.peek() {
|
||||
Some(TOKEN_INTERPOL_START) => self.parse_dynamic(),
|
||||
Some(TOKEN_STRING_START) => self.parse_string(),
|
||||
_ => {
|
||||
// NB: the tokenizer always emits TOKEN_IDENT for __curPos;
|
||||
// TOKEN_CUR_POS is included for completeness but cannot
|
||||
// currently be produced by the tokenizer.
|
||||
if self.expect_peek_any(&[TOKEN_IDENT, TOKEN_OR, TOKEN_CUR_POS]).is_some() {
|
||||
self.start_node(NODE_IDENT);
|
||||
let (_, s) = self.try_next().unwrap();
|
||||
self.manual_bump(s, TOKEN_IDENT);
|
||||
self.finish_node()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn parse_attrpath(&mut self) {
|
||||
self.start_node(NODE_ATTRPATH);
|
||||
loop {
|
||||
self.parse_attr();
|
||||
|
||||
if self.peek() == Some(T![.]) {
|
||||
self.bump();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.finish_node();
|
||||
}
|
||||
fn parse_pattern(&mut self, bound: bool) {
|
||||
if self.peek().map(|t| t == T!['}']).unwrap_or(true) {
|
||||
self.bump();
|
||||
} else {
|
||||
loop {
|
||||
match self.expect_peek_any(&[T!['}'], T![...], TOKEN_IDENT]) {
|
||||
Some(T!['}']) => {
|
||||
self.bump();
|
||||
break;
|
||||
}
|
||||
Some(T![...]) => {
|
||||
self.bump();
|
||||
self.expect(T!['}']);
|
||||
break;
|
||||
}
|
||||
Some(TOKEN_IDENT) => {
|
||||
self.start_node(NODE_PAT_ENTRY);
|
||||
self.expect_ident();
|
||||
if let Some(T![?]) = self.peek() {
|
||||
self.bump();
|
||||
self.parse_expr();
|
||||
}
|
||||
self.finish_node();
|
||||
match self.peek() {
|
||||
Some(T![,]) => self.bump(),
|
||||
_ => {
|
||||
self.expect(T!['}']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// handled by expect_peek_any
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.peek() == Some(T![@]) {
|
||||
let kind = if bound { NODE_ERROR } else { NODE_PAT_BIND };
|
||||
self.start_node(kind);
|
||||
let start = self.get_text_position();
|
||||
self.bump();
|
||||
self.expect_ident();
|
||||
let end = self.finish_error_node();
|
||||
if bound {
|
||||
self.errors.push(ParseError::UnexpectedDoubleBind(TextRange::new(start, end)));
|
||||
}
|
||||
}
|
||||
}
|
||||
fn parse_set(&mut self, until: SyntaxKind) {
|
||||
loop {
|
||||
match self.peek() {
|
||||
None => break,
|
||||
token if token == Some(until) => break,
|
||||
Some(T![inherit]) => {
|
||||
self.start_node(NODE_INHERIT);
|
||||
self.bump();
|
||||
|
||||
if self.peek() == Some(T!['(']) {
|
||||
self.start_node(NODE_INHERIT_FROM);
|
||||
self.bump();
|
||||
self.parse_expr();
|
||||
self.expect(T![')']);
|
||||
self.finish_node();
|
||||
}
|
||||
|
||||
loop {
|
||||
match self.peek() {
|
||||
Some(t) if t != T![;] => {
|
||||
self.parse_attr();
|
||||
}
|
||||
Some(_) => {
|
||||
break;
|
||||
}
|
||||
None => {
|
||||
self.errors.push(ParseError::UnexpectedEOF);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.expect(T![;]);
|
||||
self.finish_node();
|
||||
}
|
||||
Some(_) => {
|
||||
self.start_node(NODE_ATTRPATH_VALUE);
|
||||
self.parse_attrpath();
|
||||
self.expect(T![=]);
|
||||
self.parse_expr();
|
||||
self.expect(T![;]);
|
||||
self.finish_node();
|
||||
}
|
||||
}
|
||||
}
|
||||
self.bump(); // the final close, like '}'
|
||||
}
|
||||
|
||||
fn parse_simple(&mut self) -> Checkpoint {
|
||||
let peek = match self.peek() {
|
||||
Some(it) => it,
|
||||
None => {
|
||||
self.errors.push(ParseError::UnexpectedEOF);
|
||||
// NB: we don't use `self.checkpoint()` here in order to avoid
|
||||
// eating the whitespace. The actual checkpoint doesn't matter
|
||||
// in this case and, ideally, should be returning `None`, but
|
||||
// that makes code slightly more complex for little real
|
||||
// benefit.
|
||||
return self.builder.checkpoint();
|
||||
}
|
||||
};
|
||||
let checkpoint = self.checkpoint();
|
||||
match peek {
|
||||
T!['('] => {
|
||||
self.start_node(NODE_PAREN);
|
||||
self.bump();
|
||||
self.parse_expr();
|
||||
self.bump();
|
||||
self.finish_node();
|
||||
}
|
||||
T![rec] => {
|
||||
self.start_node(NODE_ATTR_SET);
|
||||
self.bump();
|
||||
self.expect(T!['{']);
|
||||
self.parse_set(T!['}']);
|
||||
self.finish_node();
|
||||
}
|
||||
T!['{'] => {
|
||||
// Do a lookahead:
|
||||
let mut peek = [None, None];
|
||||
for i in &mut peek {
|
||||
let mut token;
|
||||
*i = loop {
|
||||
token = self.iter.next();
|
||||
let kind = token.as_ref().map(|&(t, _)| t);
|
||||
if let Some(token) = token {
|
||||
self.buffer.push_back(token);
|
||||
}
|
||||
if kind.map(|t| !t.is_trivia()).unwrap_or(true) {
|
||||
break kind;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
match peek {
|
||||
[Some(TOKEN_IDENT), Some(T![,])]
|
||||
| [Some(TOKEN_IDENT), Some(T![?])]
|
||||
| [Some(TOKEN_IDENT), Some(T!['}'])]
|
||||
| [Some(T![...]), Some(T!['}'])]
|
||||
| [Some(T!['}']), Some(T![:])]
|
||||
| [Some(T!['}']), Some(T![@])] => {
|
||||
// This looks like a pattern
|
||||
self.start_node(NODE_LAMBDA);
|
||||
|
||||
self.start_node(NODE_PATTERN);
|
||||
self.bump();
|
||||
self.parse_pattern(false);
|
||||
self.finish_node();
|
||||
|
||||
self.expect(T![:]);
|
||||
self.parse_expr();
|
||||
|
||||
self.finish_node();
|
||||
}
|
||||
_ => {
|
||||
// This looks like a set
|
||||
self.start_node(NODE_ATTR_SET);
|
||||
self.bump();
|
||||
self.parse_set(T!['}']);
|
||||
self.finish_node();
|
||||
}
|
||||
}
|
||||
}
|
||||
T!['['] => {
|
||||
self.start_node(NODE_LIST);
|
||||
self.bump();
|
||||
while self.peek().map(|t| t != T![']']).unwrap_or(false) {
|
||||
self.parse_simple();
|
||||
}
|
||||
self.bump();
|
||||
self.finish_node();
|
||||
}
|
||||
TOKEN_STRING_START => self.parse_string(),
|
||||
TOKEN_PATH_ABS | TOKEN_PATH_REL | TOKEN_PATH_HOME | TOKEN_PATH_SEARCH => {
|
||||
let node_kind = match self.peek().unwrap() {
|
||||
TOKEN_PATH_ABS => NODE_PATH_ABS,
|
||||
TOKEN_PATH_REL => NODE_PATH_REL,
|
||||
TOKEN_PATH_HOME => NODE_PATH_HOME,
|
||||
TOKEN_PATH_SEARCH => NODE_PATH_SEARCH,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
self.start_node(node_kind);
|
||||
self.bump();
|
||||
|
||||
// Search paths (<nixpkgs>) don't support interpolation
|
||||
let is_search_path = node_kind == NODE_PATH_SEARCH;
|
||||
let is_complex_path = !is_search_path && self.peek() == Some(TOKEN_INTERPOL_START);
|
||||
|
||||
if is_complex_path {
|
||||
loop {
|
||||
match self.peek_raw().map(|(t, _)| t) {
|
||||
Some(TOKEN_PATH_ABS) | Some(TOKEN_PATH_REL) | Some(TOKEN_PATH_HOME) => {
|
||||
self.bump()
|
||||
}
|
||||
Some(TOKEN_INTERPOL_START) => {
|
||||
self.start_node(NODE_INTERPOL);
|
||||
self.bump();
|
||||
self.parse_expr();
|
||||
self.expect(TOKEN_INTERPOL_END);
|
||||
self.finish_node();
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
self.finish_node();
|
||||
}
|
||||
t if t.is_literal() => {
|
||||
self.start_node(NODE_LITERAL);
|
||||
self.bump();
|
||||
self.finish_node();
|
||||
}
|
||||
TOKEN_IDENT => {
|
||||
let ident = self.peek_raw().map(|&(_, s)| s);
|
||||
|
||||
match self.peek_ahead(1) {
|
||||
Some(T![:]) => {
|
||||
self.expect_ident();
|
||||
|
||||
self.start_node_at(checkpoint, NODE_LAMBDA);
|
||||
self.start_node_at(checkpoint, NODE_IDENT_PARAM);
|
||||
self.finish_node();
|
||||
self.expect(T![:]);
|
||||
self.parse_expr();
|
||||
self.finish_node();
|
||||
}
|
||||
Some(T![@]) => {
|
||||
self.expect_ident();
|
||||
|
||||
self.start_node_at(checkpoint, NODE_LAMBDA);
|
||||
self.start_node_at(checkpoint, NODE_PATTERN);
|
||||
self.start_node_at(checkpoint, NODE_PAT_BIND);
|
||||
self.expect(T![@]);
|
||||
self.finish_node(); // PatBind
|
||||
|
||||
self.expect(T!['{']);
|
||||
self.parse_pattern(true);
|
||||
self.finish_node(); // Pattern
|
||||
|
||||
self.expect(T![:]);
|
||||
self.parse_expr();
|
||||
self.finish_node(); // Lambda
|
||||
}
|
||||
_ if ident == Some("__curPos") => {
|
||||
self.start_node(NODE_CUR_POS);
|
||||
let (_, s) = self.try_next().unwrap();
|
||||
self.manual_bump(s, TOKEN_CUR_POS);
|
||||
self.finish_node();
|
||||
}
|
||||
_ => self.expect_ident(),
|
||||
}
|
||||
}
|
||||
kind => {
|
||||
let start = self.start_error_node();
|
||||
self.bump();
|
||||
let end = self.finish_error_node();
|
||||
self.errors.push(ParseError::UnexpectedWanted(
|
||||
kind,
|
||||
TextRange::new(start, end),
|
||||
[T!['('], T![rec], T!['{'], T!['['], TOKEN_STRING_START, TOKEN_IDENT]
|
||||
.to_vec()
|
||||
.into_boxed_slice(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
if self.peek() == Some(T![.]) {
|
||||
self.start_node_at(checkpoint, NODE_SELECT);
|
||||
self.bump();
|
||||
self.parse_attrpath();
|
||||
if self.peek() == Some(T![or]) {
|
||||
self.bump();
|
||||
self.parse_simple();
|
||||
}
|
||||
self.finish_node();
|
||||
|
||||
// This seems weird, but it matches Nix's behavior.
|
||||
// If there is no "." but a "or" immediately followed a primary expression,
|
||||
// we construct a application node, with "or" parsed as an identifier,
|
||||
// ignoring the associativity of ancestor applications.
|
||||
// Eg.
|
||||
// "a b or c" => "((a (b or)) c)"
|
||||
// "or a" => fail
|
||||
} else if self.peek() == Some(T![or]) {
|
||||
self.start_node_at(checkpoint, NODE_APPLY);
|
||||
self.start_node(NODE_IDENT);
|
||||
let (_, s) = self.try_next().unwrap();
|
||||
self.manual_bump(s, TOKEN_IDENT);
|
||||
self.finish_node();
|
||||
self.finish_node();
|
||||
}
|
||||
|
||||
checkpoint
|
||||
}
|
||||
fn parse_fn(&mut self) -> Checkpoint {
|
||||
let checkpoint = self.parse_simple();
|
||||
|
||||
while self.peek().map(|t| t.is_fn_arg()).unwrap_or(false) {
|
||||
self.start_node_at(checkpoint, NODE_APPLY);
|
||||
self.parse_simple();
|
||||
self.finish_node();
|
||||
}
|
||||
checkpoint
|
||||
}
|
||||
fn parse_negate(&mut self) -> Checkpoint {
|
||||
if self.peek() == Some(T![-]) {
|
||||
let checkpoint = self.checkpoint();
|
||||
self.start_node(NODE_UNARY_OP);
|
||||
self.bump();
|
||||
self.parse_negate();
|
||||
self.finish_node();
|
||||
checkpoint
|
||||
} else {
|
||||
self.parse_fn()
|
||||
}
|
||||
}
|
||||
fn parse_non_assoc(&mut self, next: fn(&mut Self) -> Checkpoint, ops: TokenSet) -> Checkpoint {
|
||||
let checkpoint = next(self);
|
||||
if self.peek().map(|t| ops.contains(t)).unwrap_or(false) {
|
||||
self.start_node_at(checkpoint, NODE_BIN_OP);
|
||||
self.bump();
|
||||
next(self);
|
||||
self.finish_node();
|
||||
}
|
||||
checkpoint
|
||||
}
|
||||
fn parse_left_assoc(&mut self, next: fn(&mut Self) -> Checkpoint, ops: TokenSet) -> Checkpoint {
|
||||
let checkpoint = next(self);
|
||||
while self.peek().map(|t| ops.contains(t)).unwrap_or(false) {
|
||||
self.start_node_at(checkpoint, NODE_BIN_OP);
|
||||
self.bump();
|
||||
next(self);
|
||||
self.finish_node();
|
||||
}
|
||||
checkpoint
|
||||
}
|
||||
fn parse_right_assoc(
|
||||
&mut self,
|
||||
next: fn(&mut Self) -> Checkpoint,
|
||||
ops: TokenSet,
|
||||
) -> Checkpoint {
|
||||
let checkpoint = next(self);
|
||||
if self.peek().map(|t| ops.contains(t)).unwrap_or(false) {
|
||||
self.start_node_at(checkpoint, NODE_BIN_OP);
|
||||
self.bump();
|
||||
self.parse_right_assoc(next, ops);
|
||||
self.finish_node();
|
||||
}
|
||||
checkpoint
|
||||
}
|
||||
fn parse_hasattr(&mut self) -> Checkpoint {
|
||||
let checkpoint = self.parse_negate();
|
||||
while self.peek().map(|t| t == T![?]).unwrap_or(false) {
|
||||
self.start_node_at(checkpoint, NODE_HAS_ATTR);
|
||||
self.bump();
|
||||
self.parse_attrpath();
|
||||
self.finish_node();
|
||||
}
|
||||
checkpoint
|
||||
}
|
||||
fn parse_concat(&mut self) -> Checkpoint {
|
||||
self.parse_right_assoc(Self::parse_hasattr, T![++] | ())
|
||||
}
|
||||
fn parse_mul(&mut self) -> Checkpoint {
|
||||
self.parse_left_assoc(Self::parse_concat, T![*] | T![/])
|
||||
}
|
||||
fn parse_add(&mut self) -> Checkpoint {
|
||||
self.parse_left_assoc(Self::parse_mul, T![+] | T![-])
|
||||
}
|
||||
fn parse_invert(&mut self) -> Checkpoint {
|
||||
if self.peek() == Some(TOKEN_INVERT) {
|
||||
let checkpoint = self.checkpoint();
|
||||
self.start_node(NODE_UNARY_OP);
|
||||
self.bump();
|
||||
self.parse_invert();
|
||||
self.finish_node();
|
||||
checkpoint
|
||||
} else {
|
||||
self.parse_add()
|
||||
}
|
||||
}
|
||||
fn parse_merge(&mut self) -> Checkpoint {
|
||||
self.parse_right_assoc(Self::parse_invert, T!["//"] | ())
|
||||
}
|
||||
fn parse_compare(&mut self) -> Checkpoint {
|
||||
self.parse_non_assoc(Self::parse_merge, T![<] | T![<=] | T![>] | T![>=])
|
||||
}
|
||||
fn parse_equal(&mut self) -> Checkpoint {
|
||||
self.parse_non_assoc(Self::parse_compare, T![==] | T![!=])
|
||||
}
|
||||
fn parse_and(&mut self) -> Checkpoint {
|
||||
self.parse_left_assoc(Self::parse_equal, T![&&] | ())
|
||||
}
|
||||
fn parse_or(&mut self) -> Checkpoint {
|
||||
self.parse_left_assoc(Self::parse_and, T![||] | ())
|
||||
}
|
||||
fn parse_implication(&mut self) -> Checkpoint {
|
||||
self.parse_right_assoc(Self::parse_or, T![->] | ())
|
||||
}
|
||||
#[inline(always)]
|
||||
fn parse_math(&mut self) -> Checkpoint {
|
||||
// Always point this to the lowest-level math function there is
|
||||
self.parse_implication()
|
||||
}
|
||||
fn parse_pipe_right(&mut self) -> Checkpoint {
|
||||
self.parse_left_assoc(Self::parse_math, T!["|>"] | ())
|
||||
}
|
||||
fn parse_pipe_left(&mut self) -> Checkpoint {
|
||||
self.parse_right_assoc(Self::parse_pipe_right, T!["<|"] | ())
|
||||
}
|
||||
/// Parse Nix code into an AST
|
||||
pub fn parse_expr(&mut self) -> Checkpoint {
|
||||
// Limit chosen somewhat arbitrarily
|
||||
if self.depth >= 512 {
|
||||
self.errors.push(ParseError::RecursionLimitExceeded);
|
||||
// Consume tokens to the end of the file. Erroring without bumping might cause
|
||||
// infinite looping elsewhere.
|
||||
self.start_error_node();
|
||||
while self.peek().is_some() {
|
||||
self.bump()
|
||||
}
|
||||
self.finish_error_node();
|
||||
return self.checkpoint();
|
||||
}
|
||||
self.depth += 1;
|
||||
let out = match self.peek() {
|
||||
Some(T![let]) => {
|
||||
let checkpoint = self.checkpoint();
|
||||
self.bump();
|
||||
|
||||
if self.peek() == Some(T!['{']) {
|
||||
self.start_node_at(checkpoint, NODE_LEGACY_LET);
|
||||
self.bump();
|
||||
self.parse_set(T!['}']);
|
||||
self.finish_node();
|
||||
} else {
|
||||
self.start_node_at(checkpoint, NODE_LET_IN);
|
||||
self.parse_set(T![in]);
|
||||
self.parse_expr();
|
||||
self.finish_node();
|
||||
}
|
||||
checkpoint
|
||||
}
|
||||
Some(T![with]) => {
|
||||
let checkpoint = self.checkpoint();
|
||||
self.start_node(NODE_WITH);
|
||||
self.bump();
|
||||
self.parse_expr();
|
||||
self.expect(T![;]);
|
||||
self.parse_expr();
|
||||
self.finish_node();
|
||||
checkpoint
|
||||
}
|
||||
Some(T![if]) => {
|
||||
let checkpoint = self.checkpoint();
|
||||
self.start_node(NODE_IF_ELSE);
|
||||
self.bump();
|
||||
self.parse_expr();
|
||||
self.expect(T![then]);
|
||||
self.parse_expr();
|
||||
self.expect(TOKEN_ELSE);
|
||||
self.parse_expr();
|
||||
self.finish_node();
|
||||
checkpoint
|
||||
}
|
||||
Some(T![assert]) => {
|
||||
let checkpoint = self.checkpoint();
|
||||
self.start_node(NODE_ASSERT);
|
||||
self.bump();
|
||||
self.parse_expr();
|
||||
self.expect(T![;]);
|
||||
self.parse_expr();
|
||||
self.finish_node();
|
||||
checkpoint
|
||||
}
|
||||
_ => self.parse_pipe_left(),
|
||||
};
|
||||
self.depth -= 1;
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse tokens into an AST
|
||||
pub fn parse<'s, I>(iter: I) -> (GreenNode, Vec<ParseError>)
|
||||
where
|
||||
I: Iterator<Item = Token<'s>>,
|
||||
{
|
||||
let mut parser = Parser::new(iter);
|
||||
parser.builder.start_node(NixLanguage::kind_to_raw(NODE_ROOT));
|
||||
parser.parse_expr();
|
||||
parser.eat_trivia();
|
||||
if parser.peek().is_some() {
|
||||
let start = parser.start_error_node();
|
||||
while parser.peek().is_some() {
|
||||
parser.bump();
|
||||
}
|
||||
let end = parser.finish_error_node();
|
||||
parser.errors.push(ParseError::UnexpectedExtra(TextRange::new(start, end)));
|
||||
parser.eat_trivia();
|
||||
}
|
||||
parser.builder.finish_node();
|
||||
(parser.builder.finish(), parser.errors)
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
use std::{ffi::OsStr, fmt::Write, fs, path::PathBuf};
|
||||
|
||||
use expect_test::expect_file;
|
||||
use rowan::ast::AstNode;
|
||||
|
||||
use crate::{
|
||||
ast::{self, HasEntry},
|
||||
tokenize, Root, SyntaxKind,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn interpolation() {
|
||||
let root = ast::Root::parse(include_str!("../test_data/parser/success/interpolation.nix"))
|
||||
.ok()
|
||||
.unwrap();
|
||||
let let_in = ast::LetIn::try_from(root.expr().unwrap()).unwrap();
|
||||
let set = ast::AttrSet::try_from(let_in.body().unwrap()).unwrap();
|
||||
let entry = set.entries().nth(1).unwrap();
|
||||
let attrpath_value = ast::AttrpathValue::try_from(entry).unwrap();
|
||||
let value = ast::Str::try_from(attrpath_value.value().unwrap()).unwrap();
|
||||
|
||||
match &*value.normalized_parts() {
|
||||
&[
|
||||
ast::InterpolPart::Literal(ref s1),
|
||||
ast::InterpolPart::Interpolation(_),
|
||||
ast::InterpolPart::Literal(ref s2),
|
||||
ast::InterpolPart::Interpolation(_),
|
||||
ast::InterpolPart::Literal(ref s3)
|
||||
]
|
||||
if s1 == "The set's x value is: "
|
||||
&& s2 == "\n\nThis line shall have no indention\n This line shall be indented by 2\n\n\n"
|
||||
&& s3 == "\n" => (),
|
||||
parts => panic!("did not match: {:#?}", parts)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inherit() {
|
||||
let root =
|
||||
ast::Root::parse(include_str!("../test_data/parser/success/inherit.nix")).ok().unwrap();
|
||||
let let_in = ast::LetIn::try_from(root.expr().unwrap()).unwrap();
|
||||
let set = ast::AttrSet::try_from(let_in.body().unwrap()).unwrap();
|
||||
let inherit = set.inherits().nth(1).unwrap();
|
||||
|
||||
let from = inherit.from().unwrap().expr().unwrap();
|
||||
let ident: ast::Ident = ast::Ident::try_from(from).unwrap();
|
||||
assert_eq!(ident.syntax().text(), "set");
|
||||
let mut children = inherit.attrs();
|
||||
assert_eq!(children.next().unwrap().syntax().text(), "z");
|
||||
assert_eq!(children.next().unwrap().syntax().text(), "a");
|
||||
assert!(children.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math() {
|
||||
let root = ast::Root::parse(include_str!("../test_data/parser/success/math.nix")).ok().unwrap();
|
||||
let op1 = ast::BinOp::try_from(root.expr().unwrap()).unwrap();
|
||||
let op2 = ast::BinOp::try_from(op1.lhs().unwrap()).unwrap();
|
||||
assert_eq!(op1.operator().unwrap(), ast::BinOpKind::Add);
|
||||
|
||||
let lhs = ast::Literal::try_from(op2.lhs().unwrap()).unwrap();
|
||||
assert_eq!(lhs.syntax().text(), "1");
|
||||
|
||||
let rhs = ast::BinOp::try_from(op2.rhs().unwrap()).unwrap();
|
||||
assert_eq!(rhs.operator().unwrap(), ast::BinOpKind::Mul);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn t_macro() {
|
||||
assert_eq!(T![@], SyntaxKind::TOKEN_AT);
|
||||
assert!(matches!(SyntaxKind::TOKEN_L_PAREN, T!['(']));
|
||||
}
|
||||
|
||||
fn dir_tests<F>(dir: &str, get_actual: F)
|
||||
where
|
||||
F: Fn(String) -> String,
|
||||
{
|
||||
let base_path: PathBuf = [env!("CARGO_MANIFEST_DIR"), "test_data", dir].iter().collect();
|
||||
let success_path = base_path.join("success");
|
||||
let error_path = base_path.join("error");
|
||||
|
||||
let entries = success_path.read_dir().unwrap().chain(error_path.read_dir().unwrap());
|
||||
|
||||
for entry in entries {
|
||||
let path = entry.unwrap().path();
|
||||
|
||||
if path.extension() != Some(OsStr::new("nix")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
println!("testing: {}", path.display());
|
||||
|
||||
let mut code = fs::read_to_string(&path).unwrap();
|
||||
if code.ends_with('\n') {
|
||||
code.truncate(code.len() - 1);
|
||||
}
|
||||
|
||||
let actual = get_actual(code);
|
||||
expect_file![path.with_extension("expect")].assert_eq(&actual);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parser_dir_tests() {
|
||||
dir_tests("parser", |code| {
|
||||
let parse = Root::parse(&code);
|
||||
|
||||
let mut actual = String::new();
|
||||
for error in parse.errors() {
|
||||
writeln!(actual, "error: {}", error).unwrap();
|
||||
}
|
||||
writeln!(actual, "{:#?}", parse.syntax()).unwrap();
|
||||
|
||||
actual
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tokenizer_dir_tests() {
|
||||
dir_tests("tokenizer", |code| {
|
||||
let mut actual = String::new();
|
||||
for (kind, str) in tokenize(&code) {
|
||||
writeln!(actual, "{:?}, \"{}\"", kind, str).unwrap();
|
||||
}
|
||||
actual
|
||||
})
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
use std::ops;
|
||||
|
||||
use crate::SyntaxKind;
|
||||
|
||||
/// A bit-set of `SyntaxKind`s
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct TokenSet(u128);
|
||||
|
||||
impl TokenSet {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) const EMPTY: TokenSet = TokenSet(0);
|
||||
|
||||
pub(crate) const fn new(kind: SyntaxKind) -> TokenSet {
|
||||
TokenSet(mask(kind))
|
||||
}
|
||||
|
||||
pub(crate) const fn from_slice(kinds: &[SyntaxKind]) -> TokenSet {
|
||||
let mut res = 0u128;
|
||||
let mut i = 0;
|
||||
while i < kinds.len() {
|
||||
res |= mask(kinds[i]);
|
||||
i += 1
|
||||
}
|
||||
TokenSet(res)
|
||||
}
|
||||
|
||||
pub(crate) const fn union(self, other: TokenSet) -> TokenSet {
|
||||
TokenSet(self.0 | other.0)
|
||||
}
|
||||
|
||||
pub(crate) const fn contains(&self, kind: SyntaxKind) -> bool {
|
||||
self.0 & mask(kind) != 0
|
||||
}
|
||||
}
|
||||
|
||||
const fn mask(kind: SyntaxKind) -> u128 {
|
||||
1u128 << (kind as usize)
|
||||
}
|
||||
|
||||
impl ops::BitOr for SyntaxKind {
|
||||
type Output = TokenSet;
|
||||
|
||||
fn bitor(self, rhs: Self) -> Self::Output {
|
||||
TokenSet(mask(self) | mask(rhs))
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::BitOr<SyntaxKind> for TokenSet {
|
||||
type Output = TokenSet;
|
||||
|
||||
fn bitor(self, rhs: SyntaxKind) -> Self::Output {
|
||||
self.union(TokenSet(mask(rhs)))
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::BitOr<TokenSet> for SyntaxKind {
|
||||
type Output = TokenSet;
|
||||
|
||||
fn bitor(self, rhs: TokenSet) -> Self::Output {
|
||||
TokenSet(mask(self)).union(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::BitOr<TokenSet> for TokenSet {
|
||||
type Output = TokenSet;
|
||||
|
||||
fn bitor(self, rhs: TokenSet) -> Self::Output {
|
||||
self.union(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::BitOr<SyntaxKind> for () {
|
||||
type Output = TokenSet;
|
||||
|
||||
fn bitor(self, rhs: SyntaxKind) -> Self::Output {
|
||||
TokenSet::new(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::BitOr<()> for SyntaxKind {
|
||||
type Output = TokenSet;
|
||||
|
||||
fn bitor(self, (): ()) -> Self::Output {
|
||||
TokenSet::new(self)
|
||||
}
|
||||
}
|
||||
+566
@@ -0,0 +1,566 @@
|
||||
//! The tokenizer: turns a string into tokens, such as numbers, strings, and keywords
|
||||
|
||||
use crate::SyntaxKind::{self, *};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum IdentType {
|
||||
Ident,
|
||||
PathRel,
|
||||
PathAbs,
|
||||
PathHome,
|
||||
PathSearch,
|
||||
Uri,
|
||||
}
|
||||
|
||||
fn is_valid_path_char(c: char) -> bool {
|
||||
matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '/' | '_' | '.' | '+' | '-')
|
||||
}
|
||||
|
||||
fn is_valid_uri_char(c: char) -> bool {
|
||||
match c {
|
||||
'%' | '?' | ':' | '@' | '&' | '=' | '$' | ',' | '!' | '~' | '*' | '\'' => true,
|
||||
_ => is_valid_path_char(c),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum Context {
|
||||
StringBody { multiline: bool },
|
||||
StringEnd,
|
||||
Interpol { brackets: u32 },
|
||||
InterpolStart,
|
||||
Path(SyntaxKind),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct State<'a> {
|
||||
input: &'a str,
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
impl PartialEq for State<'_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
std::ptr::eq(self.input, other.input) && self.offset == other.offset
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for State<'_> {}
|
||||
|
||||
pub type Token<'a> = (SyntaxKind, &'a str);
|
||||
|
||||
/// Tokenize the given input
|
||||
pub fn tokenize(input: &str) -> impl Iterator<Item = Token<'_>> + '_ {
|
||||
Tokenizer::new(input)
|
||||
}
|
||||
|
||||
struct Tokenizer<'a> {
|
||||
ctx: Vec<Context>,
|
||||
state: State<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Tokenizer<'a> {
|
||||
pub fn new(input: &'a str) -> Self {
|
||||
Self { ctx: Vec::new(), state: State { input, offset: 0 } }
|
||||
}
|
||||
}
|
||||
|
||||
impl Tokenizer<'_> {
|
||||
fn remaining(&self) -> &str {
|
||||
&self.state.input[self.state.offset..]
|
||||
}
|
||||
fn peek(&self) -> Option<char> {
|
||||
self.remaining().chars().next()
|
||||
}
|
||||
fn next(&mut self) -> Option<char> {
|
||||
let c = self.peek();
|
||||
if let Some(c) = c {
|
||||
self.state.offset += c.len_utf8();
|
||||
}
|
||||
c
|
||||
}
|
||||
fn starts_with_bump(&mut self, s: &str) -> bool {
|
||||
let starts_with = self.remaining().starts_with(s);
|
||||
if starts_with {
|
||||
self.state.offset += s.len();
|
||||
}
|
||||
starts_with
|
||||
}
|
||||
fn str_since<'a>(&self, past: State<'a>) -> &'a str {
|
||||
&past.input[past.offset..self.state.offset]
|
||||
}
|
||||
|
||||
fn push_ctx(&mut self, ctx: Context) {
|
||||
self.ctx.push(ctx)
|
||||
}
|
||||
|
||||
fn pop_ctx(&mut self, ctx: Context) {
|
||||
debug_assert!(self
|
||||
.ctx
|
||||
.last()
|
||||
.is_some_and(|c| std::mem::discriminant(c) == std::mem::discriminant(&ctx)));
|
||||
self.ctx.pop();
|
||||
}
|
||||
|
||||
/// Consumes all next characters.
|
||||
/// while the predicate function [f] returns true
|
||||
///
|
||||
/// returns the count of consumed characters.
|
||||
fn consume<F>(&mut self, mut f: F) -> usize
|
||||
where
|
||||
F: FnMut(char) -> bool,
|
||||
{
|
||||
let len: usize =
|
||||
self.remaining().chars().take_while(|&c| f(c)).map(|c| c.len_utf8()).sum::<usize>();
|
||||
self.state.offset += len;
|
||||
len
|
||||
}
|
||||
fn next_string(&mut self, multiline: bool) -> SyntaxKind {
|
||||
loop {
|
||||
let start = self.state;
|
||||
match self.next() {
|
||||
None => {
|
||||
self.pop_ctx(Context::StringBody { multiline });
|
||||
return TOKEN_ERROR;
|
||||
}
|
||||
Some('"') if !multiline => {
|
||||
self.state = start;
|
||||
self.pop_ctx(Context::StringBody { multiline: false });
|
||||
self.push_ctx(Context::StringEnd);
|
||||
return TOKEN_STRING_CONTENT;
|
||||
}
|
||||
Some('\\') if !multiline => {
|
||||
if self.next().is_none() {
|
||||
return TOKEN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
Some('\'') if multiline => match self.peek() {
|
||||
None => return TOKEN_ERROR,
|
||||
Some('\'') => {
|
||||
self.next();
|
||||
match self.peek() {
|
||||
Some('\'') | Some('$') => {
|
||||
self.next().unwrap();
|
||||
}
|
||||
Some('\\') => {
|
||||
self.next().unwrap();
|
||||
if self.next().is_none() {
|
||||
return TOKEN_ERROR;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.state = start;
|
||||
self.pop_ctx(Context::StringBody { multiline: true });
|
||||
self.push_ctx(Context::StringEnd);
|
||||
return TOKEN_STRING_CONTENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(_) => (),
|
||||
},
|
||||
|
||||
Some('$') => match self.peek() {
|
||||
Some('$') => {
|
||||
self.next().unwrap();
|
||||
}
|
||||
Some('{') => {
|
||||
self.state = start;
|
||||
self.push_ctx(Context::InterpolStart);
|
||||
return TOKEN_STRING_CONTENT;
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Some(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_path_since(&mut self, past: State, kind: SyntaxKind) -> SyntaxKind {
|
||||
self.consume(is_valid_path_char);
|
||||
let path_str = self.str_since(past);
|
||||
if self.remaining().starts_with("${") {
|
||||
self.ctx.push(Context::InterpolStart);
|
||||
} else if path_str.ends_with('/') || path_str.contains("//") {
|
||||
return TOKEN_ERROR;
|
||||
} else {
|
||||
self.pop_ctx(Context::Path(kind));
|
||||
}
|
||||
kind
|
||||
}
|
||||
|
||||
fn next_inner(&mut self) -> Option<SyntaxKind> {
|
||||
let start = self.state;
|
||||
|
||||
// Handle already started multi-token
|
||||
loop {
|
||||
match self.ctx.last() {
|
||||
Some(Context::InterpolStart) => {
|
||||
self.pop_ctx(Context::InterpolStart);
|
||||
self.ctx.push(Context::Interpol { brackets: 0 });
|
||||
if self.starts_with_bump("${") {
|
||||
return Some(TOKEN_INTERPOL_START);
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
Some(Context::Path(path_kind)) => {
|
||||
let path_kind = *path_kind;
|
||||
|
||||
// Search paths (<nixpkgs>) don't support interpolation
|
||||
if path_kind == TOKEN_PATH_SEARCH {
|
||||
if self.peek().is_some_and(is_valid_path_char) {
|
||||
return Some(self.check_path_since(start, path_kind));
|
||||
} else {
|
||||
self.pop_ctx(Context::Path(path_kind));
|
||||
}
|
||||
} else if self.starts_with_bump("${") {
|
||||
self.ctx.push(Context::Interpol { brackets: 0 });
|
||||
return Some(TOKEN_INTERPOL_START);
|
||||
} else if self.peek().is_some_and(is_valid_path_char) {
|
||||
return Some(self.check_path_since(start, path_kind));
|
||||
} else {
|
||||
self.pop_ctx(Context::Path(path_kind));
|
||||
}
|
||||
}
|
||||
Some(&Context::StringBody { multiline }) => {
|
||||
let token = self.next_string(multiline);
|
||||
// skip empty stuff
|
||||
if self.state == start {
|
||||
continue;
|
||||
}
|
||||
return Some(token);
|
||||
}
|
||||
Some(Context::StringEnd) => {
|
||||
self.pop_ctx(Context::StringEnd);
|
||||
let status = match self.peek() {
|
||||
Some('"') => {
|
||||
self.next().unwrap();
|
||||
true
|
||||
}
|
||||
Some('\'') => {
|
||||
self.next().unwrap();
|
||||
match self.peek() {
|
||||
Some('\'') => {
|
||||
self.next().unwrap();
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
return Some(if status { TOKEN_STRING_END } else { TOKEN_ERROR });
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if self.consume(char::is_whitespace) > 0 {
|
||||
return Some(TOKEN_WHITESPACE);
|
||||
}
|
||||
|
||||
if self.peek() == Some('#') {
|
||||
self.consume(|c| c != '\n');
|
||||
return Some(TOKEN_COMMENT);
|
||||
}
|
||||
if self.starts_with_bump("/*") {
|
||||
loop {
|
||||
self.consume(|c| c != '*');
|
||||
self.next(); // consume the '*', if any
|
||||
match self.peek() {
|
||||
None => return Some(TOKEN_ERROR),
|
||||
Some('/') => {
|
||||
self.next().unwrap();
|
||||
return Some(TOKEN_COMMENT);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.starts_with_bump("...") {
|
||||
return Some(TOKEN_ELLIPSIS);
|
||||
}
|
||||
|
||||
// Check if it's a path
|
||||
// First, simple prefix based detection for absolute ("/...") or home relative ("~/...") paths.
|
||||
// For absolute paths, we need to be careful to not interfere with operators
|
||||
let kind = if self.peek() == Some('/') {
|
||||
// For absolute paths, we need to have a valid path character after the '/'
|
||||
// AND we need to make sure it's not an operator like //
|
||||
if self.remaining().starts_with("//") {
|
||||
None // This could be an update operator, let the operator matcher handle it
|
||||
} else if self.remaining().starts_with("/${") {
|
||||
// Absolute path with interpolation, e.g. /${foo}/bar
|
||||
Some(IdentType::PathAbs)
|
||||
} else {
|
||||
let second_char = self.remaining().chars().nth(1);
|
||||
if second_char.is_some_and(|c| is_valid_path_char(c) && c != '*') {
|
||||
Some(IdentType::PathAbs)
|
||||
} else {
|
||||
None // Not a path, might be division operator or something else
|
||||
}
|
||||
}
|
||||
} else if self.peek() == Some('~') && self.remaining().chars().nth(1) == Some('/') {
|
||||
Some(IdentType::PathHome)
|
||||
} else {
|
||||
// Fallback to heuristic previously used for relative, search and URI paths.
|
||||
let store_path = self.peek() == Some('<');
|
||||
let skipped = self
|
||||
.remaining()
|
||||
.chars()
|
||||
.take_while(|&c| match c {
|
||||
'<' | '/' => store_path,
|
||||
_ => is_valid_path_char(c),
|
||||
})
|
||||
.collect::<String>();
|
||||
|
||||
let mut lookahead = self.remaining().chars().skip(skipped.chars().count());
|
||||
|
||||
match (lookahead.next(), lookahead.next()) {
|
||||
// a//b parses as Update(a, b)
|
||||
(Some('/'), Some('/')) => None,
|
||||
(Some('/'), Some('*')) => None,
|
||||
(Some('/'), Some(c)) if !c.is_whitespace() => {
|
||||
// If the first character of the yet‑to‑be‑consumed token was '/', we treat it as absolute later.
|
||||
// Otherwise it's a relative path.
|
||||
Some(IdentType::PathRel)
|
||||
}
|
||||
(Some('>'), _) => Some(IdentType::PathSearch),
|
||||
(Some(':'), Some(c)) if is_valid_uri_char(c) && !skipped.contains('_') => {
|
||||
Some(IdentType::Uri)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
|
||||
let c = self.next()?;
|
||||
|
||||
// First, handle specific operators that might be confused with paths
|
||||
if c == '/' && self.peek() == Some('/') {
|
||||
// This is the update operator '//'
|
||||
self.next().unwrap(); // Consume the second '/'
|
||||
return Some(TOKEN_UPDATE);
|
||||
}
|
||||
|
||||
if c == '~'
|
||||
|| matches!(kind, Some(IdentType::PathAbs | IdentType::PathRel))
|
||||
|| (c == '/' && self.peek().is_some_and(is_valid_path_char))
|
||||
{
|
||||
return Some({
|
||||
// Determine the concrete path token kind to use
|
||||
let token_kind = if c == '~' {
|
||||
// we've just consumed '~', ensure '/' exists already consumed by next()
|
||||
if self.next() != Some('/') {
|
||||
return Some(TOKEN_ERROR);
|
||||
}
|
||||
TOKEN_PATH_HOME
|
||||
} else {
|
||||
// For abs vs rel: If the first char we consumed was '/', it's absolute.
|
||||
if c == '/' {
|
||||
TOKEN_PATH_ABS
|
||||
} else {
|
||||
TOKEN_PATH_REL
|
||||
}
|
||||
};
|
||||
|
||||
// Store the kind in context so subsequent segments are consistent
|
||||
self.push_ctx(Context::Path(token_kind));
|
||||
self.check_path_since(start, token_kind)
|
||||
});
|
||||
}
|
||||
|
||||
Some(match c {
|
||||
'=' if self.peek() == Some('=') => {
|
||||
self.next().unwrap();
|
||||
TOKEN_EQUAL
|
||||
}
|
||||
'!' if self.peek() == Some('=') => {
|
||||
self.next().unwrap();
|
||||
TOKEN_NOT_EQUAL
|
||||
}
|
||||
'!' => TOKEN_INVERT,
|
||||
'{' => {
|
||||
if let Some(Context::Interpol { brackets }) = self.ctx.last_mut() {
|
||||
*brackets += 1;
|
||||
}
|
||||
TOKEN_L_BRACE
|
||||
}
|
||||
'}' => {
|
||||
if let Some(Context::Interpol { brackets }) = self.ctx.last_mut() {
|
||||
match brackets.checked_sub(1) {
|
||||
Some(new) => *brackets = new,
|
||||
None => {
|
||||
self.pop_ctx(Context::Interpol { brackets: 0 });
|
||||
return Some(TOKEN_INTERPOL_END);
|
||||
}
|
||||
}
|
||||
}
|
||||
TOKEN_R_BRACE
|
||||
}
|
||||
'[' => TOKEN_L_BRACK,
|
||||
']' => TOKEN_R_BRACK,
|
||||
'@' => TOKEN_AT,
|
||||
':' => TOKEN_COLON,
|
||||
',' => TOKEN_COMMA,
|
||||
'.' => {
|
||||
if self.peek().is_some_and(|x| x.is_ascii_digit()) {
|
||||
self.consume(|c| c.is_ascii_digit());
|
||||
self.consume_scientific()
|
||||
} else {
|
||||
TOKEN_DOT
|
||||
}
|
||||
}
|
||||
'=' => TOKEN_ASSIGN,
|
||||
'?' => TOKEN_QUESTION,
|
||||
';' => TOKEN_SEMICOLON,
|
||||
'(' => TOKEN_L_PAREN,
|
||||
')' => TOKEN_R_PAREN,
|
||||
'+' if self.peek() == Some('+') => {
|
||||
self.next().unwrap();
|
||||
TOKEN_CONCAT
|
||||
}
|
||||
'-' if self.peek() == Some('>') => {
|
||||
self.next().unwrap();
|
||||
TOKEN_IMPLICATION
|
||||
}
|
||||
'/' if self.peek() == Some('/') => {
|
||||
self.next().unwrap();
|
||||
TOKEN_UPDATE
|
||||
}
|
||||
'+' => TOKEN_ADD,
|
||||
'-' => TOKEN_SUB,
|
||||
'*' => TOKEN_MUL,
|
||||
'/' => TOKEN_DIV,
|
||||
'<' if self.peek() == Some('|') => {
|
||||
self.next().unwrap();
|
||||
TOKEN_PIPE_LEFT
|
||||
}
|
||||
'<' if kind == Some(IdentType::PathSearch) => {
|
||||
let content_start = self.state.offset;
|
||||
self.consume(is_valid_path_char);
|
||||
// Check if we consumed any content between < and >
|
||||
if self.state.offset == content_start {
|
||||
// Empty search path <> is not valid
|
||||
TOKEN_LESS
|
||||
} else if self.next() != Some('>') {
|
||||
TOKEN_ERROR
|
||||
} else {
|
||||
TOKEN_PATH_SEARCH
|
||||
}
|
||||
}
|
||||
'&' if self.peek() == Some('&') => {
|
||||
self.next().unwrap();
|
||||
TOKEN_AND_AND
|
||||
}
|
||||
'|' if self.peek() == Some('>') => {
|
||||
self.next().unwrap();
|
||||
TOKEN_PIPE_RIGHT
|
||||
}
|
||||
'|' if self.peek() == Some('|') => {
|
||||
self.next().unwrap();
|
||||
TOKEN_OR_OR
|
||||
}
|
||||
'<' if self.peek() == Some('=') => {
|
||||
self.next().unwrap();
|
||||
TOKEN_LESS_OR_EQ
|
||||
}
|
||||
'<' => TOKEN_LESS,
|
||||
'>' if self.peek() == Some('=') => {
|
||||
self.next().unwrap();
|
||||
TOKEN_MORE_OR_EQ
|
||||
}
|
||||
'>' => TOKEN_MORE,
|
||||
'$' if self.peek() == Some('{') => {
|
||||
self.next().unwrap();
|
||||
self.push_ctx(Context::Interpol { brackets: 0 });
|
||||
TOKEN_INTERPOL_START
|
||||
}
|
||||
'a'..='z' | 'A'..='Z' | '_' => {
|
||||
let kind = match kind {
|
||||
// It's detected as store if it ends with >, but if it
|
||||
// didn't start with <, that's wrong
|
||||
Some(IdentType::PathSearch) | None => IdentType::Ident,
|
||||
Some(kind) => kind,
|
||||
};
|
||||
self.consume(|c| match c {
|
||||
'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-' | '\'' => true,
|
||||
c => kind == IdentType::Uri && is_valid_uri_char(c),
|
||||
});
|
||||
match kind {
|
||||
IdentType::Ident => match self.str_since(start) {
|
||||
"assert" => TOKEN_ASSERT,
|
||||
"else" => TOKEN_ELSE,
|
||||
"if" => TOKEN_IF,
|
||||
"in" => TOKEN_IN,
|
||||
"inherit" => TOKEN_INHERIT,
|
||||
"let" => TOKEN_LET,
|
||||
// "or" is a contextual keyword and will be handled in the parser.
|
||||
"or" => TOKEN_OR,
|
||||
"rec" => TOKEN_REC,
|
||||
"then" => TOKEN_THEN,
|
||||
"with" => TOKEN_WITH,
|
||||
_ => TOKEN_IDENT,
|
||||
},
|
||||
IdentType::Uri => TOKEN_URI,
|
||||
IdentType::PathAbs | IdentType::PathRel | IdentType::PathHome => {
|
||||
panic!("paths are checked earlier")
|
||||
}
|
||||
IdentType::PathSearch => panic!("search paths are checked earlier"),
|
||||
}
|
||||
}
|
||||
'"' => {
|
||||
self.push_ctx(Context::StringBody { multiline: false });
|
||||
TOKEN_STRING_START
|
||||
}
|
||||
'\'' if self.peek() == Some('\'') => {
|
||||
self.next().unwrap();
|
||||
self.push_ctx(Context::StringBody { multiline: true });
|
||||
TOKEN_STRING_START
|
||||
}
|
||||
fst @ '0'..='9' => {
|
||||
// Due to nix's behavior on leading 0's we know immediately
|
||||
// That a '0' followed by some other number character MUST tokenize into an INT.
|
||||
// More background on this: https://github.com/nix-community/rnix-parser/issues/157
|
||||
if fst == '0' && self.peek() != Some('.') {
|
||||
// In that case just consume the remaining number characters to fill the TOKEN_INTEGER
|
||||
self.consume(|c| c.is_ascii_digit());
|
||||
TOKEN_INTEGER
|
||||
} else {
|
||||
// If we are not in that edge-case we can just continue with the regular number tokenization
|
||||
self.consume(|c| c.is_ascii_digit());
|
||||
if self.peek() == Some('.') {
|
||||
self.next().unwrap();
|
||||
self.consume(|c| c.is_ascii_digit());
|
||||
self.consume_scientific()
|
||||
} else {
|
||||
TOKEN_INTEGER
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => TOKEN_ERROR,
|
||||
})
|
||||
}
|
||||
|
||||
fn consume_scientific(&mut self) -> SyntaxKind {
|
||||
if self.peek() == Some('e') || self.peek() == Some('E') {
|
||||
self.next().unwrap();
|
||||
if self.peek() == Some('-') || self.peek() == Some('+') {
|
||||
self.next().unwrap();
|
||||
}
|
||||
if self.consume(|c| c.is_ascii_digit()) == 0 {
|
||||
return TOKEN_ERROR;
|
||||
}
|
||||
}
|
||||
TOKEN_FLOAT
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Tokenizer<'a> {
|
||||
type Item = Token<'a>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let start = self.state;
|
||||
self.next_inner().map(|syntax_kind| (syntax_kind, self.str_since(start)))
|
||||
}
|
||||
}
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
error: unexpected TOKEN_ERROR at 2..3, wanted any of [TOKEN_IDENT, TOKEN_OR, TOKEN_CUR_POS]
|
||||
NODE_ROOT@0..12
|
||||
NODE_ATTR_SET@0..12
|
||||
TOKEN_L_BRACE@0..1 "{"
|
||||
TOKEN_WHITESPACE@1..2 " "
|
||||
NODE_ATTRPATH_VALUE@2..10
|
||||
NODE_ATTRPATH@2..4
|
||||
NODE_ERROR@2..3
|
||||
TOKEN_ERROR@2..3 "$"
|
||||
NODE_IDENT@3..4
|
||||
TOKEN_IDENT@3..4 "a"
|
||||
TOKEN_WHITESPACE@4..5 " "
|
||||
TOKEN_ASSIGN@5..6 "="
|
||||
TOKEN_WHITESPACE@6..7 " "
|
||||
NODE_STRING@7..9
|
||||
TOKEN_STRING_START@7..8 "\""
|
||||
TOKEN_STRING_END@8..9 "\""
|
||||
TOKEN_SEMICOLON@9..10 ";"
|
||||
TOKEN_WHITESPACE@10..11 " "
|
||||
TOKEN_R_BRACE@11..12 "}"
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
{ $a = ""; }
|
||||
@@ -0,0 +1,110 @@
|
||||
error: unexpected TOKEN_STRING_START at 10..28, wanted any of [TOKEN_ASSIGN]
|
||||
error: unexpected TOKEN_STRING_START at 78..98, wanted any of [TOKEN_ASSIGN]
|
||||
error: unexpected TOKEN_R_BRACE at 162..166, wanted any of [TOKEN_ASSIGN]
|
||||
error: unexpected end of file, wanted any of [TOKEN_ASSIGN]
|
||||
error: unexpected end of file
|
||||
error: unexpected end of file, wanted any of [TOKEN_SEMICOLON]
|
||||
error: unexpected end of file
|
||||
error: unexpected end of file, wanted any of [TOKEN_SEMICOLON]
|
||||
error: unexpected end of file
|
||||
NODE_ROOT@0..166
|
||||
NODE_ATTR_SET@0..166
|
||||
TOKEN_L_BRACE@0..1 "{"
|
||||
TOKEN_WHITESPACE@1..4 "\n "
|
||||
NODE_ATTRPATH_VALUE@4..33
|
||||
NODE_ATTRPATH@4..9
|
||||
NODE_IDENT@4..9
|
||||
TOKEN_IDENT@4..9 "hello"
|
||||
TOKEN_WHITESPACE@9..10 " "
|
||||
NODE_ERROR@10..28
|
||||
TOKEN_STRING_START@10..11 "\""
|
||||
TOKEN_STRING_CONTENT@11..27 "Hello Ùnicödə"
|
||||
TOKEN_STRING_END@27..28 "\""
|
||||
TOKEN_WHITESPACE@28..29 " "
|
||||
TOKEN_ASSIGN@29..30 "="
|
||||
TOKEN_WHITESPACE@30..31 " "
|
||||
NODE_LITERAL@31..32
|
||||
TOKEN_INTEGER@31..32 "1"
|
||||
TOKEN_SEMICOLON@32..33 ";"
|
||||
TOKEN_WHITESPACE@33..36 "\n "
|
||||
NODE_ATTRPATH_VALUE@36..120
|
||||
NODE_ATTRPATH@36..40
|
||||
NODE_IDENT@36..40
|
||||
TOKEN_IDENT@36..40 "test"
|
||||
TOKEN_WHITESPACE@40..41 " "
|
||||
TOKEN_ASSIGN@41..42 "="
|
||||
TOKEN_WHITESPACE@42..43 " "
|
||||
NODE_ATTR_SET@43..119
|
||||
TOKEN_L_BRACE@43..44 "{"
|
||||
TOKEN_WHITESPACE@44..49 "\n "
|
||||
NODE_ATTRPATH_VALUE@49..65
|
||||
NODE_ATTRPATH@49..54
|
||||
NODE_IDENT@49..54
|
||||
TOKEN_IDENT@49..54 "valid"
|
||||
TOKEN_WHITESPACE@54..55 " "
|
||||
TOKEN_ASSIGN@55..56 "="
|
||||
TOKEN_WHITESPACE@56..57 " "
|
||||
NODE_STRING@57..64
|
||||
TOKEN_STRING_START@57..58 "\""
|
||||
TOKEN_STRING_CONTENT@58..63 "entry"
|
||||
TOKEN_STRING_END@63..64 "\""
|
||||
TOKEN_SEMICOLON@64..65 ";"
|
||||
TOKEN_WHITESPACE@65..70 "\n "
|
||||
NODE_ATTRPATH_VALUE@70..115
|
||||
NODE_ATTRPATH@70..77
|
||||
NODE_IDENT@70..77
|
||||
TOKEN_IDENT@70..77 "invalid"
|
||||
TOKEN_WHITESPACE@77..78 " "
|
||||
NODE_ERROR@78..98
|
||||
TOKEN_STRING_START@78..79 "\""
|
||||
TOKEN_STRING_CONTENT@79..84 "entry"
|
||||
TOKEN_STRING_END@84..85 "\""
|
||||
TOKEN_SEMICOLON@85..86 ";"
|
||||
TOKEN_WHITESPACE@86..91 "\n "
|
||||
TOKEN_IDENT@91..98 "another"
|
||||
TOKEN_WHITESPACE@98..99 " "
|
||||
TOKEN_ASSIGN@99..100 "="
|
||||
TOKEN_WHITESPACE@100..101 " "
|
||||
NODE_STRING@101..114
|
||||
TOKEN_STRING_START@101..102 "\""
|
||||
TOKEN_STRING_CONTENT@102..113 "valid entry"
|
||||
TOKEN_STRING_END@113..114 "\""
|
||||
TOKEN_SEMICOLON@114..115 ";"
|
||||
TOKEN_WHITESPACE@115..118 "\n "
|
||||
TOKEN_R_BRACE@118..119 "}"
|
||||
TOKEN_SEMICOLON@119..120 ";"
|
||||
TOKEN_WHITESPACE@120..123 "\n "
|
||||
NODE_ATTRPATH_VALUE@123..166
|
||||
NODE_ATTRPATH@123..128
|
||||
NODE_IDENT@123..128
|
||||
TOKEN_IDENT@123..128 "test2"
|
||||
TOKEN_WHITESPACE@128..129 " "
|
||||
TOKEN_ASSIGN@129..130 "="
|
||||
TOKEN_WHITESPACE@130..131 " "
|
||||
NODE_ATTR_SET@131..166
|
||||
TOKEN_L_BRACE@131..132 "{"
|
||||
TOKEN_WHITESPACE@132..137 "\n "
|
||||
NODE_ATTRPATH_VALUE@137..150
|
||||
NODE_ATTRPATH@137..142
|
||||
NODE_IDENT@137..142
|
||||
TOKEN_IDENT@137..142 "hello"
|
||||
TOKEN_WHITESPACE@142..143 " "
|
||||
TOKEN_ASSIGN@143..144 "="
|
||||
TOKEN_WHITESPACE@144..145 " "
|
||||
NODE_STRING@145..149
|
||||
TOKEN_STRING_START@145..146 "\""
|
||||
TOKEN_STRING_CONTENT@146..148 "hi"
|
||||
TOKEN_STRING_END@148..149 "\""
|
||||
TOKEN_SEMICOLON@149..150 ";"
|
||||
TOKEN_WHITESPACE@150..155 "\n "
|
||||
NODE_ATTRPATH_VALUE@155..166
|
||||
NODE_ATTRPATH@155..159
|
||||
NODE_IDENT@155..159
|
||||
TOKEN_IDENT@155..159 "aaaa"
|
||||
TOKEN_WHITESPACE@159..162 "\n "
|
||||
NODE_ERROR@162..166
|
||||
TOKEN_R_BRACE@162..163 "}"
|
||||
TOKEN_SEMICOLON@163..164 ";"
|
||||
TOKEN_WHITESPACE@164..165 "\n"
|
||||
TOKEN_R_BRACE@165..166 "}"
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
hello "Hello Ùnicödə" = 1;
|
||||
test = {
|
||||
valid = "entry";
|
||||
invalid "entry";
|
||||
another = "valid entry";
|
||||
};
|
||||
test2 = {
|
||||
hello = "hi";
|
||||
aaaa
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
error: unexpected token at 3..4
|
||||
NODE_ROOT@0..4
|
||||
NODE_ATTR_SET@0..2
|
||||
TOKEN_L_BRACE@0..1 "{"
|
||||
TOKEN_R_BRACE@1..2 "}"
|
||||
TOKEN_WHITESPACE@2..3 "\n"
|
||||
NODE_ERROR@3..4
|
||||
TOKEN_COMMA@3..4 ","
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
{}
|
||||
,
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
error: unexpected double bind at 4..6
|
||||
NODE_ROOT@0..10
|
||||
NODE_LAMBDA@0..10
|
||||
NODE_PATTERN@0..6
|
||||
NODE_PAT_BIND@0..2
|
||||
NODE_IDENT@0..1
|
||||
TOKEN_IDENT@0..1 "f"
|
||||
TOKEN_AT@1..2 "@"
|
||||
TOKEN_L_BRACE@2..3 "{"
|
||||
TOKEN_R_BRACE@3..4 "}"
|
||||
NODE_ERROR@4..6
|
||||
TOKEN_AT@4..5 "@"
|
||||
NODE_IDENT@5..6
|
||||
TOKEN_IDENT@5..6 "f"
|
||||
TOKEN_COLON@6..7 ":"
|
||||
TOKEN_WHITESPACE@7..8 "\n"
|
||||
NODE_STRING@8..10
|
||||
TOKEN_STRING_START@8..9 "\""
|
||||
TOKEN_STRING_END@9..10 "\""
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
f@{}@f:
|
||||
""
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
error: unexpected TOKEN_L_PAREN at 12..13, wanted any of [TOKEN_IDENT, TOKEN_OR, TOKEN_CUR_POS]
|
||||
error: unexpected TOKEN_R_PAREN at 14..15, wanted any of [TOKEN_IDENT, TOKEN_OR, TOKEN_CUR_POS]
|
||||
NODE_ROOT@0..20
|
||||
NODE_ATTR_SET@0..20
|
||||
TOKEN_L_BRACE@0..1 "{"
|
||||
TOKEN_WHITESPACE@1..2 " "
|
||||
NODE_INHERIT@2..18
|
||||
TOKEN_INHERIT@2..9 "inherit"
|
||||
TOKEN_WHITESPACE@9..10 " "
|
||||
NODE_IDENT@10..11
|
||||
TOKEN_IDENT@10..11 "a"
|
||||
TOKEN_WHITESPACE@11..12 " "
|
||||
NODE_ERROR@12..13
|
||||
TOKEN_L_PAREN@12..13 "("
|
||||
NODE_IDENT@13..14
|
||||
TOKEN_IDENT@13..14 "b"
|
||||
NODE_ERROR@14..15
|
||||
TOKEN_R_PAREN@14..15 ")"
|
||||
TOKEN_WHITESPACE@15..16 " "
|
||||
NODE_IDENT@16..17
|
||||
TOKEN_IDENT@16..17 "c"
|
||||
TOKEN_SEMICOLON@17..18 ";"
|
||||
TOKEN_WHITESPACE@18..19 " "
|
||||
TOKEN_R_BRACE@19..20 "}"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{ inherit a (b) c; }
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
error: unexpected end of file
|
||||
error: unexpected end of file, wanted any of [TOKEN_SEMICOLON]
|
||||
error: unexpected end of file
|
||||
error: unexpected end of file
|
||||
NODE_ROOT@0..13
|
||||
NODE_LET_IN@0..13
|
||||
TOKEN_LET@0..3 "let"
|
||||
TOKEN_WHITESPACE@3..6 "\n "
|
||||
NODE_INHERIT@6..13
|
||||
TOKEN_INHERIT@6..13 "inherit"
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
let
|
||||
inherit
|
||||
@@ -0,0 +1,5 @@
|
||||
error: unexpected TOKEN_ERROR at 0..1, wanted any of [TOKEN_L_PAREN, TOKEN_REC, TOKEN_L_BRACE, TOKEN_L_BRACK, TOKEN_STRING_START, TOKEN_IDENT]
|
||||
NODE_ROOT@0..1
|
||||
NODE_ERROR@0..1
|
||||
TOKEN_ERROR@0..1 "~"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
~
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
error: unexpected TOKEN_ERROR at 0..9, wanted any of [TOKEN_L_PAREN, TOKEN_REC, TOKEN_L_BRACE, TOKEN_L_BRACK, TOKEN_STRING_START, TOKEN_IDENT]
|
||||
NODE_ROOT@0..9
|
||||
NODE_ERROR@0..9
|
||||
TOKEN_ERROR@0..9 "/foo//bar"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/foo//bar
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
error: unexpected token at 1..7
|
||||
NODE_ROOT@0..7
|
||||
NODE_IDENT@0..1
|
||||
TOKEN_IDENT@0..1 "a"
|
||||
NODE_ERROR@1..7
|
||||
TOKEN_INTERPOL_START@1..3 "${"
|
||||
TOKEN_IDENT@3..4 "b"
|
||||
TOKEN_INTERPOL_END@4..5 "}"
|
||||
TOKEN_PATH_ABS@5..7 "/c"
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
a${b}/c
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
error: unexpected token at 8..9
|
||||
NODE_ROOT@0..9
|
||||
NODE_PATH_REL@0..8
|
||||
TOKEN_PATH_REL@0..2 "./"
|
||||
NODE_INTERPOL@2..8
|
||||
TOKEN_INTERPOL_START@2..4 "${"
|
||||
NODE_IDENT@4..7
|
||||
TOKEN_IDENT@4..7 "foo"
|
||||
TOKEN_INTERPOL_END@7..8 "}"
|
||||
NODE_ERROR@8..9
|
||||
TOKEN_ERROR@8..9 "/"
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
./${foo}/
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
error: unexpected TOKEN_ERROR at 57..66, wanted any of [TOKEN_L_PAREN, TOKEN_REC, TOKEN_L_BRACE, TOKEN_L_BRACK, TOKEN_STRING_START, TOKEN_IDENT]
|
||||
error: unexpected TOKEN_ERROR at 84..93, wanted any of [TOKEN_L_PAREN, TOKEN_REC, TOKEN_L_BRACE, TOKEN_L_BRACK, TOKEN_STRING_START, TOKEN_IDENT]
|
||||
error: unexpected TOKEN_ERROR at 108..110, wanted any of [TOKEN_L_PAREN, TOKEN_REC, TOKEN_L_BRACE, TOKEN_L_BRACK, TOKEN_STRING_START, TOKEN_IDENT]
|
||||
error: unexpected TOKEN_ERROR at 128..133, wanted any of [TOKEN_L_PAREN, TOKEN_REC, TOKEN_L_BRACE, TOKEN_L_BRACK, TOKEN_STRING_START, TOKEN_IDENT]
|
||||
NODE_ROOT@0..141
|
||||
NODE_ATTR_SET@0..141
|
||||
TOKEN_L_BRACE@0..1 "{"
|
||||
TOKEN_WHITESPACE@1..4 "\n "
|
||||
TOKEN_COMMENT@4..38 "# Multiple path error ..."
|
||||
TOKEN_WHITESPACE@38..41 "\n "
|
||||
NODE_ATTRPATH_VALUE@41..67
|
||||
NODE_ATTRPATH@41..54
|
||||
NODE_IDENT@41..54
|
||||
TOKEN_IDENT@41..54 "trailingSlash"
|
||||
TOKEN_WHITESPACE@54..55 " "
|
||||
TOKEN_ASSIGN@55..56 "="
|
||||
TOKEN_WHITESPACE@56..57 " "
|
||||
NODE_ERROR@57..66
|
||||
TOKEN_ERROR@57..66 "/foo/bar/"
|
||||
TOKEN_SEMICOLON@66..67 ";"
|
||||
TOKEN_WHITESPACE@67..70 "\n "
|
||||
NODE_ATTRPATH_VALUE@70..94
|
||||
NODE_ATTRPATH@70..81
|
||||
NODE_IDENT@70..81
|
||||
TOKEN_IDENT@70..81 "doubleSlash"
|
||||
TOKEN_WHITESPACE@81..82 " "
|
||||
TOKEN_ASSIGN@82..83 "="
|
||||
TOKEN_WHITESPACE@83..84 " "
|
||||
NODE_ERROR@84..93
|
||||
TOKEN_ERROR@84..93 "/foo//bar"
|
||||
TOKEN_SEMICOLON@93..94 ";"
|
||||
TOKEN_WHITESPACE@94..97 "\n "
|
||||
NODE_ATTRPATH_VALUE@97..111
|
||||
NODE_ATTRPATH@97..105
|
||||
NODE_IDENT@97..105
|
||||
TOKEN_IDENT@97..105 "bareHome"
|
||||
TOKEN_WHITESPACE@105..106 " "
|
||||
TOKEN_ASSIGN@106..107 "="
|
||||
TOKEN_WHITESPACE@107..108 " "
|
||||
NODE_ERROR@108..110
|
||||
TOKEN_ERROR@108..110 "~/"
|
||||
TOKEN_SEMICOLON@110..111 ";"
|
||||
TOKEN_WHITESPACE@111..114 "\n "
|
||||
NODE_ATTRPATH_VALUE@114..139
|
||||
NODE_ATTRPATH@114..125
|
||||
NODE_IDENT@114..125
|
||||
TOKEN_IDENT@114..125 "tildeMiddle"
|
||||
TOKEN_WHITESPACE@125..126 " "
|
||||
TOKEN_ASSIGN@126..127 "="
|
||||
TOKEN_WHITESPACE@127..128 " "
|
||||
NODE_APPLY@128..138
|
||||
NODE_ERROR@128..133
|
||||
TOKEN_ERROR@128..133 "/foo/"
|
||||
NODE_PATH_HOME@133..138
|
||||
TOKEN_PATH_HOME@133..138 "~/bar"
|
||||
TOKEN_SEMICOLON@138..139 ";"
|
||||
TOKEN_WHITESPACE@139..140 "\n"
|
||||
TOKEN_R_BRACE@140..141 "}"
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
# Multiple path errors in one file
|
||||
trailingSlash = /foo/bar/;
|
||||
doubleSlash = /foo//bar;
|
||||
bareHome = ~/;
|
||||
tildeMiddle = /foo/~/bar;
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
error: unexpected TOKEN_LESS at 0..1, wanted any of [TOKEN_L_PAREN, TOKEN_REC, TOKEN_L_BRACE, TOKEN_L_BRACK, TOKEN_STRING_START, TOKEN_IDENT]
|
||||
error: unexpected end of file
|
||||
NODE_ROOT@0..20
|
||||
NODE_BIN_OP@0..20
|
||||
NODE_APPLY@0..19
|
||||
NODE_ERROR@0..1
|
||||
TOKEN_LESS@0..1 "<"
|
||||
NODE_PATH_REL@1..19
|
||||
TOKEN_PATH_REL@1..9 "nixpkgs/"
|
||||
NODE_INTERPOL@9..15
|
||||
TOKEN_INTERPOL_START@9..11 "${"
|
||||
NODE_IDENT@11..14
|
||||
TOKEN_IDENT@11..14 "foo"
|
||||
TOKEN_INTERPOL_END@14..15 "}"
|
||||
TOKEN_PATH_REL@15..19 "/bar"
|
||||
TOKEN_MORE@19..20 ">"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<nixpkgs/${foo}/bar>
|
||||
@@ -0,0 +1,5 @@
|
||||
error: unexpected TOKEN_ERROR at 0..2, wanted any of [TOKEN_L_PAREN, TOKEN_REC, TOKEN_L_BRACE, TOKEN_L_BRACK, TOKEN_STRING_START, TOKEN_IDENT]
|
||||
NODE_ROOT@0..2
|
||||
NODE_ERROR@0..2
|
||||
TOKEN_ERROR@0..2 "~p"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
~p
|
||||
@@ -0,0 +1,5 @@
|
||||
error: unexpected TOKEN_ERROR at 0..2, wanted any of [TOKEN_L_PAREN, TOKEN_REC, TOKEN_L_BRACE, TOKEN_L_BRACK, TOKEN_STRING_START, TOKEN_IDENT]
|
||||
NODE_ROOT@0..2
|
||||
NODE_ERROR@0..2
|
||||
TOKEN_ERROR@0..2 "~/"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
~/
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
error: unexpected TOKEN_ERROR at 0..3, wanted any of [TOKEN_L_PAREN, TOKEN_REC, TOKEN_L_BRACE, TOKEN_L_BRACK, TOKEN_STRING_START, TOKEN_IDENT]
|
||||
NODE_ROOT@0..3
|
||||
NODE_ERROR@0..3
|
||||
TOKEN_ERROR@0..3 "/a/"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/a/
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
error: unexpected TOKEN_R_BRACK at 0..1, wanted any of [TOKEN_L_PAREN, TOKEN_REC, TOKEN_L_BRACE, TOKEN_L_BRACK, TOKEN_STRING_START, TOKEN_IDENT]
|
||||
error: unexpected end of file, wanted any of [TOKEN_IDENT, TOKEN_OR, TOKEN_CUR_POS]
|
||||
NODE_ROOT@0..2
|
||||
NODE_SELECT@0..2
|
||||
NODE_ERROR@0..1
|
||||
TOKEN_R_BRACK@0..1 "]"
|
||||
TOKEN_DOT@1..2 "."
|
||||
NODE_ATTRPATH@2..2
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
].
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
error: unexpected end of file
|
||||
error: unexpected end of file, wanted any of [TOKEN_SEMICOLON]
|
||||
error: unexpected end of file
|
||||
NODE_ROOT@0..50
|
||||
NODE_ATTR_SET@0..48
|
||||
TOKEN_L_BRACE@0..1 "{"
|
||||
TOKEN_WHITESPACE@1..4 "\n "
|
||||
NODE_ATTRPATH_VALUE@4..48
|
||||
NODE_ATTRPATH@4..11
|
||||
NODE_IDENT@4..11
|
||||
TOKEN_IDENT@4..11 "traceIf"
|
||||
TOKEN_WHITESPACE@11..12 " "
|
||||
TOKEN_ASSIGN@12..13 "="
|
||||
TOKEN_WHITESPACE@13..18 "\n "
|
||||
TOKEN_COMMENT@18..38 "# predicate to check"
|
||||
TOKEN_WHITESPACE@38..43 "\n "
|
||||
NODE_LAMBDA@43..48
|
||||
NODE_IDENT_PARAM@43..47
|
||||
NODE_IDENT@43..47
|
||||
TOKEN_IDENT@43..47 "pred"
|
||||
TOKEN_COLON@47..48 ":"
|
||||
TOKEN_WHITESPACE@48..50 "\n\n"
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
traceIf =
|
||||
# predicate to check
|
||||
pred:
|
||||
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
error: unexpected token at 3..4
|
||||
NODE_ROOT@0..5
|
||||
NODE_ATTR_SET@0..2
|
||||
TOKEN_L_BRACE@0..1 "{"
|
||||
TOKEN_R_BRACE@1..2 "}"
|
||||
TOKEN_WHITESPACE@2..3 " "
|
||||
NODE_ERROR@3..4
|
||||
TOKEN_ASSIGN@3..4 "="
|
||||
TOKEN_WHITESPACE@4..5 "\n"
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
{} =
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
NODE_ROOT@0..9
|
||||
NODE_BIN_OP@0..9
|
||||
NODE_APPLY@0..5
|
||||
NODE_APPLY@0..3
|
||||
NODE_IDENT@0..1
|
||||
TOKEN_IDENT@0..1 "a"
|
||||
TOKEN_WHITESPACE@1..2 " "
|
||||
NODE_LITERAL@2..3
|
||||
TOKEN_INTEGER@2..3 "1"
|
||||
TOKEN_WHITESPACE@3..4 " "
|
||||
NODE_LITERAL@4..5
|
||||
TOKEN_INTEGER@4..5 "2"
|
||||
TOKEN_WHITESPACE@5..6 " "
|
||||
TOKEN_ADD@6..7 "+"
|
||||
TOKEN_WHITESPACE@7..8 " "
|
||||
NODE_LITERAL@8..9
|
||||
TOKEN_INTEGER@8..9 "3"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
a 1 2 + 3
|
||||
@@ -0,0 +1,16 @@
|
||||
NODE_ROOT@0..18
|
||||
NODE_ASSERT@0..18
|
||||
TOKEN_ASSERT@0..6 "assert"
|
||||
TOKEN_WHITESPACE@6..7 " "
|
||||
NODE_BIN_OP@7..11
|
||||
NODE_IDENT@7..8
|
||||
TOKEN_IDENT@7..8 "a"
|
||||
TOKEN_EQUAL@8..10 "=="
|
||||
NODE_IDENT@10..11
|
||||
TOKEN_IDENT@10..11 "b"
|
||||
TOKEN_SEMICOLON@11..12 ";"
|
||||
NODE_STRING@12..18
|
||||
TOKEN_STRING_START@12..13 "\""
|
||||
TOKEN_STRING_CONTENT@13..17 "a==b"
|
||||
TOKEN_STRING_END@17..18 "\""
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
assert a==b;"a==b"
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
NODE_ROOT@0..10
|
||||
NODE_ATTR_SET@0..10
|
||||
TOKEN_L_BRACE@0..1 "{"
|
||||
NODE_ATTRPATH_VALUE@1..9
|
||||
NODE_ATTRPATH@1..6
|
||||
NODE_IDENT@1..2
|
||||
TOKEN_IDENT@1..2 "a"
|
||||
TOKEN_DOT@2..3 "."
|
||||
NODE_IDENT@3..4
|
||||
TOKEN_IDENT@3..4 "b"
|
||||
TOKEN_DOT@4..5 "."
|
||||
NODE_IDENT@5..6
|
||||
TOKEN_IDENT@5..6 "c"
|
||||
TOKEN_ASSIGN@6..7 "="
|
||||
NODE_LITERAL@7..8
|
||||
TOKEN_INTEGER@7..8 "1"
|
||||
TOKEN_SEMICOLON@8..9 ";"
|
||||
TOKEN_R_BRACE@9..10 "}"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{a.b.c=1;}
|
||||
@@ -0,0 +1,28 @@
|
||||
NODE_ROOT@0..49
|
||||
NODE_ATTR_SET@0..49
|
||||
TOKEN_L_BRACE@0..1 "{"
|
||||
TOKEN_WHITESPACE@1..4 "\n "
|
||||
NODE_ATTRPATH_VALUE@4..25
|
||||
NODE_ATTRPATH@4..19
|
||||
NODE_IDENT@4..19
|
||||
TOKEN_IDENT@4..19 "meaning_of_life"
|
||||
TOKEN_WHITESPACE@19..20 " "
|
||||
TOKEN_ASSIGN@20..21 "="
|
||||
TOKEN_WHITESPACE@21..22 " "
|
||||
NODE_LITERAL@22..24
|
||||
TOKEN_INTEGER@22..24 "42"
|
||||
TOKEN_SEMICOLON@24..25 ";"
|
||||
TOKEN_WHITESPACE@25..28 "\n "
|
||||
NODE_ATTRPATH_VALUE@28..47
|
||||
NODE_ATTRPATH@28..38
|
||||
NODE_IDENT@28..38
|
||||
TOKEN_IDENT@28..38 "H4X0RNUM83"
|
||||
TOKEN_WHITESPACE@38..39 " "
|
||||
TOKEN_ASSIGN@39..40 "="
|
||||
TOKEN_WHITESPACE@40..41 " "
|
||||
NODE_LITERAL@41..46
|
||||
TOKEN_FLOAT@41..46 "1.337"
|
||||
TOKEN_SEMICOLON@46..47 ";"
|
||||
TOKEN_WHITESPACE@47..48 "\n"
|
||||
TOKEN_R_BRACE@48..49 "}"
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
meaning_of_life = 42;
|
||||
H4X0RNUM83 = 1.337;
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
NODE_ROOT@0..33
|
||||
NODE_ATTR_SET@0..33
|
||||
TOKEN_L_BRACE@0..1 "{"
|
||||
TOKEN_WHITESPACE@1..4 "\n "
|
||||
NODE_ATTRPATH_VALUE@4..12
|
||||
NODE_ATTRPATH@4..7
|
||||
NODE_IDENT@4..5
|
||||
TOKEN_IDENT@4..5 "a"
|
||||
TOKEN_DOT@5..6 "."
|
||||
NODE_IDENT@6..7
|
||||
TOKEN_IDENT@6..7 "b"
|
||||
TOKEN_WHITESPACE@7..8 " "
|
||||
TOKEN_ASSIGN@8..9 "="
|
||||
TOKEN_WHITESPACE@9..10 " "
|
||||
NODE_LITERAL@10..11
|
||||
TOKEN_INTEGER@10..11 "2"
|
||||
TOKEN_SEMICOLON@11..12 ";"
|
||||
TOKEN_WHITESPACE@12..15 "\n "
|
||||
NODE_ATTRPATH_VALUE@15..31
|
||||
NODE_ATTRPATH@15..26
|
||||
NODE_STRING@15..21
|
||||
TOKEN_STRING_START@15..16 "\""
|
||||
NODE_INTERPOL@16..20
|
||||
TOKEN_INTERPOL_START@16..18 "${"
|
||||
NODE_IDENT@18..19
|
||||
TOKEN_IDENT@18..19 "c"
|
||||
TOKEN_INTERPOL_END@19..20 "}"
|
||||
TOKEN_STRING_END@20..21 "\""
|
||||
TOKEN_DOT@21..22 "."
|
||||
NODE_DYNAMIC@22..26
|
||||
TOKEN_INTERPOL_START@22..24 "${"
|
||||
NODE_IDENT@24..25
|
||||
TOKEN_IDENT@24..25 "d"
|
||||
TOKEN_INTERPOL_END@25..26 "}"
|
||||
TOKEN_WHITESPACE@26..27 " "
|
||||
TOKEN_ASSIGN@27..28 "="
|
||||
TOKEN_WHITESPACE@28..29 " "
|
||||
NODE_LITERAL@29..30
|
||||
TOKEN_INTEGER@29..30 "3"
|
||||
TOKEN_SEMICOLON@30..31 ";"
|
||||
TOKEN_WHITESPACE@31..32 "\n"
|
||||
TOKEN_R_BRACE@32..33 "}"
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
a.b = 2;
|
||||
"${c}".${d} = 3;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
NODE_ROOT@0..2
|
||||
NODE_ATTR_SET@0..2
|
||||
TOKEN_L_BRACE@0..1 "{"
|
||||
TOKEN_R_BRACE@1..2 "}"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1,14 @@
|
||||
NODE_ROOT@0..12
|
||||
NODE_ATTR_SET@0..12
|
||||
TOKEN_REC@0..3 "rec"
|
||||
TOKEN_L_BRACE@3..4 "{"
|
||||
NODE_ATTRPATH_VALUE@4..11
|
||||
NODE_ATTRPATH@4..8
|
||||
NODE_IDENT@4..8
|
||||
TOKEN_IDENT@4..8 "test"
|
||||
TOKEN_ASSIGN@8..9 "="
|
||||
NODE_LITERAL@9..10
|
||||
TOKEN_INTEGER@9..10 "1"
|
||||
TOKEN_SEMICOLON@10..11 ";"
|
||||
TOKEN_R_BRACE@11..12 "}"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
rec{test=1;}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
NODE_ROOT@0..20
|
||||
NODE_BIN_OP@0..20
|
||||
NODE_BIN_OP@0..3
|
||||
NODE_LITERAL@0..1
|
||||
TOKEN_INTEGER@0..1 "1"
|
||||
TOKEN_LESS@1..2 "<"
|
||||
NODE_LITERAL@2..3
|
||||
TOKEN_INTEGER@2..3 "2"
|
||||
TOKEN_OR_OR@3..5 "||"
|
||||
NODE_BIN_OP@5..20
|
||||
NODE_BIN_OP@5..14
|
||||
NODE_BIN_OP@5..9
|
||||
NODE_LITERAL@5..6
|
||||
TOKEN_INTEGER@5..6 "2"
|
||||
TOKEN_LESS_OR_EQ@6..8 "<="
|
||||
NODE_LITERAL@8..9
|
||||
TOKEN_INTEGER@8..9 "2"
|
||||
TOKEN_AND_AND@9..11 "&&"
|
||||
NODE_BIN_OP@11..14
|
||||
NODE_LITERAL@11..12
|
||||
TOKEN_INTEGER@11..12 "2"
|
||||
TOKEN_MORE@12..13 ">"
|
||||
NODE_LITERAL@13..14
|
||||
TOKEN_INTEGER@13..14 "1"
|
||||
TOKEN_AND_AND@14..16 "&&"
|
||||
NODE_BIN_OP@16..20
|
||||
NODE_LITERAL@16..17
|
||||
TOKEN_INTEGER@16..17 "2"
|
||||
TOKEN_MORE_OR_EQ@17..19 ">="
|
||||
NODE_LITERAL@19..20
|
||||
TOKEN_INTEGER@19..20 "2"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1<2||2<=2&&2>1&&2>=2
|
||||
@@ -0,0 +1,23 @@
|
||||
NODE_ROOT@0..33
|
||||
NODE_BIN_OP@0..33
|
||||
NODE_IDENT@0..5
|
||||
TOKEN_IDENT@0..5 "false"
|
||||
TOKEN_WHITESPACE@5..6 " "
|
||||
TOKEN_IMPLICATION@6..8 "->"
|
||||
NODE_BIN_OP@8..33
|
||||
NODE_BIN_OP@8..27
|
||||
NODE_UNARY_OP@8..14
|
||||
TOKEN_INVERT@8..9 "!"
|
||||
NODE_IDENT@9..14
|
||||
TOKEN_IDENT@9..14 "false"
|
||||
TOKEN_AND_AND@14..16 "&&"
|
||||
NODE_BIN_OP@16..27
|
||||
NODE_IDENT@16..21
|
||||
TOKEN_IDENT@16..21 "false"
|
||||
TOKEN_EQUAL@21..23 "=="
|
||||
NODE_IDENT@23..27
|
||||
TOKEN_IDENT@23..27 "true"
|
||||
TOKEN_OR_OR@27..29 "||"
|
||||
NODE_IDENT@29..33
|
||||
TOKEN_IDENT@29..33 "true"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
false ->!false&&false==true||true
|
||||
@@ -0,0 +1,16 @@
|
||||
NODE_ROOT@0..10
|
||||
NODE_BIN_OP@0..10
|
||||
NODE_BIN_OP@0..4
|
||||
NODE_LITERAL@0..1
|
||||
TOKEN_INTEGER@0..1 "1"
|
||||
TOKEN_EQUAL@1..3 "=="
|
||||
NODE_LITERAL@3..4
|
||||
TOKEN_INTEGER@3..4 "1"
|
||||
TOKEN_AND_AND@4..6 "&&"
|
||||
NODE_BIN_OP@6..10
|
||||
NODE_LITERAL@6..7
|
||||
TOKEN_INTEGER@6..7 "2"
|
||||
TOKEN_NOT_EQUAL@7..9 "!="
|
||||
NODE_LITERAL@9..10
|
||||
TOKEN_INTEGER@9..10 "3"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1==1&&2!=3
|
||||
Loaded 100 of 492 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user