mirror of
https://github.com/uutils/awk.git
synced 2026-06-10 16:15:04 -07:00
Initial commit
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/target
|
||||
Generated
+545
File diff suppressed because it is too large
Load Diff
+129
@@ -0,0 +1,129 @@
|
||||
[package]
|
||||
name = "uu_awk"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "awk"
|
||||
path = "src/main.rs"
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["interpreter","lexer", "parser"]
|
||||
|
||||
[workspace.dependencies]
|
||||
lexer.path = "./lexer"
|
||||
parser.path = "./parser"
|
||||
interpreter.path = "./interpreter"
|
||||
color-eyre = "0.6.5"
|
||||
either = "1.15.0"
|
||||
memchr = "2.8.0"
|
||||
thiserror = "2.0.18"
|
||||
tracing = "0.1.44"
|
||||
smallvec = { version = "1.15.1", features = ["union", "const_generics", "const_new"] }
|
||||
bumpalo = { version = "3.20.2", features = ["boxed", "collections", "std", "allocator-api2"] }
|
||||
hashbrown = "0.17.0"
|
||||
allocator-api2 = "0.4.0"
|
||||
|
||||
[dependencies]
|
||||
# lexer.workspace = true # FIXME(parser complete): bypass through parser
|
||||
interpreter.workspace = true
|
||||
parser.workspace = true
|
||||
color-eyre.workspace = true
|
||||
either.workspace = true
|
||||
memchr.workspace = true
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
bumpalo.workspace = true
|
||||
rustix = { version = "1.1.4", default-features = false, features = ["process"]}
|
||||
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
|
||||
tracing-error = "0.2.1"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
panic = "abort"
|
||||
codegen-units = 1
|
||||
debug = true
|
||||
|
||||
# A release-like profile that is as small as possible.
|
||||
[profile.release-small]
|
||||
inherits = "release"
|
||||
opt-level = "z"
|
||||
strip = true
|
||||
|
||||
[profile.dev.package.backtrace]
|
||||
opt-level = 3
|
||||
|
||||
# A release-like profile with debug info for profiling.
|
||||
# See https://github.com/mstange/samply .
|
||||
[profile.profiling]
|
||||
inherits = "release"
|
||||
panic = "unwind"
|
||||
debug = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
# This is the linting configuration for all crates.
|
||||
# In order to use these, all crates have `[lints] workspace = true` section.
|
||||
[workspace.lints.rust]
|
||||
# Allow "fuzzing" as a "cfg" condition name and "cygwin" as a value for "target_os"
|
||||
# https://doc.rust-lang.org/nightly/rustc/check-cfg/cargo-specifics.html
|
||||
unexpected_cfgs = { level = "warn", check-cfg = [
|
||||
'cfg(fuzzing)',
|
||||
'cfg(target_os, values("cygwin"))',
|
||||
'cfg(wasi_runner)',
|
||||
] }
|
||||
unused_qualifications = "warn"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
collapsible_if = { level = "allow", priority = 127 } # remove me
|
||||
# The counts were generated with this command:
|
||||
# cargo clippy --all-targets --workspace --message-format=json --quiet \
|
||||
# | jq -r '.message.code.code | select(. != null and startswith("clippy::"))' \
|
||||
# | sort | uniq -c | sort -h -r
|
||||
#
|
||||
all = { level = "warn", priority = -1 }
|
||||
cargo = { level = "warn", priority = -1 }
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
use_self = "warn" # nursery lint
|
||||
cargo_common_metadata = "allow" # 3240
|
||||
multiple_crate_versions = "allow" # 2882
|
||||
missing_errors_doc = "allow" # 1572
|
||||
missing_panics_doc = "allow" # 946
|
||||
must_use_candidate = "allow" # 322
|
||||
match_same_arms = "allow" # 204
|
||||
cast_possible_truncation = "allow" # 122
|
||||
too_many_lines = "allow" # 101
|
||||
cast_possible_wrap = "allow" # 78
|
||||
cast_sign_loss = "allow" # 70
|
||||
struct_excessive_bools = "allow" # 68
|
||||
cast_precision_loss = "allow" # 52
|
||||
cast_lossless = "allow" # 35
|
||||
ignored_unit_patterns = "allow" # 21
|
||||
similar_names = "allow" # 20
|
||||
needless_pass_by_value = "allow" # 16
|
||||
float_cmp = "allow" # 12
|
||||
items_after_statements = "allow" # 11
|
||||
return_self_not_must_use = "allow" # 8
|
||||
inline_always = "allow" # 6
|
||||
fn_params_excessive_bools = "allow" # 6
|
||||
used_underscore_items = "allow" # 2
|
||||
should_panic_without_expect = "allow" # 2
|
||||
|
||||
doc_markdown = "allow"
|
||||
unused_self = "allow"
|
||||
enum_glob_use = "allow"
|
||||
unnested_or_patterns = "allow"
|
||||
implicit_hasher = "allow"
|
||||
doc_link_with_quotes = "allow"
|
||||
format_push_string = "allow"
|
||||
flat_map_option = "allow"
|
||||
from_iter_instead_of_collect = "allow"
|
||||
large_types_passed_by_value = "allow"
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
Copyright (c) 2026- Guillem Larrosa Jara
|
||||
Copyright (c) 2026- The uutils AWK Contributors
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2026- Guillem Larrosa Jara
|
||||
Copyright (c) 2026- The uutils AWK Contributors
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,15 @@
|
||||
# uutils AWK
|
||||
|
||||
This is a human, WIP, and clean implementation of an AWK interpreter, written in Rust and compatible with GNU's AWK (`gawk`) bug-for-bug. Expected to be production-ready before Ubuntu 26.10. Made with love.
|
||||
|
||||
## State of the Repo
|
||||
|
||||
The lexer is pretty much done; however, it should track the span of the tokens so we can produce contextual error messages in the parser, and it is also lacking a POSIX-compatibility mode (trivial). The parser is mostly done, although it probably has some rough edges, and the preprocessor is TBD; also, the Pratt routine is a bit spaghetti and should be refactored (trivial). The interpreter is also TBD; work on it will be started once we get good testing on the parser. Expect this to be a fast-paced repo.
|
||||
|
||||
## Contributing
|
||||
|
||||
See [this](https://github.com/uutils/coreutils/blob/main/CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
This is licensed under either the MIT License or the Apache License v2.0. See the `LICENSE-MIT` and `LICENSE-APACHE` files for details.
|
||||
@@ -0,0 +1,240 @@
|
||||
# This template contains all of the possible sections and their default values
|
||||
|
||||
# Note that all fields that take a lint level have these possible values:
|
||||
# * deny - An error will be produced and the check will fail
|
||||
# * warn - A warning will be produced, but the check will not fail
|
||||
# * allow - No warning or error will be produced, though in some cases a note
|
||||
# will be
|
||||
|
||||
# The values provided in this template are the default values that will be used
|
||||
# when any section or field is not specified in your own configuration
|
||||
|
||||
# Root options
|
||||
|
||||
# The graph table configures how the dependency graph is constructed and thus
|
||||
# which crates the checks are performed against
|
||||
[graph]
|
||||
# If 1 or more target triples (and optionally, target_features) are specified,
|
||||
# only the specified targets will be checked when running `cargo deny check`.
|
||||
# This means, if a particular package is only ever used as a target specific
|
||||
# dependency, such as, for example, the `nix` crate only being used via the
|
||||
# `target_family = "unix"` configuration, that only having windows targets in
|
||||
# this list would mean the nix crate, as well as any of its exclusive
|
||||
# dependencies not shared by any other crates, would be ignored, as the target
|
||||
# list here is effectively saying which targets you are building for.
|
||||
targets = [
|
||||
# The triple can be any string, but only the target triples built in to
|
||||
# rustc (as of 1.40) can be checked against actual config expressions
|
||||
#"x86_64-unknown-linux-musl",
|
||||
# You can also specify which target_features you promise are enabled for a
|
||||
# particular target. target_features are currently not validated against
|
||||
# the actual valid features supported by the target architecture.
|
||||
#{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
|
||||
]
|
||||
# When creating the dependency graph used as the source of truth when checks are
|
||||
# executed, this field can be used to prune crates from the graph, removing them
|
||||
# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate
|
||||
# is pruned from the graph, all of its dependencies will also be pruned unless
|
||||
# they are connected to another crate in the graph that hasn't been pruned,
|
||||
# so it should be used with care. The identifiers are [Package ID Specifications]
|
||||
# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html)
|
||||
#exclude = []
|
||||
# If true, metadata will be collected with `--all-features`. Note that this can't
|
||||
# be toggled off if true, if you want to conditionally enable `--all-features` it
|
||||
# is recommended to pass `--all-features` on the cmd line instead
|
||||
all-features = false
|
||||
# If true, metadata will be collected with `--no-default-features`. The same
|
||||
# caveat with `all-features` applies
|
||||
no-default-features = false
|
||||
# If set, these feature will be enabled when collecting metadata. If `--features`
|
||||
# is specified on the cmd line they will take precedence over this option.
|
||||
#features = []
|
||||
|
||||
# The output table provides options for how/if diagnostics are outputted
|
||||
[output]
|
||||
# When outputting inclusion graphs in diagnostics that include features, this
|
||||
# option can be used to specify the depth at which feature edges will be added.
|
||||
# This option is included since the graphs can be quite large and the addition
|
||||
# of features from the crate(s) to all of the graph roots can be far too verbose.
|
||||
# This option can be overridden via `--feature-depth` on the cmd line
|
||||
feature-depth = 1
|
||||
|
||||
# This section is considered when running `cargo deny check advisories`
|
||||
# More documentation for the advisories section can be found here:
|
||||
# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
|
||||
[advisories]
|
||||
# The path where the advisory databases are cloned/fetched into
|
||||
#db-path = "$CARGO_HOME/advisory-dbs"
|
||||
# The url(s) of the advisory databases to use
|
||||
#db-urls = ["https://github.com/rustsec/advisory-db"]
|
||||
# A list of advisory IDs to ignore. Note that ignored advisories will still
|
||||
# output a note when they are encountered.
|
||||
ignore = [
|
||||
#"RUSTSEC-0000-0000",
|
||||
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
|
||||
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
|
||||
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
|
||||
]
|
||||
# If this is true, then cargo deny will use the git executable to fetch advisory database.
|
||||
# If this is false, then it uses a built-in git library.
|
||||
# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support.
|
||||
# See Git Authentication for more information about setting up git authentication.
|
||||
#git-fetch-with-cli = true
|
||||
|
||||
# This section is considered when running `cargo deny check licenses`
|
||||
# More documentation for the licenses section can be found here:
|
||||
# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
|
||||
[licenses]
|
||||
# List of explicitly allowed licenses
|
||||
# See https://spdx.org/licenses/ for list of possible licenses
|
||||
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
|
||||
allow = [
|
||||
"MIT",
|
||||
"Apache-2.0",
|
||||
# "Apache-2.0 WITH LLVM-exception",
|
||||
"Unicode-3.0"
|
||||
]
|
||||
# The confidence threshold for detecting a license from license text.
|
||||
# The higher the value, the more closely the license text must be to the
|
||||
# canonical license text of a valid SPDX license file.
|
||||
# [possible values: any between 0.0 and 1.0].
|
||||
confidence-threshold = 0.8
|
||||
# Allow 1 or more licenses on a per-crate basis, so that particular licenses
|
||||
# aren't accepted for every possible crate as with the normal allow list
|
||||
exceptions = [
|
||||
# Each entry is the crate and version constraint, and its specific allow
|
||||
# list
|
||||
#{ allow = ["Zlib"], crate = "adler32" },
|
||||
]
|
||||
|
||||
# Some crates don't have (easily) machine readable licensing information,
|
||||
# adding a clarification entry for it allows you to manually specify the
|
||||
# licensing information
|
||||
#[[licenses.clarify]]
|
||||
# The package spec the clarification applies to
|
||||
#crate = "ring"
|
||||
# The SPDX expression for the license requirements of the crate
|
||||
#expression = "MIT AND ISC AND OpenSSL"
|
||||
# One or more files in the crate's source used as the "source of truth" for
|
||||
# the license expression. If the contents match, the clarification will be used
|
||||
# when running the license check, otherwise the clarification will be ignored
|
||||
# and the crate will be checked normally, which may produce warnings or errors
|
||||
# depending on the rest of your configuration
|
||||
#license-files = [
|
||||
# Each entry is a crate relative path, and the (opaque) hash of its contents
|
||||
#{ path = "LICENSE", hash = 0xbd0eed23 }
|
||||
#]
|
||||
|
||||
[licenses.private]
|
||||
# If true, ignores workspace crates that aren't published, or are only
|
||||
# published to private registries.
|
||||
# To see how to mark a crate as unpublished (to the official registry),
|
||||
# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field.
|
||||
ignore = false
|
||||
# One or more private registries that you might publish crates to, if a crate
|
||||
# is only published to private registries, and ignore is true, the crate will
|
||||
# not have its license(s) checked
|
||||
registries = [
|
||||
#"https://sekretz.com/registry
|
||||
]
|
||||
|
||||
# This section is considered when running `cargo deny check bans`.
|
||||
# More documentation about the 'bans' section can be found here:
|
||||
# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
|
||||
[bans]
|
||||
# Lint level for when multiple versions of the same crate are detected
|
||||
multiple-versions = "warn"
|
||||
# Lint level for when a crate version requirement is `*`
|
||||
wildcards = "allow"
|
||||
# The graph highlighting used when creating dotgraphs for crates
|
||||
# with multiple versions
|
||||
# * lowest-version - The path to the lowest versioned duplicate is highlighted
|
||||
# * simplest-path - The path to the version with the fewest edges is highlighted
|
||||
# * all - Both lowest-version and simplest-path are used
|
||||
highlight = "all"
|
||||
# The default lint level for `default` features for crates that are members of
|
||||
# the workspace that is being checked. This can be overridden by allowing/denying
|
||||
# `default` on a crate-by-crate basis if desired.
|
||||
workspace-default-features = "allow"
|
||||
# The default lint level for `default` features for external crates that are not
|
||||
# members of the workspace. This can be overridden by allowing/denying `default`
|
||||
# on a crate-by-crate basis if desired.
|
||||
external-default-features = "allow"
|
||||
# List of crates that are allowed. Use with care!
|
||||
allow = [
|
||||
#"ansi_term@0.11.0",
|
||||
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" },
|
||||
]
|
||||
# If true, workspace members are automatically allowed even when using deny-by-default
|
||||
# This is useful for organizations that want to deny all external dependencies by default
|
||||
# but allow their own workspace crates without having to explicitly list them
|
||||
allow-workspace = false
|
||||
# List of crates to deny
|
||||
deny = [
|
||||
#"ansi_term@0.11.0",
|
||||
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" },
|
||||
# Wrapper crates can optionally be specified to allow the crate when it
|
||||
# is a direct dependency of the otherwise banned crate
|
||||
#{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] },
|
||||
]
|
||||
|
||||
# List of features to allow/deny
|
||||
# Each entry the name of a crate and a version range. If version is
|
||||
# not specified, all versions will be matched.
|
||||
#[[bans.features]]
|
||||
#crate = "reqwest"
|
||||
# Features to not allow
|
||||
#deny = ["json"]
|
||||
# Features to allow
|
||||
#allow = [
|
||||
# "rustls",
|
||||
# "__rustls",
|
||||
# "__tls",
|
||||
# "hyper-rustls",
|
||||
# "rustls",
|
||||
# "rustls-pemfile",
|
||||
# "rustls-tls-webpki-roots",
|
||||
# "tokio-rustls",
|
||||
# "webpki-roots",
|
||||
#]
|
||||
# If true, the allowed features must exactly match the enabled feature set. If
|
||||
# this is set there is no point setting `deny`
|
||||
#exact = true
|
||||
|
||||
# Certain crates/versions that will be skipped when doing duplicate detection.
|
||||
skip = [
|
||||
#"ansi_term@0.11.0",
|
||||
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" },
|
||||
]
|
||||
# Similarly to `skip` allows you to skip certain crates during duplicate
|
||||
# detection. Unlike skip, it also includes the entire tree of transitive
|
||||
# dependencies starting at the specified crate, up to a certain depth, which is
|
||||
# by default infinite.
|
||||
skip-tree = [
|
||||
#"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies
|
||||
#{ crate = "ansi_term@0.11.0", depth = 20 },
|
||||
]
|
||||
|
||||
# This section is considered when running `cargo deny check sources`.
|
||||
# More documentation about the 'sources' section can be found here:
|
||||
# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
|
||||
[sources]
|
||||
# Lint level for what to happen when a crate from a crate registry that is not
|
||||
# in the allow list is encountered
|
||||
unknown-registry = "warn"
|
||||
# Lint level for what to happen when a crate from a git repository that is not
|
||||
# in the allow list is encountered
|
||||
unknown-git = "warn"
|
||||
# List of URLs for allowed crate registries. Defaults to the crates.io index
|
||||
# if not specified. If it is specified but empty, no registries are allowed.
|
||||
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
|
||||
# List of URLs for allowed Git repositories
|
||||
allow-git = []
|
||||
|
||||
[sources.allow-org]
|
||||
# github.com organizations to allow git sources for
|
||||
github = []
|
||||
# gitlab.com organizations to allow git sources for
|
||||
gitlab = []
|
||||
# bitbucket.org organizations to allow git sources for
|
||||
bitbucket = []
|
||||
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "interpreter"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
color-eyre.workspace = true
|
||||
either.workspace = true
|
||||
memchr.workspace = true
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,22 @@
|
||||
use color_eyre::eyre::Result;
|
||||
use either::Either;
|
||||
|
||||
pub enum BuiltinCommand {}
|
||||
pub enum BuiltinVar {}
|
||||
|
||||
pub type Command<'a> = Either<BuiltinCommand, &'a str>;
|
||||
pub type Variable<'a> = Either<BuiltinVar, &'a str>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Interpreter;
|
||||
|
||||
impl Interpreter {
|
||||
#[tracing::instrument]
|
||||
pub fn run(self) -> Result<Option<i32>> {
|
||||
todo!()
|
||||
}
|
||||
pub fn eval_expression(&mut self) {}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum InterpreterError {}
|
||||
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "lexer"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
logos = "0.16.1"
|
||||
# bytecount = "0.6.9"
|
||||
memchr.workspace = true
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,412 @@
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use core::str;
|
||||
use std::{borrow::Cow, fmt::Debug, slice::SliceIndex};
|
||||
|
||||
use logos::Skip;
|
||||
pub use logos::{Logos, Span};
|
||||
use memchr::{memchr, memchr3};
|
||||
use thiserror::Error;
|
||||
|
||||
pub type Lexer<'a> = logos::Lexer<'a, Token<'a>>;
|
||||
pub type Result<T, E = LexingError> = std::result::Result<T, E>;
|
||||
|
||||
// TODO: check wnat GNU does about potentially reserved `@ident`; add error branch if so.
|
||||
#[derive(Logos, Debug, PartialEq)]
|
||||
#[logos(utf8 = false)]
|
||||
#[logos(skip("[ \t]+"))]
|
||||
#[logos(skip(r"(\\\n)+"))]
|
||||
#[logos(skip("#", skip_line))]
|
||||
#[logos(extras = Context)]
|
||||
#[logos(subpattern identifier = r"[a-zA-Z_][a-zA-Z0-9_]*")]
|
||||
#[logos(error(LexingError, callback = |lex| LexingError::from(lex)))]
|
||||
pub enum Token<'a> {
|
||||
#[regex(r"[0-9]+(\.[0-9]+)?([eE][+-]?[0-9]+)?", parse_float)]
|
||||
#[regex(r"\.[0-9]+([eE][+-]?[0-9]+)?", parse_float)]
|
||||
Number(f64),
|
||||
#[token("\"", parse_string)]
|
||||
String(Slice<'a>),
|
||||
#[token("BEGIN", accept_expression)]
|
||||
BeginPattern,
|
||||
#[token("END", accept_expression)]
|
||||
EndPattern,
|
||||
#[token("BEGINFILE", accept_expression)]
|
||||
BeginFilePattern,
|
||||
#[token("ENDFILE", accept_expression)]
|
||||
EndFilePattern,
|
||||
#[token("@load \"", parse_directive)]
|
||||
LoadDirective(Slice<'a>),
|
||||
#[token("@include \"", parse_directive)]
|
||||
IncludeDirective(Slice<'a>),
|
||||
#[token("@nsinclude \"", parse_directive)]
|
||||
NsIncludeDirective(Slice<'a>),
|
||||
#[regex("@namespace \"(?&identifier)\"", |lex| parse_ident(lex, 12..lex.slice().len() - 1))]
|
||||
NamespaceDirective(&'a str),
|
||||
#[token("@concurrent", accept_expression)]
|
||||
ConcurrentDirective,
|
||||
#[token("if", accept_expression)]
|
||||
If,
|
||||
#[regex(r"else\n*", accept_expression)]
|
||||
Else,
|
||||
#[token("switch", accept_expression)]
|
||||
Switch,
|
||||
#[token("case", accept_expression)]
|
||||
Case,
|
||||
#[token("default", accept_expression)]
|
||||
Default,
|
||||
#[regex(r"do\n*", accept_expression)]
|
||||
Do,
|
||||
#[token("while", accept_expression)]
|
||||
While,
|
||||
#[token("for", accept_expression)]
|
||||
For,
|
||||
#[token("in", accept_expression)]
|
||||
In,
|
||||
#[token("print", accept_expression)]
|
||||
Print,
|
||||
#[token("printf", accept_expression)]
|
||||
Printf,
|
||||
#[token("getline", accept_expression)]
|
||||
Getline,
|
||||
#[token("next", accept_expression)]
|
||||
Next,
|
||||
#[token("nextfile", accept_expression)]
|
||||
NextFile,
|
||||
#[token("exit", accept_expression)]
|
||||
Exit,
|
||||
#[token("break", accept_expression)]
|
||||
Break,
|
||||
#[token("continue", accept_expression)]
|
||||
Continue,
|
||||
#[token("return", accept_expression)]
|
||||
Return,
|
||||
#[token("delete", accept_expression)]
|
||||
Delete,
|
||||
#[token("function", accept_expression)]
|
||||
Function,
|
||||
#[token("NR", accept_expression)]
|
||||
NrVariable,
|
||||
#[token("NF", accept_expression)]
|
||||
NfVariable,
|
||||
#[token("FS", accept_expression)]
|
||||
FsVariable,
|
||||
#[token("RS", accept_expression)]
|
||||
RsVariable,
|
||||
#[token("OFS", accept_expression)]
|
||||
OfsVariable,
|
||||
#[token("ORS", accept_expression)]
|
||||
OrsVariable,
|
||||
#[token("FILENAME", accept_expression)]
|
||||
FilenameVariable,
|
||||
#[token("ARGC", accept_expression)]
|
||||
ArgcVariable,
|
||||
#[token("ARGV", accept_expression)]
|
||||
ArgvVariable,
|
||||
#[token("SUBSEP", accept_expression)]
|
||||
SubsepVariable,
|
||||
#[token("FNR", accept_expression)]
|
||||
FnrVariable,
|
||||
#[token("ARGIND")]
|
||||
ArgindVariable,
|
||||
#[token("OFMT", accept_expression)]
|
||||
OfmtVariable,
|
||||
#[token("RSTART", accept_expression)]
|
||||
RstartVariable,
|
||||
#[token("RLENGTH", accept_expression)]
|
||||
RlengthVariable,
|
||||
#[token("ENVIRON", accept_expression)]
|
||||
EnvironVariable,
|
||||
#[regex("(?&identifier)", Identifier::without_namespace)]
|
||||
#[regex(r"(?&identifier)::(?&identifier)", Identifier::with_namespace)]
|
||||
Identifier(Identifier<'a>),
|
||||
#[regex(r"(?&identifier)\(", Identifier::without_namespace)]
|
||||
#[regex(r"(?&identifier)::(?&identifier)\(", Identifier::with_namespace)]
|
||||
FunctionCall(Identifier<'a>),
|
||||
#[token("+", accept_expression)]
|
||||
Plus,
|
||||
#[token("-", accept_expression)]
|
||||
Minus,
|
||||
#[token("*", accept_expression)]
|
||||
Star,
|
||||
#[token("/", parse_regex_or_slash)]
|
||||
Slash,
|
||||
/// Not generated by Logos directly.
|
||||
Regex(Slice<'a>),
|
||||
#[token("%", accept_expression)]
|
||||
Percent,
|
||||
#[token("^", accept_expression)]
|
||||
Circumflex,
|
||||
#[token("++", accept_expression)]
|
||||
Increment,
|
||||
#[token("--", accept_expression)]
|
||||
Decrement,
|
||||
#[token("=", accept_expression)]
|
||||
Assignment,
|
||||
#[token("+=", accept_expression)]
|
||||
PlusAssign,
|
||||
#[token("-=", accept_expression)]
|
||||
MinusAssign,
|
||||
#[token("*=", accept_expression)]
|
||||
StarAssign,
|
||||
#[token("/=", accept_expression)]
|
||||
SlashAssign,
|
||||
#[token("%=", accept_expression)]
|
||||
PercentAssign,
|
||||
#[token("^=", accept_expression)]
|
||||
CaretAssign,
|
||||
#[token("==", accept_expression)]
|
||||
EqualTo,
|
||||
#[token("!=", accept_expression)]
|
||||
NotEqualTo,
|
||||
#[token("<", accept_expression)]
|
||||
LesserThan,
|
||||
#[token("<=", accept_expression)]
|
||||
LesserOrEqualThan,
|
||||
#[token(">", accept_expression)]
|
||||
GreaterThan,
|
||||
#[token(">=", accept_expression)]
|
||||
GreaterOrEqualThan,
|
||||
#[regex(r"&&\n*", accept_expression)]
|
||||
BooleanAnd,
|
||||
#[regex(r"\|\|\n*", accept_expression)]
|
||||
BooleanOr,
|
||||
#[token("!", accept_expression)]
|
||||
Negation,
|
||||
#[token("~", accept_expression)]
|
||||
Matching,
|
||||
#[token("!~", accept_expression)]
|
||||
NotMatching,
|
||||
#[token("|", accept_expression)]
|
||||
Pipe,
|
||||
#[token("|&", accept_expression)]
|
||||
DoublePipe,
|
||||
#[token(">>", accept_expression)]
|
||||
AppendPipe,
|
||||
#[regex(r"\?\n*", accept_expression)]
|
||||
QuestionMark,
|
||||
#[regex(r":\n*", accept_expression)]
|
||||
Colon,
|
||||
#[regex(r"\{\n*", accept_expression)]
|
||||
OpenBrace,
|
||||
#[token("}", accept_expression)]
|
||||
ClosedBrace,
|
||||
#[token("(", accept_expression)]
|
||||
OpenParent,
|
||||
#[token(")", accept_operator)]
|
||||
ClosedParent,
|
||||
#[token("[", accept_expression)]
|
||||
OpenBracket,
|
||||
#[token("]", accept_operator)]
|
||||
ClosedBracket,
|
||||
#[regex(r",\n*", accept_expression)]
|
||||
Comma,
|
||||
#[token("$", accept_expression)]
|
||||
Record,
|
||||
#[regex(r"\n+", accept_expression)]
|
||||
Newline,
|
||||
#[regex(";+", accept_expression)]
|
||||
Semicolon,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub enum Context {
|
||||
#[default]
|
||||
AcceptExpression,
|
||||
AcceptOperator,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Error, Clone, PartialEq, Eq)]
|
||||
pub enum LexingError {
|
||||
#[default]
|
||||
#[error("Unknown error")]
|
||||
Unknown,
|
||||
#[error("Unexpected token at {:?}: `{}`", .0, .1)]
|
||||
Unexpected(Span, String),
|
||||
#[error("Unterminated string at {:?}", .0)]
|
||||
UnterminatedString(Span),
|
||||
#[error("Unterminated regex at {:?}", .0)]
|
||||
UnterminatedRegex(Span),
|
||||
#[error("Unexpected End of File!")]
|
||||
UnexpectedEof,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Identifier<'a> {
|
||||
pub namespace: Option<&'a str>,
|
||||
pub literal: &'a str,
|
||||
}
|
||||
|
||||
impl From<&mut Lexer<'_>> for LexingError {
|
||||
fn from(lex: &mut Lexer<'_>) -> Self {
|
||||
Self::Unexpected(lex.span(), String::from_utf8_lossy(lex.slice()).to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn skip_line(lex: &mut Lexer<'_>) -> Skip {
|
||||
lex.bump(memchr(b'\n', lex.remainder()).unwrap_or(lex.remainder().len()));
|
||||
logos::skip(lex)
|
||||
}
|
||||
|
||||
fn parse_string<'a>(lex: &mut logos::Lexer<'a, Token<'a>>) -> Result<Slice<'a>> {
|
||||
parse_content::<false, false, '"'>(lex)
|
||||
}
|
||||
|
||||
fn parse_regex_or_slash<'a>(lex: &mut logos::Lexer<'a, Token<'a>>) -> Result<Token<'a>> {
|
||||
match lex.extras {
|
||||
Context::AcceptExpression => {
|
||||
accept_operator(lex);
|
||||
parse_content::<false, true, '/'>(lex).map(Token::Regex)
|
||||
}
|
||||
Context::AcceptOperator => {
|
||||
accept_expression(lex);
|
||||
Ok(Token::Slash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_directive<'a>(lex: &mut Lexer<'a>) -> Result<Slice<'a>> {
|
||||
parse_content::<true, false, '"'>(lex)
|
||||
}
|
||||
|
||||
fn parse_content<'a, const MINIMAL: bool, const REGEX: bool, const DELIMITER: char>(
|
||||
lex: &mut Lexer<'a>,
|
||||
) -> Result<Slice<'a>> {
|
||||
let rest = lex.remainder();
|
||||
let mut start = 0;
|
||||
let mut out: Cow<'a, [u8]> = Cow::Borrowed(&[]);
|
||||
|
||||
while let Some(rel_i) = memchr3(b'\n', b'\\', DELIMITER as u8, &rest[start..]) {
|
||||
let i = start + rel_i;
|
||||
|
||||
match rest[i] {
|
||||
d if d == DELIMITER as u8 => {
|
||||
// push remaining segment
|
||||
lex.bump(i + 1);
|
||||
if start == 0 {
|
||||
out = Cow::Borrowed(&rest[..i]);
|
||||
} else {
|
||||
out.to_mut().extend_from_slice(&rest[start..i]);
|
||||
}
|
||||
return Ok(Slice(out));
|
||||
}
|
||||
b'\\' => {
|
||||
out.to_mut().extend_from_slice(&rest[start..i]);
|
||||
let consumed = parse_escape::<MINIMAL, REGEX>(&rest[i..], out.to_mut())?;
|
||||
start = i + consumed;
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
if REGEX {
|
||||
Err(LexingError::UnterminatedRegex(lex.span()))
|
||||
} else {
|
||||
Err(LexingError::UnterminatedString(lex.span()))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_escape<const MINIMAL: bool, const REGEX: bool>(
|
||||
slice: &[u8],
|
||||
out: &mut Vec<u8>,
|
||||
) -> Result<usize> {
|
||||
let mut count = 2;
|
||||
let is_oct = |x: char| ('0'..'8').contains(&x);
|
||||
let is_slice_oct = |i| slice.get(i).map(|&x| x as char).is_some_and(is_oct);
|
||||
let Some(to_escape) = slice.get(1).map(|x| *x as char) else {
|
||||
return Err(LexingError::UnexpectedEof);
|
||||
};
|
||||
|
||||
if to_escape == '\n' {
|
||||
return Ok(count);
|
||||
}
|
||||
|
||||
// On minimal, only drop backslash for '"' and '\n'
|
||||
let escaped = if MINIMAL && !matches!(to_escape, '"' | '\n') {
|
||||
out.push(b'\\');
|
||||
to_escape
|
||||
} else if MINIMAL {
|
||||
to_escape
|
||||
} else {
|
||||
match to_escape {
|
||||
c @ ('\\' | '"') if !REGEX => c,
|
||||
c @ ('[' | ']' | '{' | '}' | '(' | ')' | '*' | '+' | '^' | '$' | '.' | '?')
|
||||
if REGEX =>
|
||||
{
|
||||
out.push(b'\\');
|
||||
c
|
||||
}
|
||||
'a' => 7 as char,
|
||||
'b' => 8 as char,
|
||||
'f' => 12 as char,
|
||||
'n' => '\n',
|
||||
'r' => '\r',
|
||||
't' => '\t',
|
||||
'v' => 11 as char,
|
||||
n if is_oct(n) => {
|
||||
count += is_slice_oct(2) as usize + (is_slice_oct(2) && is_slice_oct(3)) as usize;
|
||||
slice[1..]
|
||||
.iter()
|
||||
.take(count - 1)
|
||||
.fold(0, |acc, digit| acc * 8 + digit - b'0') as char
|
||||
}
|
||||
'x' => todo!(),
|
||||
'u' => todo!(),
|
||||
// Unspecified by POSIX; we ditto GNU.
|
||||
c => c, // TODO: Output warning
|
||||
}
|
||||
};
|
||||
out.push(escaped as u8);
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
fn parse_ident<'a>(lex: &mut Lexer<'a>, index: impl SliceIndex<[u8], Output = [u8]>) -> &'a str {
|
||||
accept_operator(lex);
|
||||
// SAFETY: The regex matching ensures it is ASCII.
|
||||
unsafe { str::from_utf8_unchecked(lex.slice().get_unchecked(index)) }
|
||||
}
|
||||
|
||||
fn parse_float(lex: &mut Lexer<'_>) -> f64 {
|
||||
parse_ident(lex, ..).parse().unwrap_or(0.)
|
||||
}
|
||||
|
||||
impl<'a> Identifier<'a> {
|
||||
fn without_namespace(lex: &mut Lexer<'a>) -> Self {
|
||||
Self {
|
||||
namespace: None,
|
||||
literal: parse_ident(lex, ..),
|
||||
}
|
||||
}
|
||||
|
||||
fn with_namespace(lex: &mut Lexer<'a>) -> Self {
|
||||
// SAFETY: The regex matching ensures it is present and well-formed.
|
||||
let separator = unsafe { memchr(b':', lex.slice()).unwrap_unchecked() };
|
||||
Self {
|
||||
namespace: Some(parse_ident(lex, ..separator)),
|
||||
literal: parse_ident(lex, separator + 2..),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn accept_expression(lex: &mut Lexer<'_>) {
|
||||
lex.extras = Context::AcceptExpression;
|
||||
}
|
||||
|
||||
fn accept_operator(lex: &mut Lexer<'_>) {
|
||||
lex.extras = Context::AcceptOperator;
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub struct Slice<'a>(Cow<'a, [u8]>);
|
||||
|
||||
impl Debug for Slice<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "\"{}\"", String::from_utf8_lossy(&self.0).as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for Slice<'_> {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "parser"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
lexer.workspace = true
|
||||
either.workspace = true
|
||||
memchr.workspace = true
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
smallvec.workspace = true
|
||||
bumpalo.workspace = true
|
||||
hashbrown.workspace = true
|
||||
allocator-api2.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,204 @@
|
||||
use std::iter::Peekable;
|
||||
|
||||
use lexer::{LexingError, Logos, Token};
|
||||
|
||||
use crate::{
|
||||
ParsingError,
|
||||
ast::{Command, SpecialPattern},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Lexer<'a> {
|
||||
inner: Peekable<lexer::Lexer<'a>>,
|
||||
// source: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a> Lexer<'a> {
|
||||
pub fn new(source: &'a [u8]) -> Self {
|
||||
Self {
|
||||
inner: Token::lexer(source).peekable(),
|
||||
// source,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn peek(&mut self) -> Option<&<Self as Iterator>::Item> {
|
||||
self.inner.peek()
|
||||
}
|
||||
|
||||
pub fn peek_with(&mut self, f: impl FnOnce(&Token<'a>) -> bool) -> bool {
|
||||
self.peek().is_some_and(|r| r.as_ref().is_ok_and(f))
|
||||
}
|
||||
|
||||
pub fn peek_is(&mut self, b: &Token<'a>) -> bool {
|
||||
self.peek()
|
||||
.is_some_and(|r| r.as_ref().is_ok_and(|t| t == b))
|
||||
}
|
||||
|
||||
pub fn expect(&mut self, expected: &Token) -> super::Result<Token<'a>> {
|
||||
match self.next() {
|
||||
Some(Ok(tok)) if expected == &tok => Ok(tok),
|
||||
Some(Ok(_)) => Err(ParsingError::UnexpectedToken),
|
||||
Some(err @ Err(_)) => err.map_err(Into::into),
|
||||
None => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_with(
|
||||
&mut self,
|
||||
expected: impl FnOnce(&Token<'a>) -> bool,
|
||||
) -> super::Result<Token<'a>> {
|
||||
match self.next() {
|
||||
Some(Ok(tok)) if expected(&tok) => Ok(tok),
|
||||
Some(Ok(_)) => Err(ParsingError::UnexpectedToken),
|
||||
Some(err @ Err(_)) => err.map_err(Into::into),
|
||||
None => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_identifier(&mut self) -> super::Result<lexer::Identifier<'a>> {
|
||||
let Token::Identifier(name) = self.expect_with(|t| matches!(t, Token::Identifier(_)))?
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
Ok(name)
|
||||
}
|
||||
|
||||
pub fn consume(&mut self, token: &Token) -> bool {
|
||||
if let Some(Ok(next)) = self.peek()
|
||||
&& next == token
|
||||
{
|
||||
self.next();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn consume_with(&mut self, f: impl FnOnce(&Token<'a>) -> bool) -> bool {
|
||||
if let Some(Ok(next)) = self.peek()
|
||||
&& f(next)
|
||||
{
|
||||
self.next();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_if(
|
||||
&mut self,
|
||||
f: impl FnOnce(&<Self as Iterator>::Item) -> bool,
|
||||
) -> Option<<Self as Iterator>::Item> {
|
||||
self.inner.next_if(f)
|
||||
}
|
||||
|
||||
pub fn expect_next(&mut self) -> super::Result<Token<'a>> {
|
||||
match self.next() {
|
||||
None => Err(ParsingError::LexingError(LexingError::UnexpectedEof)),
|
||||
Some(Ok(tok)) => Ok(tok),
|
||||
Some(Err(err)) => Err(ParsingError::LexingError(err)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_peek(&mut self) -> super::Result<&Token<'a>> {
|
||||
match self.peek() {
|
||||
None => Err(ParsingError::LexingError(LexingError::UnexpectedEof)),
|
||||
Some(Ok(tok)) => Ok(tok),
|
||||
Some(Err(err)) => Err(ParsingError::LexingError(err.clone())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Lexer<'a> {
|
||||
type Item = Result<Token<'a>, LexingError>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TokenExt {
|
||||
fn is_prefix_op(&self) -> bool;
|
||||
fn is_atom(&self) -> bool;
|
||||
fn is_expr_start(&self) -> bool;
|
||||
fn is_pattern_start(&self) -> bool;
|
||||
fn maps_to_command(&self) -> Option<Command>;
|
||||
fn maps_to_special_pat(&self) -> Option<SpecialPattern>;
|
||||
fn is_stmnt_end(&self) -> bool;
|
||||
fn is_stmnt_or_block_end(&self) -> bool;
|
||||
fn is_brace(&self) -> bool;
|
||||
}
|
||||
|
||||
impl TokenExt for Token<'_> {
|
||||
fn is_prefix_op(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Token::Increment
|
||||
| Token::Decrement
|
||||
| Token::Record
|
||||
| Token::Negation
|
||||
| Token::Minus
|
||||
| Token::Plus
|
||||
| Token::Getline
|
||||
)
|
||||
}
|
||||
fn is_atom(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Token::Number(_)
|
||||
| Token::String(_)
|
||||
| Token::Regex(_)
|
||||
| Token::Identifier(_)
|
||||
| Token::NrVariable
|
||||
| Token::NfVariable
|
||||
| Token::FsVariable
|
||||
| Token::RsVariable
|
||||
| Token::OfsVariable
|
||||
| Token::OrsVariable
|
||||
| Token::FilenameVariable
|
||||
| Token::ArgcVariable
|
||||
| Token::ArgvVariable
|
||||
| Token::SubsepVariable
|
||||
| Token::FnrVariable
|
||||
| Token::OfmtVariable
|
||||
| Token::RstartVariable
|
||||
| Token::RlengthVariable
|
||||
| Token::EnvironVariable
|
||||
)
|
||||
}
|
||||
fn is_expr_start(&self) -> bool {
|
||||
self.is_atom() || self.is_prefix_op()
|
||||
}
|
||||
fn is_pattern_start(&self) -> bool {
|
||||
self.is_expr_start() || self.maps_to_special_pat().is_some()
|
||||
}
|
||||
fn maps_to_command(&self) -> Option<Command> {
|
||||
match self {
|
||||
Token::Print => Some(Command::Print),
|
||||
Token::Printf => Some(Command::Printf),
|
||||
Token::Getline => Some(Command::Getline),
|
||||
Token::Next => Some(Command::Next),
|
||||
Token::NextFile => Some(Command::NextFile),
|
||||
Token::Exit => Some(Command::Exit),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
fn maps_to_special_pat(&self) -> Option<SpecialPattern> {
|
||||
match self {
|
||||
Self::BeginPattern => Some(SpecialPattern::Begin),
|
||||
Self::EndPattern => Some(SpecialPattern::End),
|
||||
Self::BeginFilePattern => Some(SpecialPattern::BeginFile),
|
||||
Self::EndFilePattern => Some(SpecialPattern::EndFile),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
fn is_stmnt_end(&self) -> bool {
|
||||
matches!(self, Token::Newline | Token::Semicolon)
|
||||
}
|
||||
fn is_stmnt_or_block_end(&self) -> bool {
|
||||
self.is_stmnt_end() || self == &Token::ClosedBrace
|
||||
}
|
||||
fn is_brace(&self) -> bool {
|
||||
matches!(self, Token::OpenBrace | Token::ClosedBrace)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+37
@@ -0,0 +1,37 @@
|
||||
// static POSIX: bool = false;
|
||||
|
||||
mod utils;
|
||||
|
||||
use bumpalo::Bump;
|
||||
use color_eyre::Result;
|
||||
use parser::{Lexer, Parser};
|
||||
|
||||
use crate::utils::{ensure_consistent_panic, exit_err};
|
||||
|
||||
fn main() {
|
||||
if let Err(e) = ensure_consistent_panic(uu_main) {
|
||||
exit_err(e)
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
fn uu_main() -> Result<()> {
|
||||
let arg = std::env::args().nth(1).unwrap();
|
||||
|
||||
let arena = Bump::with_capacity(4000); // 4KB minus metadata-ish
|
||||
let mut lex = Lexer::new(arg.as_bytes());
|
||||
let mut parser = Parser::new(&arena);
|
||||
let ast = dbg!(parser.parse(&mut lex, true).unwrap());
|
||||
let arena2 = Bump::new();
|
||||
arena2.alloc_with(|| ast.clone());
|
||||
dbg!(arena.chunk_capacity());
|
||||
|
||||
// for token in lex {
|
||||
// let Ok(x) = token else {
|
||||
// return token.map(drop).map_err(color_eyre::Report::from);
|
||||
// };
|
||||
// println!("{x:?}");
|
||||
// }
|
||||
// exit_with(Interpreter.run())
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::panic::{UnwindSafe, catch_unwind, set_hook, take_hook};
|
||||
use std::process::exit;
|
||||
|
||||
use color_eyre::config::HookBuilder;
|
||||
use rustix::process::{EXIT_FAILURE, EXIT_SUCCESS};
|
||||
use tracing_error::ErrorLayer;
|
||||
use tracing_subscriber::{EnvFilter, prelude::*};
|
||||
|
||||
type ExitCode = i32;
|
||||
|
||||
const AWK_PANIC_CODE: ExitCode = 2;
|
||||
|
||||
fn install_abort_hook() {
|
||||
let run_hook = take_hook();
|
||||
|
||||
set_hook(Box::new(move |info| {
|
||||
run_hook(info);
|
||||
exit(AWK_PANIC_CODE);
|
||||
}));
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn install_error_hooks() {
|
||||
tracing_subscriber::registry()
|
||||
// .with(EnvFilter::from_default_env())
|
||||
.with(tracing_subscriber::fmt::layer().compact())
|
||||
.with(ErrorLayer::default())
|
||||
.init();
|
||||
|
||||
HookBuilder::default()
|
||||
.capture_span_trace_by_default(true)
|
||||
.install()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Ensures exit code 2 on panics, which is GNU's behavior. Also installs
|
||||
/// color-eyre's panic hook.
|
||||
/// https://www.gnu.org/software/gawk/manual/html_node/Exit-Status.html
|
||||
#[inline(always)] // Hide from stack trace on panics.
|
||||
pub fn ensure_consistent_panic<T>(f: impl UnwindSafe + FnOnce() -> T) -> T {
|
||||
install_error_hooks();
|
||||
if cfg!(panic = "abort") {
|
||||
// Prevents core dumps on panic. We _might_ want to carve an exception.
|
||||
install_abort_hook();
|
||||
f()
|
||||
} else {
|
||||
// If unwinding is enabled, we catch it before the Rust entry point
|
||||
// does and exit with our custom exit code. The panic msg is printed.
|
||||
match catch_unwind(f) {
|
||||
Ok(x) => x,
|
||||
Err(_) => exit(AWK_PANIC_CODE),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Exits with a custom exit code or libc's codes, as per POSIX.
|
||||
pub fn exit_with(res: Result<Option<impl Into<ExitCode>>, impl Display + Debug>) -> ! {
|
||||
let code = match res {
|
||||
Ok(Some(x)) => x.into(),
|
||||
Ok(None) => EXIT_SUCCESS,
|
||||
Err(e) => exit_err(e),
|
||||
};
|
||||
|
||||
exit(code)
|
||||
}
|
||||
|
||||
pub fn exit_err(err: impl Display + Debug) -> ! {
|
||||
eprintln!("{err:?}");
|
||||
exit(EXIT_FAILURE)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
Reference in New Issue
Block a user