feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
+1
@@ -0,0 +1 @@
|
||||
{"files":{".cargo_vcs_info.json":"1d284346b0db20937e02606b30217561a2973ec96750275eb9c06293dc8c515d","Cargo.lock":"9cce1167161bcb808f278554dd6c2baff1c4c2c40cd8fb690ea3d095f9880a45","Cargo.toml":"0fb66eee4d7376aae2d503538d5bef11856511d2651181f2234d3e0a77a08645","Cargo.toml.orig":"53550828a145a7b302cdb3b142b0c2f5877d792e69fe16fff6a473400038ace6","LICENSE-APACHE":"c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08","LICENSE-MIT":"6efb0476a1cc085077ed49357026d8c173bf33017278ef440f222fb9cbcb66e6","README.md":"5457b5b2529aeef7bca1ab74c5f735e43d90b27b305a98d5fc98c3866ddefb9a","src/debug.rs":"89559acf50045e301ebc738fd4f23a6662a5f48d2033d564d795015a8c8d628c","src/decoder/mod.rs":"b8407b1fa8157495c9501d875befaa07d2a01179e6d9db2fda9eb242a34a4059","src/decoder/scalar.rs":"63eaee42e94846da762f2a5c28f867113bdd994a5026b38017eab5d41390703b","src/decoder/string.rs":"ed677e3c93df9069a002a3236f82f496f98d690c6c7c0a9e3f45d97dde10ab34","src/decoder/ws.rs":"d48009946c8371f222daef48f4722d8af75c0f46bd706630389a1cacd277a123","src/error.rs":"458abec1fc2bc5b8c5edfcfe488783646dbcda1f6a9451c7db4bcb9521c5a113","src/lexer/mod.rs":"d70f08326eb95665604ef35022c9bdb0a35ba686bb02a3832849eee32cfadac1","src/lexer/test.rs":"dd39a1b6e1aef4b73c40e8ade87d9fee0b065c19aca036960c51837e8b31be10","src/lexer/token.rs":"30f7a7dd608d874f0bf9649cb5f89f10366ab96abda75dc82155949f0d694a51","src/lib.rs":"d26e6723317857205b3c84bfc9c3e79c4f7c7ba0bd69d48b237263a4bebb9674","src/macros.rs":"01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b","src/parser/document.rs":"9897f0b5d8b980e8d86000733d13b1058380d094f313a7493617710b9da4baff","src/parser/event.rs":"1b7461b35a31bc70b618e7d5253edda6aae512c39a164892c3d0c82d0afe5d8f","src/parser/mod.rs":"7f12b2fd441f9483c226881be8e09835a05951638ee6128ea6fddf5e58fd8921","src/source.rs":"88f17fc3876bc21971f73bf7c3155206271df3753171485e58572a770e3d48d0"},"package":"2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011"}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"git": {
|
||||
"sha1": "d66e46e2c3f91e1d6d6479c5decea0993c2c76ba"
|
||||
},
|
||||
"path_in_vcs": "crates/toml_parser"
|
||||
}
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse 0.2.7",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse 1.0.0",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||
|
||||
[[package]]
|
||||
name = "normalize-line-endings"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
|
||||
|
||||
[[package]]
|
||||
name = "similar"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
|
||||
|
||||
[[package]]
|
||||
name = "snapbox"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7a2e43da79cf7c50d938bd15e4b17e7fc450113e5ace6e6e9acfe022facbf8d"
|
||||
dependencies = [
|
||||
"anstream 1.0.0",
|
||||
"anstyle",
|
||||
"normalize-line-endings",
|
||||
"similar",
|
||||
"snapbox-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snapbox-macros"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d62a48443d404309a6006676336ff6311ef72ee78561d6a7628d952912155386"
|
||||
dependencies = [
|
||||
"anstream 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.1.0+spec-1.1.0"
|
||||
dependencies = [
|
||||
"anstream 0.6.20",
|
||||
"anstyle",
|
||||
"snapbox",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
Vendored
+195
@@ -0,0 +1,195 @@
|
||||
# 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 = "2024"
|
||||
rust-version = "1.85"
|
||||
name = "toml_parser"
|
||||
version = "1.1.0+spec-1.1.0"
|
||||
build = false
|
||||
include = [
|
||||
"build.rs",
|
||||
"src/**/*",
|
||||
"Cargo.toml",
|
||||
"Cargo.lock",
|
||||
"LICENSE*",
|
||||
"README.md",
|
||||
"examples/**/*",
|
||||
]
|
||||
autolib = false
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
description = "Yet another format-preserving TOML parser."
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"encoding",
|
||||
"toml",
|
||||
"no_std",
|
||||
]
|
||||
categories = [
|
||||
"encoding",
|
||||
"parser-implementations",
|
||||
"parsing",
|
||||
"config",
|
||||
]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/toml-rs/toml"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
[[package.metadata.release.pre-release-replacements]]
|
||||
file = "CHANGELOG.md"
|
||||
search = "Unreleased"
|
||||
replace = "{{version}}"
|
||||
min = 1
|
||||
|
||||
[[package.metadata.release.pre-release-replacements]]
|
||||
file = "CHANGELOG.md"
|
||||
search = '\.\.\.HEAD'
|
||||
replace = "...{{tag_name}}"
|
||||
exactly = 1
|
||||
|
||||
[[package.metadata.release.pre-release-replacements]]
|
||||
file = "CHANGELOG.md"
|
||||
search = "ReleaseDate"
|
||||
replace = "{{date}}"
|
||||
min = 1
|
||||
|
||||
[[package.metadata.release.pre-release-replacements]]
|
||||
file = "CHANGELOG.md"
|
||||
search = "<!-- next-header -->"
|
||||
replace = """
|
||||
<!-- next-header -->
|
||||
## [Unreleased] - ReleaseDate
|
||||
"""
|
||||
exactly = 1
|
||||
|
||||
[[package.metadata.release.pre-release-replacements]]
|
||||
file = "CHANGELOG.md"
|
||||
search = "<!-- next-url -->"
|
||||
replace = """
|
||||
<!-- next-url -->
|
||||
[Unreleased]: https://github.com/toml-rs/toml/compare/{{tag_name}}...HEAD"""
|
||||
exactly = 1
|
||||
|
||||
[features]
|
||||
alloc = []
|
||||
debug = [
|
||||
"std",
|
||||
"dep:anstream",
|
||||
"dep:anstyle",
|
||||
]
|
||||
default = ["std"]
|
||||
simd = ["winnow/simd"]
|
||||
std = ["alloc"]
|
||||
unsafe = []
|
||||
|
||||
[lib]
|
||||
name = "toml_parser"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies.anstream]
|
||||
version = "0.6.20"
|
||||
optional = true
|
||||
|
||||
[dependencies.anstyle]
|
||||
version = "1.0.11"
|
||||
optional = true
|
||||
|
||||
[dependencies.winnow]
|
||||
version = "1.0.0"
|
||||
default-features = false
|
||||
|
||||
[dev-dependencies.anstream]
|
||||
version = "0.6.20"
|
||||
features = ["test"]
|
||||
|
||||
[dev-dependencies.snapbox]
|
||||
version = "1.0.0"
|
||||
|
||||
[lints.clippy]
|
||||
bool_assert_comparison = "allow"
|
||||
branches_sharing_code = "allow"
|
||||
checked_conversions = "warn"
|
||||
collapsible_else_if = "allow"
|
||||
create_dir = "warn"
|
||||
dbg_macro = "warn"
|
||||
debug_assert_with_mut_call = "warn"
|
||||
doc_markdown = "warn"
|
||||
empty_enums = "warn"
|
||||
enum_glob_use = "warn"
|
||||
expl_impl_clone_on_copy = "warn"
|
||||
explicit_deref_methods = "warn"
|
||||
explicit_into_iter_loop = "warn"
|
||||
fallible_impl_from = "warn"
|
||||
filter_map_next = "warn"
|
||||
flat_map_option = "warn"
|
||||
float_cmp_const = "warn"
|
||||
fn_params_excessive_bools = "warn"
|
||||
from_iter_instead_of_collect = "warn"
|
||||
get_first = "allow"
|
||||
if_same_then_else = "allow"
|
||||
implicit_clone = "warn"
|
||||
imprecise_flops = "warn"
|
||||
inconsistent_struct_constructor = "warn"
|
||||
inefficient_to_string = "warn"
|
||||
infinite_loop = "warn"
|
||||
invalid_upcast_comparisons = "warn"
|
||||
large_digit_groups = "warn"
|
||||
large_stack_arrays = "warn"
|
||||
large_types_passed_by_value = "warn"
|
||||
let_and_return = "allow"
|
||||
linkedlist = "warn"
|
||||
lossy_float_literal = "warn"
|
||||
macro_use_imports = "warn"
|
||||
mem_forget = "warn"
|
||||
mutex_integer = "warn"
|
||||
needless_bool = "allow"
|
||||
needless_continue = "allow"
|
||||
needless_for_each = "warn"
|
||||
negative_feature_names = "warn"
|
||||
path_buf_push_overwrite = "warn"
|
||||
ptr_as_ptr = "warn"
|
||||
rc_mutex = "warn"
|
||||
redundant_feature_names = "warn"
|
||||
ref_option_ref = "warn"
|
||||
rest_pat_in_fully_bound_structs = "warn"
|
||||
result_large_err = "allow"
|
||||
same_functions_in_if_condition = "warn"
|
||||
self_named_module_files = "warn"
|
||||
semicolon_if_nothing_returned = "warn"
|
||||
str_to_string = "warn"
|
||||
string_add = "warn"
|
||||
string_add_assign = "warn"
|
||||
string_lit_as_bytes = "warn"
|
||||
todo = "warn"
|
||||
trait_duplication_in_bounds = "warn"
|
||||
uninlined_format_args = "warn"
|
||||
use_self = "warn"
|
||||
verbose_file_reads = "warn"
|
||||
wildcard_imports = "warn"
|
||||
zero_sized_map_values = "warn"
|
||||
|
||||
[lints.rust]
|
||||
unnameable_types = "allow"
|
||||
unreachable_pub = "warn"
|
||||
unsafe_op_in_unsafe_fn = "warn"
|
||||
unused_lifetimes = "warn"
|
||||
unused_macro_rules = "warn"
|
||||
unused_qualifications = "warn"
|
||||
|
||||
[lints.rust.rust_2018_idioms]
|
||||
level = "warn"
|
||||
priority = -1
|
||||
Vendored
+202
@@ -0,0 +1,202 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
Copyright (c) Individual 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.
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
# toml_parser
|
||||
|
||||
[](https://docs.rs/toml_parser)
|
||||

|
||||
[](https://crates.io/crates/toml_parser)
|
||||
|
||||
|
||||
Zero-copy parsing of TOML
|
||||
|
||||
## License
|
||||
|
||||
Licensed under either of
|
||||
|
||||
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or <https://www.apache.org/licenses/LICENSE-2.0>)
|
||||
* MIT license ([LICENSE-MIT](LICENSE-MIT) or <https://opensource.org/license/mit>)
|
||||
|
||||
at your option.
|
||||
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
|
||||
Vendored
+140
@@ -0,0 +1,140 @@
|
||||
use crate::ErrorSink;
|
||||
use crate::Span;
|
||||
use crate::decoder::Encoding;
|
||||
|
||||
pub(crate) struct DebugDepth(core::sync::atomic::AtomicUsize);
|
||||
|
||||
impl DebugDepth {
|
||||
pub(crate) fn enter_unchecked(&self) -> usize {
|
||||
self.0.fetch_add(1, core::sync::atomic::Ordering::SeqCst)
|
||||
}
|
||||
|
||||
pub(crate) fn exit_unchecked(&self) {
|
||||
let _ = self.0.fetch_sub(1, core::sync::atomic::Ordering::SeqCst);
|
||||
}
|
||||
|
||||
pub(crate) fn depth(&self) -> usize {
|
||||
self.0.load(core::sync::atomic::Ordering::SeqCst)
|
||||
}
|
||||
}
|
||||
|
||||
static DEBUG_DEPTH: DebugDepth = DebugDepth(core::sync::atomic::AtomicUsize::new(0));
|
||||
|
||||
fn render_event(span: impl Into<Option<Span>>, text: &str, style: anstyle::Style) {
|
||||
#![allow(unexpected_cfgs)] // HACK: fixed in newer versions
|
||||
let span = span.into();
|
||||
let depth = DEBUG_DEPTH.depth().min(20);
|
||||
anstream::eprintln!("{:depth$}{style}{text}: {span:?}{style:#}", "");
|
||||
}
|
||||
|
||||
pub(crate) struct DebugErrorSink<'s> {
|
||||
sink: &'s mut dyn ErrorSink,
|
||||
}
|
||||
|
||||
impl<'s> DebugErrorSink<'s> {
|
||||
pub(crate) fn new(sink: &'s mut dyn ErrorSink) -> Self {
|
||||
Self { sink }
|
||||
}
|
||||
}
|
||||
|
||||
impl ErrorSink for DebugErrorSink<'_> {
|
||||
fn report_error(&mut self, error: crate::ParseError) {
|
||||
render_event(
|
||||
error.unexpected(),
|
||||
&format!("{error:?}"),
|
||||
anstyle::AnsiColor::Red.on_default(),
|
||||
);
|
||||
self.sink.report_error(error);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DebugEventReceiver<'r> {
|
||||
receiver: &'r mut dyn crate::parser::EventReceiver,
|
||||
}
|
||||
|
||||
impl<'r> DebugEventReceiver<'r> {
|
||||
pub(crate) fn new(receiver: &'r mut dyn crate::parser::EventReceiver) -> Self {
|
||||
Self { receiver }
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::parser::EventReceiver for DebugEventReceiver<'_> {
|
||||
fn std_table_open(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.std_table_open(span, error);
|
||||
render_event(span, "[", anstyle::Style::new() | anstyle::Effects::DIMMED);
|
||||
DEBUG_DEPTH.enter_unchecked();
|
||||
}
|
||||
fn std_table_close(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.std_table_close(span, error);
|
||||
DEBUG_DEPTH.exit_unchecked();
|
||||
render_event(span, "]", anstyle::Style::new() | anstyle::Effects::DIMMED);
|
||||
}
|
||||
fn array_table_open(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.array_table_open(span, error);
|
||||
render_event(span, "[[", anstyle::Style::new() | anstyle::Effects::DIMMED);
|
||||
DEBUG_DEPTH.enter_unchecked();
|
||||
}
|
||||
fn array_table_close(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.array_table_close(span, error);
|
||||
DEBUG_DEPTH.exit_unchecked();
|
||||
render_event(span, "]]", anstyle::Style::new() | anstyle::Effects::DIMMED);
|
||||
}
|
||||
fn inline_table_open(&mut self, span: Span, error: &mut dyn ErrorSink) -> bool {
|
||||
let allowed = self.receiver.inline_table_open(span, error);
|
||||
render_event(span, "{", anstyle::Style::new() | anstyle::Effects::DIMMED);
|
||||
DEBUG_DEPTH.enter_unchecked();
|
||||
allowed
|
||||
}
|
||||
fn inline_table_close(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.inline_table_close(span, error);
|
||||
DEBUG_DEPTH.exit_unchecked();
|
||||
render_event(span, "}", anstyle::Style::new() | anstyle::Effects::DIMMED);
|
||||
}
|
||||
fn array_open(&mut self, span: Span, error: &mut dyn ErrorSink) -> bool {
|
||||
let allowed = self.receiver.array_open(span, error);
|
||||
render_event(span, "[", anstyle::Style::new() | anstyle::Effects::DIMMED);
|
||||
DEBUG_DEPTH.enter_unchecked();
|
||||
allowed
|
||||
}
|
||||
fn array_close(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.array_close(span, error);
|
||||
DEBUG_DEPTH.exit_unchecked();
|
||||
render_event(span, "]", anstyle::Style::new() | anstyle::Effects::DIMMED);
|
||||
}
|
||||
fn simple_key(&mut self, span: Span, encoding: Option<Encoding>, error: &mut dyn ErrorSink) {
|
||||
self.receiver.simple_key(span, encoding, error);
|
||||
render_event(span, "<key>", anstyle::AnsiColor::Magenta.on_default());
|
||||
}
|
||||
fn key_sep(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.key_sep(span, error);
|
||||
render_event(span, ".", anstyle::Style::new() | anstyle::Effects::DIMMED);
|
||||
}
|
||||
fn key_val_sep(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.key_val_sep(span, error);
|
||||
render_event(span, "=", anstyle::Style::new() | anstyle::Effects::DIMMED);
|
||||
}
|
||||
fn scalar(&mut self, span: Span, encoding: Option<Encoding>, error: &mut dyn ErrorSink) {
|
||||
self.receiver.scalar(span, encoding, error);
|
||||
render_event(span, "<scalar>", anstyle::AnsiColor::Green.on_default());
|
||||
}
|
||||
fn value_sep(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.value_sep(span, error);
|
||||
render_event(span, ",", anstyle::Style::new() | anstyle::Effects::DIMMED);
|
||||
}
|
||||
fn whitespace(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.whitespace(span, error);
|
||||
render_event(span, "<whitespace>", anstyle::AnsiColor::Cyan.on_default());
|
||||
}
|
||||
fn comment(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.comment(span, error);
|
||||
render_event(span, "<comment>", anstyle::AnsiColor::Cyan.on_default());
|
||||
}
|
||||
fn newline(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.newline(span, error);
|
||||
render_event(span, "<newline>", anstyle::AnsiColor::Cyan.on_default());
|
||||
}
|
||||
fn error(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.error(span, error);
|
||||
render_event(span, "<error>", anstyle::AnsiColor::Red.on_default());
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
//! Decode [raw][crate::Raw] TOML values into Rust native types
|
||||
//!
|
||||
//! See
|
||||
//! - [`Raw::decode_key`][crate::Raw::decode_key]
|
||||
//! - [`Raw::decode_scalar`][crate::Raw::decode_scalar]
|
||||
//! - [`Raw::decode_whitespace`][crate::Raw::decode_whitespace]
|
||||
//! - [`Raw::decode_comment`][crate::Raw::decode_comment]
|
||||
//! - [`Raw::decode_newline`][crate::Raw::decode_newline]
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::borrow::Cow;
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::string::String;
|
||||
|
||||
pub(crate) mod scalar;
|
||||
pub(crate) mod string;
|
||||
pub(crate) mod ws;
|
||||
|
||||
pub use scalar::IntegerRadix;
|
||||
pub use scalar::ScalarKind;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum Encoding {
|
||||
LiteralString = crate::lexer::APOSTROPHE,
|
||||
BasicString = crate::lexer::QUOTATION_MARK,
|
||||
MlLiteralString = 1,
|
||||
MlBasicString,
|
||||
}
|
||||
|
||||
impl Encoding {
|
||||
pub const fn description(&self) -> &'static str {
|
||||
match self {
|
||||
Self::LiteralString => crate::lexer::TokenKind::LiteralString.description(),
|
||||
Self::BasicString => crate::lexer::TokenKind::BasicString.description(),
|
||||
Self::MlLiteralString => crate::lexer::TokenKind::MlLiteralString.description(),
|
||||
Self::MlBasicString => crate::lexer::TokenKind::MlBasicString.description(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait StringBuilder<'s> {
|
||||
fn clear(&mut self);
|
||||
#[must_use]
|
||||
fn push_str(&mut self, append: &'s str) -> bool;
|
||||
#[must_use]
|
||||
fn push_char(&mut self, append: char) -> bool;
|
||||
}
|
||||
|
||||
impl<'s> StringBuilder<'s> for () {
|
||||
fn clear(&mut self) {}
|
||||
fn push_str(&mut self, _append: &'s str) -> bool {
|
||||
true
|
||||
}
|
||||
fn push_char(&mut self, _append: char) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StringBuilder<'s> for &'s str {
|
||||
fn clear(&mut self) {
|
||||
*self = &self[0..0];
|
||||
}
|
||||
fn push_str(&mut self, append: &'s str) -> bool {
|
||||
if self.is_empty() {
|
||||
*self = append;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
fn push_char(&mut self, _append: char) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<'s> StringBuilder<'s> for Cow<'s, str> {
|
||||
fn clear(&mut self) {
|
||||
match self {
|
||||
Cow::Borrowed(s) => {
|
||||
s.clear();
|
||||
}
|
||||
Cow::Owned(s) => s.clear(),
|
||||
}
|
||||
}
|
||||
fn push_str(&mut self, append: &'s str) -> bool {
|
||||
match self {
|
||||
Cow::Borrowed(s) => {
|
||||
if !s.push_str(append) {
|
||||
self.to_mut().push_str(append);
|
||||
}
|
||||
}
|
||||
Cow::Owned(s) => s.push_str(append),
|
||||
}
|
||||
true
|
||||
}
|
||||
fn push_char(&mut self, append: char) -> bool {
|
||||
self.to_mut().push(append);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<'s> StringBuilder<'s> for String {
|
||||
fn clear(&mut self) {
|
||||
self.clear();
|
||||
}
|
||||
fn push_str(&mut self, append: &'s str) -> bool {
|
||||
self.push_str(append);
|
||||
true
|
||||
}
|
||||
fn push_char(&mut self, append: char) -> bool {
|
||||
self.push(append);
|
||||
true
|
||||
}
|
||||
}
|
||||
+772
@@ -0,0 +1,772 @@
|
||||
use winnow::stream::ContainsToken as _;
|
||||
use winnow::stream::FindSlice as _;
|
||||
use winnow::stream::Offset as _;
|
||||
use winnow::stream::Stream as _;
|
||||
|
||||
use crate::ErrorSink;
|
||||
use crate::Expected;
|
||||
use crate::ParseError;
|
||||
use crate::Raw;
|
||||
use crate::Span;
|
||||
use crate::decoder::StringBuilder;
|
||||
|
||||
const ALLOCATION_ERROR: &str = "could not allocate for string";
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub enum ScalarKind {
|
||||
String,
|
||||
Boolean(bool),
|
||||
DateTime,
|
||||
Float,
|
||||
Integer(IntegerRadix),
|
||||
}
|
||||
|
||||
impl ScalarKind {
|
||||
pub fn description(&self) -> &'static str {
|
||||
match self {
|
||||
Self::String => "string",
|
||||
Self::Boolean(_) => "boolean",
|
||||
Self::DateTime => "date-time",
|
||||
Self::Float => "float",
|
||||
Self::Integer(radix) => radix.description(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn invalid_description(&self) -> &'static str {
|
||||
match self {
|
||||
Self::String => "invalid string",
|
||||
Self::Boolean(_) => "invalid boolean",
|
||||
Self::DateTime => "invalid date-time",
|
||||
Self::Float => "invalid float",
|
||||
Self::Integer(radix) => radix.invalid_description(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub enum IntegerRadix {
|
||||
#[default]
|
||||
Dec,
|
||||
Hex,
|
||||
Oct,
|
||||
Bin,
|
||||
}
|
||||
|
||||
impl IntegerRadix {
|
||||
pub fn description(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Dec => "integer",
|
||||
Self::Hex => "hexadecimal",
|
||||
Self::Oct => "octal",
|
||||
Self::Bin => "binary",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn value(&self) -> u32 {
|
||||
match self {
|
||||
Self::Dec => 10,
|
||||
Self::Hex => 16,
|
||||
Self::Oct => 8,
|
||||
Self::Bin => 2,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn invalid_description(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Dec => "invalid integer number",
|
||||
Self::Hex => "invalid hexadecimal number",
|
||||
Self::Oct => "invalid octal number",
|
||||
Self::Bin => "invalid binary number",
|
||||
}
|
||||
}
|
||||
|
||||
fn validator(&self) -> fn(char) -> bool {
|
||||
match self {
|
||||
Self::Dec => |c| c.is_ascii_digit(),
|
||||
Self::Hex => |c| c.is_ascii_hexdigit(),
|
||||
Self::Oct => |c| matches!(c, '0'..='7'),
|
||||
Self::Bin => |c| matches!(c, '0'..='1'),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn decode_unquoted_scalar<'i>(
|
||||
raw: Raw<'i>,
|
||||
output: &mut dyn StringBuilder<'i>,
|
||||
error: &mut dyn ErrorSink,
|
||||
) -> ScalarKind {
|
||||
let s = raw.as_str();
|
||||
let Some(first) = s.as_bytes().first() else {
|
||||
return decode_invalid(raw, output, error);
|
||||
};
|
||||
if !first.is_ascii_digit() && s.contains(" ") {
|
||||
// Only datetimes can have a space
|
||||
return decode_invalid(raw, output, error);
|
||||
}
|
||||
match first {
|
||||
// number starts
|
||||
b'+' | b'-' => {
|
||||
let value = &raw.as_str()[1..];
|
||||
decode_sign_prefix(raw, value, output, error)
|
||||
}
|
||||
// Report as if they were numbers because its most likely a typo
|
||||
b'_' => decode_datetime_or_float_or_integer(raw.as_str(), raw, output, error),
|
||||
// Date/number starts
|
||||
b'0' => decode_zero_prefix(raw.as_str(), false, raw, output, error),
|
||||
b'1'..=b'9' => decode_datetime_or_float_or_integer(raw.as_str(), raw, output, error),
|
||||
// Report as if they were numbers because its most likely a typo
|
||||
b'.' => {
|
||||
let kind = ScalarKind::Float;
|
||||
let stream = raw.as_str();
|
||||
if ensure_float(stream, raw, error) {
|
||||
decode_float_or_integer(stream, raw, kind, output, error)
|
||||
} else {
|
||||
kind
|
||||
}
|
||||
}
|
||||
b't' | b'T' => {
|
||||
const SYMBOL: &str = "true";
|
||||
let kind = ScalarKind::Boolean(true);
|
||||
let expected = &[Expected::Literal(SYMBOL)];
|
||||
decode_symbol(raw, SYMBOL, kind, expected, output, error)
|
||||
}
|
||||
b'f' | b'F' => {
|
||||
const SYMBOL: &str = "false";
|
||||
let kind = ScalarKind::Boolean(false);
|
||||
let expected = &[Expected::Literal(SYMBOL)];
|
||||
decode_symbol(raw, SYMBOL, kind, expected, output, error)
|
||||
}
|
||||
b'i' | b'I' => {
|
||||
const SYMBOL: &str = "inf";
|
||||
let kind = ScalarKind::Float;
|
||||
let expected = &[Expected::Literal(SYMBOL)];
|
||||
decode_symbol(raw, SYMBOL, kind, expected, output, error)
|
||||
}
|
||||
b'n' | b'N' => {
|
||||
const SYMBOL: &str = "nan";
|
||||
let kind = ScalarKind::Float;
|
||||
let expected = &[Expected::Literal(SYMBOL)];
|
||||
decode_symbol(raw, SYMBOL, kind, expected, output, error)
|
||||
}
|
||||
_ => decode_invalid(raw, output, error),
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_sign_prefix<'i>(
|
||||
raw: Raw<'i>,
|
||||
value: &'i str,
|
||||
output: &mut dyn StringBuilder<'i>,
|
||||
error: &mut dyn ErrorSink,
|
||||
) -> ScalarKind {
|
||||
let Some(first) = value.as_bytes().first() else {
|
||||
return decode_invalid(raw, output, error);
|
||||
};
|
||||
match first {
|
||||
// number starts
|
||||
b'+' | b'-' => {
|
||||
let start = value.offset_from(&raw.as_str());
|
||||
let end = start + 1;
|
||||
error.report_error(
|
||||
ParseError::new("redundant numeric sign")
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_expected(&[])
|
||||
.with_unexpected(Span::new_unchecked(start, end)),
|
||||
);
|
||||
|
||||
let value = &value[1..];
|
||||
decode_sign_prefix(raw, value, output, error)
|
||||
}
|
||||
// Report as if they were numbers because its most likely a typo
|
||||
b'_' => decode_datetime_or_float_or_integer(value, raw, output, error),
|
||||
// Date/number starts
|
||||
b'0' => decode_zero_prefix(value, true, raw, output, error),
|
||||
b'1'..=b'9' => decode_datetime_or_float_or_integer(value, raw, output, error),
|
||||
// Report as if they were numbers because its most likely a typo
|
||||
b'.' => {
|
||||
let kind = ScalarKind::Float;
|
||||
let stream = raw.as_str();
|
||||
if ensure_float(stream, raw, error) {
|
||||
decode_float_or_integer(stream, raw, kind, output, error)
|
||||
} else {
|
||||
kind
|
||||
}
|
||||
}
|
||||
b'i' | b'I' => {
|
||||
const SYMBOL: &str = "inf";
|
||||
let kind = ScalarKind::Float;
|
||||
if value != SYMBOL {
|
||||
let expected = &[Expected::Literal(SYMBOL)];
|
||||
let start = value.offset_from(&raw.as_str());
|
||||
let end = start + value.len();
|
||||
error.report_error(
|
||||
ParseError::new(kind.invalid_description())
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_expected(expected)
|
||||
.with_unexpected(Span::new_unchecked(start, end)),
|
||||
);
|
||||
decode_as(raw, SYMBOL, kind, output, error)
|
||||
} else {
|
||||
decode_as_is(raw, kind, output, error)
|
||||
}
|
||||
}
|
||||
b'n' | b'N' => {
|
||||
const SYMBOL: &str = "nan";
|
||||
let kind = ScalarKind::Float;
|
||||
if value != SYMBOL {
|
||||
let expected = &[Expected::Literal(SYMBOL)];
|
||||
let start = value.offset_from(&raw.as_str());
|
||||
let end = start + value.len();
|
||||
error.report_error(
|
||||
ParseError::new(kind.invalid_description())
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_expected(expected)
|
||||
.with_unexpected(Span::new_unchecked(start, end)),
|
||||
);
|
||||
decode_as(raw, SYMBOL, kind, output, error)
|
||||
} else {
|
||||
decode_as_is(raw, kind, output, error)
|
||||
}
|
||||
}
|
||||
_ => decode_invalid(raw, output, error),
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_zero_prefix<'i>(
|
||||
value: &'i str,
|
||||
signed: bool,
|
||||
raw: Raw<'i>,
|
||||
output: &mut dyn StringBuilder<'i>,
|
||||
error: &mut dyn ErrorSink,
|
||||
) -> ScalarKind {
|
||||
debug_assert_eq!(value.as_bytes()[0], b'0');
|
||||
if value.len() == 1 {
|
||||
let kind = ScalarKind::Integer(IntegerRadix::Dec);
|
||||
// No extra validation needed
|
||||
decode_float_or_integer(raw.as_str(), raw, kind, output, error)
|
||||
} else {
|
||||
let radix = value.as_bytes()[1];
|
||||
match radix {
|
||||
b'x' | b'X' => {
|
||||
if value.contains(" ") {
|
||||
// Only datetimes can have a space
|
||||
return decode_invalid(raw, output, error);
|
||||
}
|
||||
if signed {
|
||||
error.report_error(
|
||||
ParseError::new("integers with a radix cannot be signed")
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_expected(&[])
|
||||
.with_unexpected(Span::new_unchecked(0, 1)),
|
||||
);
|
||||
}
|
||||
if radix == b'X' {
|
||||
let start = value.offset_from(&raw.as_str());
|
||||
let end = start + 2;
|
||||
error.report_error(
|
||||
ParseError::new("radix must be lowercase")
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_expected(&[Expected::Literal("0x")])
|
||||
.with_unexpected(Span::new_unchecked(start, end)),
|
||||
);
|
||||
}
|
||||
let radix = IntegerRadix::Hex;
|
||||
let kind = ScalarKind::Integer(radix);
|
||||
let stream = &value[2..];
|
||||
if ensure_radixed_value(stream, raw, radix, error) {
|
||||
decode_float_or_integer(stream, raw, kind, output, error)
|
||||
} else {
|
||||
kind
|
||||
}
|
||||
}
|
||||
b'o' | b'O' => {
|
||||
if value.contains(" ") {
|
||||
// Only datetimes can have a space
|
||||
return decode_invalid(raw, output, error);
|
||||
}
|
||||
if signed {
|
||||
error.report_error(
|
||||
ParseError::new("integers with a radix cannot be signed")
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_expected(&[])
|
||||
.with_unexpected(Span::new_unchecked(0, 1)),
|
||||
);
|
||||
}
|
||||
if radix == b'O' {
|
||||
let start = value.offset_from(&raw.as_str());
|
||||
let end = start + 2;
|
||||
error.report_error(
|
||||
ParseError::new("radix must be lowercase")
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_expected(&[Expected::Literal("0o")])
|
||||
.with_unexpected(Span::new_unchecked(start, end)),
|
||||
);
|
||||
}
|
||||
let radix = IntegerRadix::Oct;
|
||||
let kind = ScalarKind::Integer(radix);
|
||||
let stream = &value[2..];
|
||||
if ensure_radixed_value(stream, raw, radix, error) {
|
||||
decode_float_or_integer(stream, raw, kind, output, error)
|
||||
} else {
|
||||
kind
|
||||
}
|
||||
}
|
||||
b'b' | b'B' => {
|
||||
if value.contains(" ") {
|
||||
// Only datetimes can have a space
|
||||
return decode_invalid(raw, output, error);
|
||||
}
|
||||
if signed {
|
||||
error.report_error(
|
||||
ParseError::new("integers with a radix cannot be signed")
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_expected(&[])
|
||||
.with_unexpected(Span::new_unchecked(0, 1)),
|
||||
);
|
||||
}
|
||||
if radix == b'B' {
|
||||
let start = value.offset_from(&raw.as_str());
|
||||
let end = start + 2;
|
||||
error.report_error(
|
||||
ParseError::new("radix must be lowercase")
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_expected(&[Expected::Literal("0b")])
|
||||
.with_unexpected(Span::new_unchecked(start, end)),
|
||||
);
|
||||
}
|
||||
let radix = IntegerRadix::Bin;
|
||||
let kind = ScalarKind::Integer(radix);
|
||||
let stream = &value[2..];
|
||||
if ensure_radixed_value(stream, raw, radix, error) {
|
||||
decode_float_or_integer(stream, raw, kind, output, error)
|
||||
} else {
|
||||
kind
|
||||
}
|
||||
}
|
||||
b'd' | b'D' => {
|
||||
if value.contains(" ") {
|
||||
// Only datetimes can have a space
|
||||
return decode_invalid(raw, output, error);
|
||||
}
|
||||
if signed {
|
||||
error.report_error(
|
||||
ParseError::new("integers with a radix cannot be signed")
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_expected(&[])
|
||||
.with_unexpected(Span::new_unchecked(0, 1)),
|
||||
);
|
||||
}
|
||||
let radix = IntegerRadix::Dec;
|
||||
let kind = ScalarKind::Integer(radix);
|
||||
let stream = &value[2..];
|
||||
error.report_error(
|
||||
ParseError::new("redundant integer number prefix")
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_expected(&[])
|
||||
.with_unexpected(Span::new_unchecked(0, 2)),
|
||||
);
|
||||
if ensure_radixed_value(stream, raw, radix, error) {
|
||||
decode_float_or_integer(stream, raw, kind, output, error)
|
||||
} else {
|
||||
kind
|
||||
}
|
||||
}
|
||||
_ => decode_datetime_or_float_or_integer(value, raw, output, error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_datetime_or_float_or_integer<'i>(
|
||||
value: &'i str,
|
||||
raw: Raw<'i>,
|
||||
output: &mut dyn StringBuilder<'i>,
|
||||
error: &mut dyn ErrorSink,
|
||||
) -> ScalarKind {
|
||||
let Some(digit_end) = value
|
||||
.as_bytes()
|
||||
.offset_for(|b| !(b'0'..=b'9').contains_token(b))
|
||||
else {
|
||||
let kind = ScalarKind::Integer(IntegerRadix::Dec);
|
||||
let stream = raw.as_str();
|
||||
if ensure_no_leading_zero(value, raw, error) {
|
||||
return decode_float_or_integer(stream, raw, kind, output, error);
|
||||
} else {
|
||||
return kind;
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(feature = "unsafe")] // SAFETY: ascii digits ensures UTF-8 boundary
|
||||
let rest = unsafe { &value.get_unchecked(digit_end..) };
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
let rest = &value[digit_end..];
|
||||
|
||||
if rest.starts_with("-") || rest.starts_with(":") {
|
||||
decode_as_is(raw, ScalarKind::DateTime, output, error)
|
||||
} else if rest.contains(" ") {
|
||||
decode_invalid(raw, output, error)
|
||||
} else if is_float(rest) {
|
||||
let kind = ScalarKind::Float;
|
||||
let stream = raw.as_str();
|
||||
if ensure_float(value, raw, error) {
|
||||
decode_float_or_integer(stream, raw, kind, output, error)
|
||||
} else {
|
||||
kind
|
||||
}
|
||||
} else if rest.starts_with("_") {
|
||||
let kind = ScalarKind::Integer(IntegerRadix::Dec);
|
||||
let stream = raw.as_str();
|
||||
if ensure_no_leading_zero(value, raw, error) {
|
||||
decode_float_or_integer(stream, raw, kind, output, error)
|
||||
} else {
|
||||
kind
|
||||
}
|
||||
} else {
|
||||
decode_invalid(raw, output, error)
|
||||
}
|
||||
}
|
||||
|
||||
/// ```abnf
|
||||
/// ;; Float
|
||||
///
|
||||
/// float = float-int-part ( exp / frac [ exp ] )
|
||||
///
|
||||
/// float-int-part = dec-int
|
||||
/// frac = decimal-point zero-prefixable-int
|
||||
/// decimal-point = %x2E ; .
|
||||
/// zero-prefixable-int = DIGIT *( DIGIT / underscore DIGIT )
|
||||
///
|
||||
/// exp = "e" float-exp-part
|
||||
/// float-exp-part = [ minus / plus ] zero-prefixable-int
|
||||
/// ```
|
||||
#[must_use]
|
||||
fn ensure_float<'i>(mut value: &'i str, raw: Raw<'i>, error: &mut dyn ErrorSink) -> bool {
|
||||
let mut is_valid = true;
|
||||
|
||||
is_valid &= ensure_dec_uint(&mut value, raw, false, "invalid mantissa", error);
|
||||
|
||||
if value.starts_with(".") {
|
||||
let _ = value.next_token();
|
||||
is_valid &= ensure_dec_uint(&mut value, raw, true, "invalid fraction", error);
|
||||
}
|
||||
|
||||
if value.starts_with(['e', 'E']) {
|
||||
let _ = value.next_token();
|
||||
if value.starts_with(['+', '-']) {
|
||||
let _ = value.next_token();
|
||||
}
|
||||
is_valid &= ensure_dec_uint(&mut value, raw, true, "invalid exponent", error);
|
||||
}
|
||||
|
||||
if !value.is_empty() {
|
||||
let start = value.offset_from(&raw.as_str());
|
||||
let end = raw.len();
|
||||
error.report_error(
|
||||
ParseError::new(ScalarKind::Float.invalid_description())
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_expected(&[])
|
||||
.with_unexpected(Span::new_unchecked(start, end)),
|
||||
);
|
||||
is_valid = false;
|
||||
}
|
||||
|
||||
is_valid
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn ensure_dec_uint<'i>(
|
||||
value: &mut &'i str,
|
||||
raw: Raw<'i>,
|
||||
zero_prefix: bool,
|
||||
invalid_description: &'static str,
|
||||
error: &mut dyn ErrorSink,
|
||||
) -> bool {
|
||||
let mut is_valid = true;
|
||||
|
||||
let start = *value;
|
||||
let mut digit_count = 0;
|
||||
while let Some(current) = value.chars().next() {
|
||||
if current.is_ascii_digit() {
|
||||
digit_count += 1;
|
||||
} else if current == '_' {
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
let _ = value.next_token();
|
||||
}
|
||||
|
||||
match digit_count {
|
||||
0 => {
|
||||
let start = start.offset_from(&raw.as_str());
|
||||
let end = start;
|
||||
error.report_error(
|
||||
ParseError::new(invalid_description)
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_expected(&[Expected::Description("digits")])
|
||||
.with_unexpected(Span::new_unchecked(start, end)),
|
||||
);
|
||||
is_valid = false;
|
||||
}
|
||||
1 => {}
|
||||
_ if start.starts_with("0") && !zero_prefix => {
|
||||
let start = start.offset_from(&raw.as_str());
|
||||
let end = start + 1;
|
||||
error.report_error(
|
||||
ParseError::new("unexpected leading zero")
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_expected(&[])
|
||||
.with_unexpected(Span::new_unchecked(start, end)),
|
||||
);
|
||||
is_valid = false;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
is_valid
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn ensure_no_leading_zero<'i>(value: &'i str, raw: Raw<'i>, error: &mut dyn ErrorSink) -> bool {
|
||||
let mut is_valid = true;
|
||||
|
||||
if value.starts_with("0") {
|
||||
let start = value.offset_from(&raw.as_str());
|
||||
let end = start + 1;
|
||||
error.report_error(
|
||||
ParseError::new("unexpected leading zero")
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_expected(&[])
|
||||
.with_unexpected(Span::new_unchecked(start, end)),
|
||||
);
|
||||
is_valid = false;
|
||||
}
|
||||
|
||||
is_valid
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn ensure_radixed_value(
|
||||
value: &str,
|
||||
raw: Raw<'_>,
|
||||
radix: IntegerRadix,
|
||||
error: &mut dyn ErrorSink,
|
||||
) -> bool {
|
||||
let mut is_valid = true;
|
||||
|
||||
let invalid = ['+', '-'];
|
||||
let value = if let Some(value) = value.strip_prefix(invalid) {
|
||||
let pos = raw.as_str().find(invalid).unwrap();
|
||||
error.report_error(
|
||||
ParseError::new("unexpected sign")
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_expected(&[])
|
||||
.with_unexpected(Span::new_unchecked(pos, pos + 1)),
|
||||
);
|
||||
is_valid = false;
|
||||
value
|
||||
} else {
|
||||
value
|
||||
};
|
||||
|
||||
let valid = radix.validator();
|
||||
for (index, c) in value.char_indices() {
|
||||
if !valid(c) && c != '_' {
|
||||
let pos = value.offset_from(&raw.as_str()) + index;
|
||||
error.report_error(
|
||||
ParseError::new(radix.invalid_description())
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_unexpected(Span::new_unchecked(pos, pos)),
|
||||
);
|
||||
is_valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
is_valid
|
||||
}
|
||||
|
||||
fn decode_float_or_integer<'i>(
|
||||
mut stream: &'i str,
|
||||
raw: Raw<'i>,
|
||||
kind: ScalarKind,
|
||||
output: &mut dyn StringBuilder<'i>,
|
||||
error: &mut dyn ErrorSink,
|
||||
) -> ScalarKind {
|
||||
output.clear();
|
||||
|
||||
let underscore = "_";
|
||||
|
||||
let stream_start = stream.offset_from(&raw.as_str());
|
||||
while !stream.is_empty() {
|
||||
let sep_pos = stream.find_slice(underscore);
|
||||
let sep_start = sep_pos
|
||||
.clone()
|
||||
.map(|r| r.start)
|
||||
.unwrap_or_else(|| stream.len());
|
||||
|
||||
let part_start = stream.offset_from(&raw.as_str());
|
||||
let part_end = part_start + sep_start;
|
||||
let part = stream.next_slice(sep_start);
|
||||
|
||||
if sep_pos.is_some() {
|
||||
let _ = stream.next_slice(underscore.len());
|
||||
|
||||
let mut is_invalid_sep = false;
|
||||
if let Some(last_pos) = sep_start.checked_sub(1) {
|
||||
let last_byte = raw.as_bytes()[part_start + last_pos];
|
||||
if !is_any_digit(last_byte, kind) {
|
||||
is_invalid_sep = true;
|
||||
}
|
||||
} else if part_start == stream_start {
|
||||
is_invalid_sep = true;
|
||||
}
|
||||
|
||||
if let Some(next_byte) = stream.as_bytes().first() {
|
||||
if !is_any_digit(*next_byte, kind) {
|
||||
is_invalid_sep = true;
|
||||
}
|
||||
} else if stream.is_empty() {
|
||||
is_invalid_sep = true;
|
||||
}
|
||||
|
||||
if is_invalid_sep {
|
||||
let start = part_end;
|
||||
let end = start + underscore.len();
|
||||
error.report_error(
|
||||
ParseError::new("`_` may only go between digits")
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_expected(&[])
|
||||
.with_unexpected(Span::new_unchecked(end - underscore.len(), end)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if !part.is_empty() && !output.push_str(part) {
|
||||
error.report_error(
|
||||
ParseError::new(ALLOCATION_ERROR)
|
||||
.with_unexpected(Span::new_unchecked(part_start, part_end)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
kind
|
||||
}
|
||||
|
||||
fn is_any_digit(b: u8, kind: ScalarKind) -> bool {
|
||||
if kind == ScalarKind::Float {
|
||||
is_dec_integer_digit(b)
|
||||
} else {
|
||||
is_any_integer_digit(b)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_any_integer_digit(b: u8) -> bool {
|
||||
(b'0'..=b'9', b'a'..=b'f', b'A'..=b'F').contains_token(b)
|
||||
}
|
||||
|
||||
fn is_dec_integer_digit(b: u8) -> bool {
|
||||
(b'0'..=b'9').contains_token(b)
|
||||
}
|
||||
|
||||
fn is_float(raw: &str) -> bool {
|
||||
raw.as_bytes().find_slice((b'.', b'e', b'E')).is_some()
|
||||
}
|
||||
|
||||
fn decode_as_is<'i>(
|
||||
raw: Raw<'i>,
|
||||
kind: ScalarKind,
|
||||
output: &mut dyn StringBuilder<'i>,
|
||||
error: &mut dyn ErrorSink,
|
||||
) -> ScalarKind {
|
||||
let kind = decode_as(raw, raw.as_str(), kind, output, error);
|
||||
kind
|
||||
}
|
||||
|
||||
fn decode_as<'i>(
|
||||
raw: Raw<'i>,
|
||||
symbol: &'i str,
|
||||
kind: ScalarKind,
|
||||
output: &mut dyn StringBuilder<'i>,
|
||||
error: &mut dyn ErrorSink,
|
||||
) -> ScalarKind {
|
||||
output.clear();
|
||||
if !output.push_str(symbol) {
|
||||
error.report_error(
|
||||
ParseError::new(ALLOCATION_ERROR).with_unexpected(Span::new_unchecked(0, raw.len())),
|
||||
);
|
||||
}
|
||||
kind
|
||||
}
|
||||
|
||||
fn decode_symbol<'i>(
|
||||
raw: Raw<'i>,
|
||||
symbol: &'static str,
|
||||
kind: ScalarKind,
|
||||
expected: &'static [Expected],
|
||||
output: &mut dyn StringBuilder<'i>,
|
||||
error: &mut dyn ErrorSink,
|
||||
) -> ScalarKind {
|
||||
if raw.as_str() != symbol {
|
||||
if raw.as_str().contains(" ") {
|
||||
return decode_invalid(raw, output, error);
|
||||
} else {
|
||||
error.report_error(
|
||||
ParseError::new(kind.invalid_description())
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_expected(expected)
|
||||
.with_unexpected(Span::new_unchecked(0, raw.len())),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
decode_as(raw, symbol, kind, output, error)
|
||||
}
|
||||
|
||||
fn decode_invalid<'i>(
|
||||
raw: Raw<'i>,
|
||||
output: &mut dyn StringBuilder<'i>,
|
||||
error: &mut dyn ErrorSink,
|
||||
) -> ScalarKind {
|
||||
if raw.as_str().ends_with("'''") {
|
||||
error.report_error(
|
||||
ParseError::new("missing opening quote")
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_expected(&[Expected::Literal(r#"'''"#)])
|
||||
.with_unexpected(Span::new_unchecked(0, 0)),
|
||||
);
|
||||
} else if raw.as_str().ends_with(r#"""""#) {
|
||||
error.report_error(
|
||||
ParseError::new("missing opening quote")
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_expected(&[Expected::Description("multi-line basic string")])
|
||||
.with_expected(&[Expected::Literal(r#"""""#)])
|
||||
.with_unexpected(Span::new_unchecked(0, 0)),
|
||||
);
|
||||
} else if raw.as_str().ends_with("'") {
|
||||
error.report_error(
|
||||
ParseError::new("missing opening quote")
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_expected(&[Expected::Literal(r#"'"#)])
|
||||
.with_unexpected(Span::new_unchecked(0, 0)),
|
||||
);
|
||||
} else if raw.as_str().ends_with(r#"""#) {
|
||||
error.report_error(
|
||||
ParseError::new("missing opening quote")
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_expected(&[Expected::Literal(r#"""#)])
|
||||
.with_unexpected(Span::new_unchecked(0, 0)),
|
||||
);
|
||||
} else {
|
||||
error.report_error(
|
||||
ParseError::new("string values must be quoted")
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_expected(&[Expected::Description("literal string")])
|
||||
.with_unexpected(Span::new_unchecked(0, raw.len())),
|
||||
);
|
||||
}
|
||||
|
||||
output.clear();
|
||||
if !output.push_str(raw.as_str()) {
|
||||
error.report_error(
|
||||
ParseError::new(ALLOCATION_ERROR).with_unexpected(Span::new_unchecked(0, raw.len())),
|
||||
);
|
||||
}
|
||||
ScalarKind::String
|
||||
}
|
||||
+1279
File diff suppressed because it is too large.
Load diff
Vendored
+76
@@ -0,0 +1,76 @@
|
||||
use core::ops::RangeInclusive;
|
||||
|
||||
use winnow::stream::ContainsToken as _;
|
||||
|
||||
use crate::ErrorSink;
|
||||
use crate::Expected;
|
||||
use crate::ParseError;
|
||||
use crate::Raw;
|
||||
use crate::Span;
|
||||
use crate::lexer::COMMENT_START_SYMBOL;
|
||||
|
||||
/// Parse comment
|
||||
///
|
||||
/// ```abnf
|
||||
/// ;; Comment
|
||||
///
|
||||
/// comment-start-symbol = %x23 ; #
|
||||
/// non-ascii = %x80-D7FF / %xE000-10FFFF
|
||||
/// non-eol = %x09 / %x20-7E / non-ascii
|
||||
///
|
||||
/// comment = comment-start-symbol *non-eol
|
||||
/// ```
|
||||
pub(crate) fn decode_comment(raw: Raw<'_>, error: &mut dyn ErrorSink) {
|
||||
let s = raw.as_bytes();
|
||||
|
||||
if s.first() != Some(&COMMENT_START_SYMBOL) {
|
||||
error.report_error(
|
||||
ParseError::new("missing comment start")
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_expected(&[Expected::Literal("#")])
|
||||
.with_unexpected(Span::new_unchecked(0, 0)),
|
||||
);
|
||||
}
|
||||
|
||||
for (i, b) in s.iter().copied().enumerate() {
|
||||
if !NON_EOL.contains_token(b) {
|
||||
error.report_error(
|
||||
ParseError::new("invalid comment character")
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_expected(&[Expected::Description("printable characters")])
|
||||
.with_unexpected(Span::new_unchecked(i, i)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// non-ascii = %x80-D7FF / %xE000-10FFFF
|
||||
// - ASCII is 0xxxxxxx
|
||||
// - First byte for UTF-8 is 11xxxxxx
|
||||
// - Subsequent UTF-8 bytes are 10xxxxxx
|
||||
pub(crate) const NON_ASCII: RangeInclusive<u8> = 0x80..=0xff;
|
||||
|
||||
// non-eol = %x09 / %x20-7E / non-ascii
|
||||
pub(crate) const NON_EOL: (u8, RangeInclusive<u8>, RangeInclusive<u8>) =
|
||||
(0x09, 0x20..=0x7E, NON_ASCII);
|
||||
|
||||
/// Parse newline
|
||||
///
|
||||
/// ```abnf
|
||||
///;; Newline
|
||||
///
|
||||
/// newline = %x0A ; LF
|
||||
/// newline =/ %x0D.0A ; CRLF
|
||||
/// ```
|
||||
pub(crate) fn decode_newline(raw: Raw<'_>, error: &mut dyn ErrorSink) {
|
||||
let s = raw.as_str();
|
||||
|
||||
if s == "\r" {
|
||||
error.report_error(
|
||||
ParseError::new("carriage return must be followed by newline")
|
||||
.with_context(Span::new_unchecked(0, raw.len()))
|
||||
.with_expected(&[Expected::Literal("\n")])
|
||||
.with_unexpected(Span::new_unchecked(raw.len(), raw.len())),
|
||||
);
|
||||
}
|
||||
}
|
||||
Vendored
+102
@@ -0,0 +1,102 @@
|
||||
use crate::Span;
|
||||
|
||||
pub trait ErrorSink {
|
||||
fn report_error(&mut self, error: ParseError);
|
||||
}
|
||||
|
||||
impl<F> ErrorSink for F
|
||||
where
|
||||
F: FnMut(ParseError),
|
||||
{
|
||||
fn report_error(&mut self, error: ParseError) {
|
||||
(self)(error);
|
||||
}
|
||||
}
|
||||
|
||||
impl ErrorSink for () {
|
||||
fn report_error(&mut self, _error: ParseError) {}
|
||||
}
|
||||
|
||||
impl ErrorSink for Option<ParseError> {
|
||||
fn report_error(&mut self, error: ParseError) {
|
||||
self.get_or_insert(error);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
#[allow(unused_qualifications)]
|
||||
impl ErrorSink for alloc::vec::Vec<ParseError> {
|
||||
fn report_error(&mut self, error: ParseError) {
|
||||
self.push(error);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct ParseError {
|
||||
context: Option<Span>,
|
||||
description: ErrorStr,
|
||||
expected: Option<&'static [Expected]>,
|
||||
unexpected: Option<Span>,
|
||||
}
|
||||
|
||||
impl ParseError {
|
||||
pub fn new(description: impl Into<ErrorStr>) -> Self {
|
||||
Self {
|
||||
context: None,
|
||||
description: description.into(),
|
||||
expected: None,
|
||||
unexpected: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_context(mut self, context: Span) -> Self {
|
||||
self.context = Some(context);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_expected(mut self, expected: &'static [Expected]) -> Self {
|
||||
self.expected = Some(expected);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_unexpected(mut self, unexpected: Span) -> Self {
|
||||
self.unexpected = Some(unexpected);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn context(&self) -> Option<Span> {
|
||||
self.context
|
||||
}
|
||||
pub fn description(&self) -> &str {
|
||||
&self.description
|
||||
}
|
||||
pub fn expected(&self) -> Option<&'static [Expected]> {
|
||||
self.expected
|
||||
}
|
||||
pub fn unexpected(&self) -> Option<Span> {
|
||||
self.unexpected
|
||||
}
|
||||
|
||||
pub(crate) fn rebase_spans(mut self, offset: usize) -> Self {
|
||||
if let Some(context) = self.context.as_mut() {
|
||||
*context += offset;
|
||||
}
|
||||
if let Some(unexpected) = self.unexpected.as_mut() {
|
||||
*unexpected += offset;
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
type ErrorStr = alloc::borrow::Cow<'static, str>;
|
||||
#[cfg(not(feature = "alloc"))]
|
||||
type ErrorStr = &'static str;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum Expected {
|
||||
Literal(&'static str),
|
||||
Description(&'static str),
|
||||
}
|
||||
Vendored
+638
@@ -0,0 +1,638 @@
|
||||
//! Lex TOML tokens
|
||||
//!
|
||||
//! To get started, see [`Source::lex`][crate::Source::lex]
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "std")]
|
||||
mod test;
|
||||
mod token;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use winnow::stream::AsBStr as _;
|
||||
use winnow::stream::ContainsToken as _;
|
||||
use winnow::stream::FindSlice as _;
|
||||
use winnow::stream::Location;
|
||||
use winnow::stream::Stream as _;
|
||||
|
||||
use crate::Span;
|
||||
|
||||
pub use token::Token;
|
||||
pub use token::TokenKind;
|
||||
|
||||
/// Lex TOML [tokens][Token]
|
||||
///
|
||||
/// To get started, see [`Source::lex`][crate::Source::lex]
|
||||
pub struct Lexer<'i> {
|
||||
stream: Stream<'i>,
|
||||
eof: bool,
|
||||
}
|
||||
|
||||
impl<'i> Lexer<'i> {
|
||||
pub(crate) fn new(input: &'i str) -> Self {
|
||||
let mut stream = Stream::new(input);
|
||||
if input.as_bytes().starts_with(BOM) {
|
||||
let offset = BOM.len();
|
||||
#[cfg(feature = "unsafe")] // SAFETY: only called when next character is ASCII
|
||||
unsafe {
|
||||
stream.next_slice_unchecked(offset)
|
||||
};
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
stream.next_slice(offset);
|
||||
}
|
||||
Lexer { stream, eof: false }
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub fn into_vec(self) -> Vec<Token> {
|
||||
#![allow(unused_qualifications)] // due to MSRV of 1.66
|
||||
let capacity = core::cmp::min(
|
||||
self.stream.len(),
|
||||
usize::MAX / core::mem::size_of::<Token>(),
|
||||
);
|
||||
let mut vec = Vec::with_capacity(capacity);
|
||||
vec.extend(self);
|
||||
vec
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Lexer<'_> {
|
||||
type Item = Token;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let Some(peek_byte) = self.stream.as_bstr().first() else {
|
||||
if self.eof {
|
||||
return None;
|
||||
} else {
|
||||
self.eof = true;
|
||||
let start = self.stream.current_token_start();
|
||||
let span = Span::new_unchecked(start, start);
|
||||
return Some(Token::new(TokenKind::Eof, span));
|
||||
}
|
||||
};
|
||||
Some(process_token(*peek_byte, &mut self.stream))
|
||||
}
|
||||
}
|
||||
|
||||
const BOM: &[u8] = b"\xEF\xBB\xBF";
|
||||
|
||||
pub(crate) type Stream<'i> = winnow::stream::LocatingSlice<&'i str>;
|
||||
|
||||
fn process_token(peek_byte: u8, stream: &mut Stream<'_>) -> Token {
|
||||
let token = match peek_byte {
|
||||
b'.' => lex_ascii_char(stream, TokenKind::Dot),
|
||||
b'=' => lex_ascii_char(stream, TokenKind::Equals),
|
||||
b',' => lex_ascii_char(stream, TokenKind::Comma),
|
||||
b'[' => lex_ascii_char(stream, TokenKind::LeftSquareBracket),
|
||||
b']' => lex_ascii_char(stream, TokenKind::RightSquareBracket),
|
||||
b'{' => lex_ascii_char(stream, TokenKind::LeftCurlyBracket),
|
||||
b'}' => lex_ascii_char(stream, TokenKind::RightCurlyBracket),
|
||||
b' ' => lex_whitespace(stream),
|
||||
b'\t' => lex_whitespace(stream),
|
||||
b'#' => lex_comment(stream),
|
||||
b'\r' => lex_crlf(stream),
|
||||
b'\n' => lex_ascii_char(stream, TokenKind::Newline),
|
||||
b'\'' => {
|
||||
if stream.starts_with(ML_LITERAL_STRING_DELIM) {
|
||||
lex_ml_literal_string(stream)
|
||||
} else {
|
||||
lex_literal_string(stream)
|
||||
}
|
||||
}
|
||||
b'"' => {
|
||||
if stream.starts_with(ML_BASIC_STRING_DELIM) {
|
||||
lex_ml_basic_string(stream)
|
||||
} else {
|
||||
lex_basic_string(stream)
|
||||
}
|
||||
}
|
||||
_ => lex_atom(stream),
|
||||
};
|
||||
token
|
||||
}
|
||||
|
||||
/// Process an ASCII character token
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - `stream` must be UTF-8
|
||||
/// - `stream` must be non-empty
|
||||
/// - `stream[0]` must be ASCII
|
||||
fn lex_ascii_char(stream: &mut Stream<'_>, kind: TokenKind) -> Token {
|
||||
debug_assert!(!stream.is_empty());
|
||||
let start = stream.current_token_start();
|
||||
|
||||
let offset = 1; // an ascii character
|
||||
#[cfg(feature = "unsafe")] // SAFETY: only called when next character is ASCII
|
||||
unsafe {
|
||||
stream.next_slice_unchecked(offset)
|
||||
};
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
stream.next_slice(offset);
|
||||
|
||||
let end = stream.previous_token_end();
|
||||
let span = Span::new_unchecked(start, end);
|
||||
Token::new(kind, span)
|
||||
}
|
||||
|
||||
/// Process Whitespace
|
||||
///
|
||||
/// ```abnf
|
||||
/// ;; Whitespace
|
||||
///
|
||||
/// ws = *wschar
|
||||
/// wschar = %x20 ; Space
|
||||
/// wschar =/ %x09 ; Horizontal tab
|
||||
/// ```
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - `stream` must be UTF-8
|
||||
/// - `stream` must be non-empty
|
||||
fn lex_whitespace(stream: &mut Stream<'_>) -> Token {
|
||||
debug_assert!(!stream.is_empty());
|
||||
let start = stream.current_token_start();
|
||||
|
||||
let offset = stream
|
||||
.as_bstr()
|
||||
.offset_for(|b| !WSCHAR.contains_token(b))
|
||||
.unwrap_or(stream.eof_offset());
|
||||
#[cfg(feature = "unsafe")] // SAFETY: WSCHAR ensures `offset` will be at UTF-8 boundary
|
||||
unsafe {
|
||||
stream.next_slice_unchecked(offset)
|
||||
};
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
stream.next_slice(offset);
|
||||
|
||||
let end = stream.previous_token_end();
|
||||
let span = Span::new_unchecked(start, end);
|
||||
Token::new(TokenKind::Whitespace, span)
|
||||
}
|
||||
|
||||
/// ```abnf
|
||||
/// wschar = %x20 ; Space
|
||||
/// wschar =/ %x09 ; Horizontal tab
|
||||
/// ```
|
||||
pub(crate) const WSCHAR: (u8, u8) = (b' ', b'\t');
|
||||
|
||||
/// Process Comment
|
||||
///
|
||||
/// ```abnf
|
||||
/// ;; Comment
|
||||
///
|
||||
/// comment-start-symbol = %x23 ; #
|
||||
/// non-ascii = %x80-D7FF / %xE000-10FFFF
|
||||
/// non-eol = %x09 / %x20-7E / non-ascii
|
||||
///
|
||||
/// comment = comment-start-symbol *non-eol
|
||||
/// ```
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - `stream` must be UTF-8
|
||||
/// - `stream[0] == b'#'`
|
||||
fn lex_comment(stream: &mut Stream<'_>) -> Token {
|
||||
let start = stream.current_token_start();
|
||||
|
||||
let offset = stream
|
||||
.as_bytes()
|
||||
.find_slice((b'\r', b'\n'))
|
||||
.map(|s| s.start)
|
||||
.unwrap_or_else(|| stream.eof_offset());
|
||||
#[cfg(feature = "unsafe")] // SAFETY: newlines ensure `offset` is along UTF-8 boundary
|
||||
unsafe {
|
||||
stream.next_slice_unchecked(offset)
|
||||
};
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
stream.next_slice(offset);
|
||||
|
||||
let end = stream.previous_token_end();
|
||||
let span = Span::new_unchecked(start, end);
|
||||
Token::new(TokenKind::Comment, span)
|
||||
}
|
||||
|
||||
/// ```abnf
|
||||
/// comment-start-symbol = %x23 ; #
|
||||
/// ```
|
||||
pub(crate) const COMMENT_START_SYMBOL: u8 = b'#';
|
||||
|
||||
/// Process Newline
|
||||
///
|
||||
/// ```abnf
|
||||
/// ;; Newline
|
||||
///
|
||||
/// newline = %x0A ; LF
|
||||
/// newline =/ %x0D.0A ; CRLF
|
||||
/// ```
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - `stream` must be UTF-8
|
||||
/// - `stream[0] == b'\r'`
|
||||
fn lex_crlf(stream: &mut Stream<'_>) -> Token {
|
||||
let start = stream.current_token_start();
|
||||
|
||||
let mut offset = '\r'.len_utf8();
|
||||
let has_lf = stream.as_bstr().get(1) == Some(&b'\n');
|
||||
if has_lf {
|
||||
offset += '\n'.len_utf8();
|
||||
}
|
||||
|
||||
#[cfg(feature = "unsafe")] // SAFETY: newlines ensure `offset` is along UTF-8 boundary
|
||||
unsafe {
|
||||
stream.next_slice_unchecked(offset)
|
||||
};
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
stream.next_slice(offset);
|
||||
let end = stream.previous_token_end();
|
||||
let span = Span::new_unchecked(start, end);
|
||||
|
||||
Token::new(TokenKind::Newline, span)
|
||||
}
|
||||
|
||||
/// Process literal string
|
||||
///
|
||||
/// ```abnf
|
||||
/// ;; Literal String
|
||||
///
|
||||
/// literal-string = apostrophe *literal-char apostrophe
|
||||
///
|
||||
/// apostrophe = %x27 ; ' apostrophe
|
||||
///
|
||||
/// literal-char = %x09 / %x20-26 / %x28-7E / non-ascii
|
||||
/// ```
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - `stream` must be UTF-8
|
||||
/// - `stream[0] == b'\''`
|
||||
fn lex_literal_string(stream: &mut Stream<'_>) -> Token {
|
||||
let start = stream.current_token_start();
|
||||
|
||||
let offset = 1; // APOSTROPHE
|
||||
#[cfg(feature = "unsafe")] // SAFETY: only called when next character is ASCII
|
||||
unsafe {
|
||||
stream.next_slice_unchecked(offset)
|
||||
};
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
stream.next_slice(offset);
|
||||
|
||||
let offset = match stream.as_bstr().find_slice((APOSTROPHE, b'\n')) {
|
||||
Some(span) => {
|
||||
if stream.as_bstr()[span.start] == APOSTROPHE {
|
||||
span.end
|
||||
} else {
|
||||
span.start
|
||||
}
|
||||
}
|
||||
None => stream.eof_offset(),
|
||||
};
|
||||
#[cfg(feature = "unsafe")]
|
||||
// SAFETY: `APOSTROPHE`/newline ensure `offset` is along UTF-8 boundary
|
||||
unsafe {
|
||||
stream.next_slice_unchecked(offset)
|
||||
};
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
stream.next_slice(offset);
|
||||
|
||||
let end = stream.previous_token_end();
|
||||
let span = Span::new_unchecked(start, end);
|
||||
Token::new(TokenKind::LiteralString, span)
|
||||
}
|
||||
|
||||
/// ```abnf
|
||||
/// apostrophe = %x27 ; ' apostrophe
|
||||
/// ```
|
||||
pub(crate) const APOSTROPHE: u8 = b'\'';
|
||||
|
||||
/// Process multi-line literal string
|
||||
///
|
||||
/// ```abnf
|
||||
/// ;; Multiline Literal String
|
||||
///
|
||||
/// ml-literal-string = ml-literal-string-delim [ newline ] ml-literal-body
|
||||
/// ml-literal-string-delim
|
||||
/// ml-literal-string-delim = 3apostrophe
|
||||
/// ml-literal-body = *mll-content *( mll-quotes 1*mll-content ) [ mll-quotes ]
|
||||
///
|
||||
/// mll-content = literal-char / newline
|
||||
/// mll-quotes = 1*2apostrophe
|
||||
/// ```
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - `stream` must be UTF-8
|
||||
/// - `stream.starts_with(ML_LITERAL_STRING_DELIM)`
|
||||
fn lex_ml_literal_string(stream: &mut Stream<'_>) -> Token {
|
||||
let start = stream.current_token_start();
|
||||
|
||||
let offset = ML_LITERAL_STRING_DELIM.len();
|
||||
#[cfg(feature = "unsafe")] // SAFETY: only called when next character is ASCII
|
||||
unsafe {
|
||||
stream.next_slice_unchecked(offset)
|
||||
};
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
stream.next_slice(offset);
|
||||
|
||||
let offset = match stream.as_bstr().find_slice(ML_LITERAL_STRING_DELIM) {
|
||||
Some(span) => span.end,
|
||||
None => stream.eof_offset(),
|
||||
};
|
||||
#[cfg(feature = "unsafe")]
|
||||
// SAFETY: `ML_LITERAL_STRING_DELIM` ensure `offset` is along UTF-8 boundary
|
||||
unsafe {
|
||||
stream.next_slice_unchecked(offset)
|
||||
};
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
stream.next_slice(offset);
|
||||
|
||||
if stream.as_bstr().peek_token() == Some(APOSTROPHE) {
|
||||
let offset = 1;
|
||||
#[cfg(feature = "unsafe")] // SAFETY: `APOSTROPHE` ensure `offset` is along UTF-8 boundary
|
||||
unsafe {
|
||||
stream.next_slice_unchecked(offset)
|
||||
};
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
stream.next_slice(offset);
|
||||
|
||||
if stream.as_bstr().peek_token() == Some(APOSTROPHE) {
|
||||
let offset = 1;
|
||||
#[cfg(feature = "unsafe")]
|
||||
// SAFETY: `APOSTROPHE` ensure `offset` is along UTF-8 boundary
|
||||
unsafe {
|
||||
stream.next_slice_unchecked(offset)
|
||||
};
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
stream.next_slice(offset);
|
||||
}
|
||||
}
|
||||
|
||||
let end = stream.previous_token_end();
|
||||
let span = Span::new_unchecked(start, end);
|
||||
Token::new(TokenKind::MlLiteralString, span)
|
||||
}
|
||||
|
||||
/// ```abnf
|
||||
/// ml-literal-string-delim = 3apostrophe
|
||||
/// ```
|
||||
pub(crate) const ML_LITERAL_STRING_DELIM: &str = "'''";
|
||||
|
||||
/// Process basic string
|
||||
///
|
||||
/// ```abnf
|
||||
/// ;; Basic String
|
||||
///
|
||||
/// basic-string = quotation-mark *basic-char quotation-mark
|
||||
///
|
||||
/// quotation-mark = %x22 ; "
|
||||
///
|
||||
/// basic-char = basic-unescaped / escaped
|
||||
/// basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii
|
||||
/// escaped = escape escape-seq-char
|
||||
///
|
||||
/// escape = %x5C ; \
|
||||
/// escape-seq-char = %x22 ; " quotation mark U+0022
|
||||
/// escape-seq-char =/ %x5C ; \ reverse solidus U+005C
|
||||
/// escape-seq-char =/ %x62 ; b backspace U+0008
|
||||
/// escape-seq-char =/ %x65 ; e escape U+001B
|
||||
/// escape-seq-char =/ %x66 ; f form feed U+000C
|
||||
/// escape-seq-char =/ %x6E ; n line feed U+000A
|
||||
/// escape-seq-char =/ %x72 ; r carriage return U+000D
|
||||
/// escape-seq-char =/ %x74 ; t tab U+0009
|
||||
/// escape-seq-char =/ %x78 2HEXDIG ; xHH U+00HH
|
||||
/// escape-seq-char =/ %x75 4HEXDIG ; uHHHH U+HHHH
|
||||
/// escape-seq-char =/ %x55 8HEXDIG ; UHHHHHHHH U+HHHHHHHH
|
||||
/// ```
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - `stream` must be UTF-8
|
||||
/// - `stream[0] == b'"'`
|
||||
fn lex_basic_string(stream: &mut Stream<'_>) -> Token {
|
||||
let start = stream.current_token_start();
|
||||
|
||||
let offset = 1; // QUOTATION_MARK
|
||||
#[cfg(feature = "unsafe")] // SAFETY: only called when next character is ASCII
|
||||
unsafe {
|
||||
stream.next_slice_unchecked(offset)
|
||||
};
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
stream.next_slice(offset);
|
||||
|
||||
loop {
|
||||
// newline is present for error recovery
|
||||
match stream.as_bstr().find_slice((QUOTATION_MARK, ESCAPE, b'\n')) {
|
||||
Some(span) => {
|
||||
let found = stream.as_bstr()[span.start];
|
||||
if found == QUOTATION_MARK {
|
||||
let offset = span.end;
|
||||
#[cfg(feature = "unsafe")]
|
||||
// SAFETY: `QUOTATION_MARK` ensure `offset` is along UTF-8 boundary
|
||||
unsafe {
|
||||
stream.next_slice_unchecked(offset)
|
||||
};
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
stream.next_slice(offset);
|
||||
break;
|
||||
} else if found == ESCAPE {
|
||||
let offset = span.end;
|
||||
#[cfg(feature = "unsafe")]
|
||||
// SAFETY: `ESCAPE` / newline ensure `offset` is along UTF-8 boundary
|
||||
unsafe {
|
||||
stream.next_slice_unchecked(offset)
|
||||
};
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
stream.next_slice(offset);
|
||||
|
||||
let peek = stream.as_bstr().peek_token();
|
||||
match peek {
|
||||
Some(ESCAPE) | Some(QUOTATION_MARK) => {
|
||||
let offset = 1; // ESCAPE / QUOTATION_MARK
|
||||
#[cfg(feature = "unsafe")]
|
||||
#[cfg(feature = "unsafe")]
|
||||
// SAFETY: `ESCAPE` / newline ensure `offset` is along UTF-8 boundary
|
||||
unsafe {
|
||||
stream.next_slice_unchecked(offset)
|
||||
};
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
stream.next_slice(offset);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
continue;
|
||||
} else if found == b'\n' {
|
||||
let offset = span.start;
|
||||
#[cfg(feature = "unsafe")]
|
||||
// SAFETY: newline ensure `offset` is along UTF-8 boundary
|
||||
unsafe {
|
||||
stream.next_slice_unchecked(offset)
|
||||
};
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
stream.next_slice(offset);
|
||||
break;
|
||||
} else {
|
||||
unreachable!("found `{found}`");
|
||||
}
|
||||
}
|
||||
None => {
|
||||
stream.finish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let end = stream.previous_token_end();
|
||||
let span = Span::new_unchecked(start, end);
|
||||
Token::new(TokenKind::BasicString, span)
|
||||
}
|
||||
|
||||
/// ```abnf
|
||||
/// quotation-mark = %x22 ; "
|
||||
/// ```
|
||||
pub(crate) const QUOTATION_MARK: u8 = b'"';
|
||||
|
||||
/// ```abnf
|
||||
/// escape = %x5C ; \
|
||||
/// ```
|
||||
pub(crate) const ESCAPE: u8 = b'\\';
|
||||
|
||||
/// Process multi-line basic string
|
||||
///
|
||||
/// ```abnf
|
||||
/// ;; Multiline Basic String
|
||||
///
|
||||
/// ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body
|
||||
/// ml-basic-string-delim
|
||||
/// ml-basic-string-delim = 3quotation-mark
|
||||
/// ml-basic-body = *mlb-content *( mlb-quotes 1*mlb-content ) [ mlb-quotes ]
|
||||
///
|
||||
/// mlb-content = basic-char / newline / mlb-escaped-nl
|
||||
/// mlb-quotes = 1*2quotation-mark
|
||||
/// mlb-escaped-nl = escape ws newline *( wschar / newline )
|
||||
/// ```
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - `stream` must be UTF-8
|
||||
/// - `stream.starts_with(ML_BASIC_STRING_DELIM)`
|
||||
fn lex_ml_basic_string(stream: &mut Stream<'_>) -> Token {
|
||||
let start = stream.current_token_start();
|
||||
|
||||
let offset = ML_BASIC_STRING_DELIM.len();
|
||||
#[cfg(feature = "unsafe")] // SAFETY: only called when next character is ASCII
|
||||
unsafe {
|
||||
stream.next_slice_unchecked(offset)
|
||||
};
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
stream.next_slice(offset);
|
||||
|
||||
loop {
|
||||
// newline is present for error recovery
|
||||
match stream.as_bstr().find_slice((ML_BASIC_STRING_DELIM, "\\")) {
|
||||
Some(span) => {
|
||||
let found = stream.as_bstr()[span.start];
|
||||
if found == QUOTATION_MARK {
|
||||
let offset = span.end;
|
||||
#[cfg(feature = "unsafe")]
|
||||
// SAFETY: `QUOTATION_MARK` ensure `offset` is along UTF-8 boundary
|
||||
unsafe {
|
||||
stream.next_slice_unchecked(offset)
|
||||
};
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
stream.next_slice(offset);
|
||||
break;
|
||||
} else if found == ESCAPE {
|
||||
let offset = span.end;
|
||||
#[cfg(feature = "unsafe")]
|
||||
// SAFETY: `ESCAPE` ensure `offset` is along UTF-8 boundary
|
||||
unsafe {
|
||||
stream.next_slice_unchecked(offset)
|
||||
};
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
stream.next_slice(offset);
|
||||
|
||||
let peek = stream.as_bstr().peek_token();
|
||||
match peek {
|
||||
Some(ESCAPE) | Some(QUOTATION_MARK) => {
|
||||
let offset = 1; // ESCAPE / QUOTATION_MARK
|
||||
#[cfg(feature = "unsafe")]
|
||||
// SAFETY: `QUOTATION_MARK`/`ESCAPE` ensure `offset` is along UTF-8 boundary
|
||||
unsafe {
|
||||
stream.next_slice_unchecked(offset)
|
||||
};
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
stream.next_slice(offset);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
unreachable!("found `{found}`");
|
||||
}
|
||||
}
|
||||
None => {
|
||||
stream.finish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if stream.as_bstr().peek_token() == Some(QUOTATION_MARK) {
|
||||
let offset = 1;
|
||||
#[cfg(feature = "unsafe")]
|
||||
// SAFETY: `QUOTATION_MARK` ensure `offset` is along UTF-8 boundary
|
||||
unsafe {
|
||||
stream.next_slice_unchecked(offset)
|
||||
};
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
stream.next_slice(offset);
|
||||
if stream.as_bstr().peek_token() == Some(QUOTATION_MARK) {
|
||||
let offset = 1;
|
||||
#[cfg(feature = "unsafe")]
|
||||
// SAFETY: `QUOTATION_MARK` ensure `offset` is along UTF-8 boundary
|
||||
unsafe {
|
||||
stream.next_slice_unchecked(offset)
|
||||
};
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
stream.next_slice(offset);
|
||||
}
|
||||
}
|
||||
|
||||
let end = stream.previous_token_end();
|
||||
let span = Span::new_unchecked(start, end);
|
||||
Token::new(TokenKind::MlBasicString, span)
|
||||
}
|
||||
|
||||
/// ```abnf
|
||||
/// ml-basic-string-delim = 3quotation-mark
|
||||
/// ```
|
||||
pub(crate) const ML_BASIC_STRING_DELIM: &str = "\"\"\"";
|
||||
|
||||
/// Process Atom
|
||||
///
|
||||
/// This is everything else
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - `stream` must be UTF-8
|
||||
/// - `stream` must be non-empty
|
||||
fn lex_atom(stream: &mut Stream<'_>) -> Token {
|
||||
let start = stream.current_token_start();
|
||||
|
||||
// Intentionally leaves off quotes in case the opening quote was missing
|
||||
const TOKEN_START: &[u8] = b".=,[]{} \t#\r\n";
|
||||
let offset = stream
|
||||
.as_bstr()
|
||||
.offset_for(|b| TOKEN_START.contains_token(b))
|
||||
.unwrap_or_else(|| stream.eof_offset());
|
||||
#[cfg(feature = "unsafe")] // SAFETY: `TOKEN_START` ensure `offset` is along UTF-8 boundary
|
||||
unsafe {
|
||||
stream.next_slice_unchecked(offset)
|
||||
};
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
stream.next_slice(offset);
|
||||
|
||||
let end = stream.previous_token_end();
|
||||
let span = Span::new_unchecked(start, end);
|
||||
Token::new(TokenKind::Atom, span)
|
||||
}
|
||||
Vendored
+1700
File diff suppressed because it is too large.
Load diff
+106
@@ -0,0 +1,106 @@
|
||||
//! Lexed TOML tokens
|
||||
|
||||
use super::APOSTROPHE;
|
||||
use super::COMMENT_START_SYMBOL;
|
||||
use super::QUOTATION_MARK;
|
||||
use super::Span;
|
||||
use super::WSCHAR;
|
||||
use crate::decoder::Encoding;
|
||||
|
||||
/// An unvalidated TOML Token
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub struct Token {
|
||||
pub(super) kind: TokenKind,
|
||||
pub(super) span: Span,
|
||||
}
|
||||
|
||||
impl Token {
|
||||
pub(super) fn new(kind: TokenKind, span: Span) -> Self {
|
||||
Self { kind, span }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn kind(&self) -> TokenKind {
|
||||
self.kind
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum TokenKind {
|
||||
/// Either for dotted-key or float
|
||||
Dot = b'.',
|
||||
/// Key-value separator
|
||||
Equals = b'=',
|
||||
/// Value separator
|
||||
Comma = b',',
|
||||
/// Either array or standard-table start
|
||||
LeftSquareBracket = b'[',
|
||||
/// Either array or standard-table end
|
||||
RightSquareBracket = b']',
|
||||
/// Inline table start
|
||||
LeftCurlyBracket = b'{',
|
||||
/// Inline table end
|
||||
RightCurlyBracket = b'}',
|
||||
Whitespace = WSCHAR.0,
|
||||
Comment = COMMENT_START_SYMBOL,
|
||||
Newline = b'\n',
|
||||
LiteralString = APOSTROPHE,
|
||||
BasicString = QUOTATION_MARK,
|
||||
MlLiteralString = 1,
|
||||
MlBasicString,
|
||||
|
||||
/// Anything else
|
||||
Atom,
|
||||
|
||||
Eof,
|
||||
}
|
||||
|
||||
impl TokenKind {
|
||||
pub const fn description(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Dot => "`.`",
|
||||
Self::Equals => "`=`",
|
||||
Self::Comma => "`,`",
|
||||
Self::LeftSquareBracket => "`[`",
|
||||
Self::RightSquareBracket => "`]`",
|
||||
Self::LeftCurlyBracket => "`{`",
|
||||
Self::RightCurlyBracket => "`}`",
|
||||
Self::Whitespace => "whitespace",
|
||||
Self::Comment => "comment",
|
||||
Self::Newline => "newline",
|
||||
Self::LiteralString => "literal string",
|
||||
Self::BasicString => "basic string",
|
||||
Self::MlLiteralString => "multi-line literal string",
|
||||
Self::MlBasicString => "multi-line basic string",
|
||||
Self::Atom => "token",
|
||||
Self::Eof => "end-of-input",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encoding(&self) -> Option<Encoding> {
|
||||
match self {
|
||||
Self::LiteralString => Some(Encoding::LiteralString),
|
||||
Self::BasicString => Some(Encoding::BasicString),
|
||||
Self::MlLiteralString => Some(Encoding::MlLiteralString),
|
||||
Self::MlBasicString => Some(Encoding::MlBasicString),
|
||||
Self::Atom
|
||||
| Self::LeftSquareBracket
|
||||
| Self::RightSquareBracket
|
||||
| Self::Dot
|
||||
| Self::Equals
|
||||
| Self::Comma
|
||||
| Self::RightCurlyBracket
|
||||
| Self::LeftCurlyBracket
|
||||
| Self::Whitespace
|
||||
| Self::Newline
|
||||
| Self::Comment
|
||||
| Self::Eof => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+49
@@ -0,0 +1,49 @@
|
||||
//! TOML lexer and parser
|
||||
//!
|
||||
//! Characteristics:
|
||||
//! - Error recovery
|
||||
//! - Lazy validation
|
||||
//! - `forbid(unsafe)` by default, requiring the `unsafe` feature otherwise
|
||||
//! - `no_std` support, including putting users in charge of allocation choices (including not
|
||||
//! allocating)
|
||||
//!
|
||||
//! Full parsing is broken into three phases:
|
||||
//! 1. [Lexing tokens][lexer]
|
||||
//! 2. [Parsing tokens][parser] (push parser)
|
||||
//! 3. Organizing the physical layout into the logical layout,
|
||||
//! including [decoding keys and values][decoder]
|
||||
|
||||
#![cfg_attr(all(not(feature = "std"), not(test)), no_std)]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(not(feature = "unsafe"), forbid(unsafe_code))]
|
||||
#![warn(clippy::std_instead_of_core)]
|
||||
#![warn(clippy::std_instead_of_alloc)]
|
||||
#![warn(clippy::print_stderr)]
|
||||
#![warn(clippy::print_stdout)]
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
extern crate alloc;
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
pub(crate) mod debug;
|
||||
mod error;
|
||||
mod source;
|
||||
|
||||
pub mod decoder;
|
||||
pub mod lexer;
|
||||
pub mod parser;
|
||||
|
||||
pub use error::ErrorSink;
|
||||
pub use error::Expected;
|
||||
pub use error::ParseError;
|
||||
pub use source::Raw;
|
||||
pub use source::Source;
|
||||
pub use source::SourceIndex;
|
||||
pub use source::Span;
|
||||
|
||||
#[doc = include_str!("../README.md")]
|
||||
#[cfg(doctest)]
|
||||
pub struct ReadmeDoctests;
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
|
||||
+1626
File diff suppressed because it is too large.
Load diff
+543
@@ -0,0 +1,543 @@
|
||||
use crate::ErrorSink;
|
||||
use crate::ParseError;
|
||||
use crate::Source;
|
||||
use crate::Span;
|
||||
use crate::decoder::Encoding;
|
||||
|
||||
pub trait EventReceiver {
|
||||
fn std_table_open(&mut self, _span: Span, _error: &mut dyn ErrorSink) {}
|
||||
fn std_table_close(&mut self, _span: Span, _error: &mut dyn ErrorSink) {}
|
||||
fn array_table_open(&mut self, _span: Span, _error: &mut dyn ErrorSink) {}
|
||||
fn array_table_close(&mut self, _span: Span, _error: &mut dyn ErrorSink) {}
|
||||
/// Returns if entering the inline table is allowed
|
||||
#[must_use]
|
||||
fn inline_table_open(&mut self, _span: Span, _error: &mut dyn ErrorSink) -> bool {
|
||||
true
|
||||
}
|
||||
fn inline_table_close(&mut self, _span: Span, _error: &mut dyn ErrorSink) {}
|
||||
/// Returns if entering the array is allowed
|
||||
#[must_use]
|
||||
fn array_open(&mut self, _span: Span, _error: &mut dyn ErrorSink) -> bool {
|
||||
true
|
||||
}
|
||||
fn array_close(&mut self, _span: Span, _error: &mut dyn ErrorSink) {}
|
||||
fn simple_key(&mut self, _span: Span, _kind: Option<Encoding>, _error: &mut dyn ErrorSink) {}
|
||||
fn key_sep(&mut self, _span: Span, _error: &mut dyn ErrorSink) {}
|
||||
fn key_val_sep(&mut self, _span: Span, _error: &mut dyn ErrorSink) {}
|
||||
fn scalar(&mut self, _span: Span, _kind: Option<Encoding>, _error: &mut dyn ErrorSink) {}
|
||||
fn value_sep(&mut self, _span: Span, _error: &mut dyn ErrorSink) {}
|
||||
fn whitespace(&mut self, _span: Span, _error: &mut dyn ErrorSink) {}
|
||||
fn comment(&mut self, _span: Span, _error: &mut dyn ErrorSink) {}
|
||||
fn newline(&mut self, _span: Span, _error: &mut dyn ErrorSink) {}
|
||||
fn error(&mut self, _span: Span, _error: &mut dyn ErrorSink) {}
|
||||
}
|
||||
|
||||
impl<F> EventReceiver for F
|
||||
where
|
||||
F: FnMut(Event),
|
||||
{
|
||||
fn std_table_open(&mut self, span: Span, _error: &mut dyn ErrorSink) {
|
||||
(self)(Event {
|
||||
kind: EventKind::StdTableOpen,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn std_table_close(&mut self, span: Span, _error: &mut dyn ErrorSink) {
|
||||
(self)(Event {
|
||||
kind: EventKind::StdTableClose,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn array_table_open(&mut self, span: Span, _error: &mut dyn ErrorSink) {
|
||||
(self)(Event {
|
||||
kind: EventKind::ArrayTableOpen,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn array_table_close(&mut self, span: Span, _error: &mut dyn ErrorSink) {
|
||||
(self)(Event {
|
||||
kind: EventKind::ArrayTableClose,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn inline_table_open(&mut self, span: Span, _error: &mut dyn ErrorSink) -> bool {
|
||||
(self)(Event {
|
||||
kind: EventKind::InlineTableOpen,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
true
|
||||
}
|
||||
fn inline_table_close(&mut self, span: Span, _error: &mut dyn ErrorSink) {
|
||||
(self)(Event {
|
||||
kind: EventKind::InlineTableClose,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn array_open(&mut self, span: Span, _error: &mut dyn ErrorSink) -> bool {
|
||||
(self)(Event {
|
||||
kind: EventKind::ArrayOpen,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
true
|
||||
}
|
||||
fn array_close(&mut self, span: Span, _error: &mut dyn ErrorSink) {
|
||||
(self)(Event {
|
||||
kind: EventKind::ArrayClose,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn simple_key(&mut self, span: Span, encoding: Option<Encoding>, _error: &mut dyn ErrorSink) {
|
||||
(self)(Event {
|
||||
kind: EventKind::SimpleKey,
|
||||
encoding,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn key_sep(&mut self, span: Span, _error: &mut dyn ErrorSink) {
|
||||
(self)(Event {
|
||||
kind: EventKind::KeySep,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn key_val_sep(&mut self, span: Span, _error: &mut dyn ErrorSink) {
|
||||
(self)(Event {
|
||||
kind: EventKind::KeyValSep,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn scalar(&mut self, span: Span, encoding: Option<Encoding>, _error: &mut dyn ErrorSink) {
|
||||
(self)(Event {
|
||||
kind: EventKind::Scalar,
|
||||
encoding,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn value_sep(&mut self, span: Span, _error: &mut dyn ErrorSink) {
|
||||
(self)(Event {
|
||||
kind: EventKind::ValueSep,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn whitespace(&mut self, span: Span, _error: &mut dyn ErrorSink) {
|
||||
(self)(Event {
|
||||
kind: EventKind::Whitespace,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn comment(&mut self, span: Span, _error: &mut dyn ErrorSink) {
|
||||
(self)(Event {
|
||||
kind: EventKind::Comment,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn newline(&mut self, span: Span, _error: &mut dyn ErrorSink) {
|
||||
(self)(Event {
|
||||
kind: EventKind::Newline,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn error(&mut self, span: Span, _error: &mut dyn ErrorSink) {
|
||||
(self)(Event {
|
||||
kind: EventKind::Error,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
#[allow(unused_qualifications)]
|
||||
impl EventReceiver for alloc::vec::Vec<Event> {
|
||||
fn std_table_open(&mut self, span: Span, _error: &mut dyn ErrorSink) {
|
||||
self.push(Event {
|
||||
kind: EventKind::StdTableOpen,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn std_table_close(&mut self, span: Span, _error: &mut dyn ErrorSink) {
|
||||
self.push(Event {
|
||||
kind: EventKind::StdTableClose,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn array_table_open(&mut self, span: Span, _error: &mut dyn ErrorSink) {
|
||||
self.push(Event {
|
||||
kind: EventKind::ArrayTableOpen,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn array_table_close(&mut self, span: Span, _error: &mut dyn ErrorSink) {
|
||||
self.push(Event {
|
||||
kind: EventKind::ArrayTableClose,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn inline_table_open(&mut self, span: Span, _error: &mut dyn ErrorSink) -> bool {
|
||||
self.push(Event {
|
||||
kind: EventKind::InlineTableOpen,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
true
|
||||
}
|
||||
fn inline_table_close(&mut self, span: Span, _error: &mut dyn ErrorSink) {
|
||||
self.push(Event {
|
||||
kind: EventKind::InlineTableClose,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn array_open(&mut self, span: Span, _error: &mut dyn ErrorSink) -> bool {
|
||||
self.push(Event {
|
||||
kind: EventKind::ArrayOpen,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
true
|
||||
}
|
||||
fn array_close(&mut self, span: Span, _error: &mut dyn ErrorSink) {
|
||||
self.push(Event {
|
||||
kind: EventKind::ArrayClose,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn simple_key(&mut self, span: Span, encoding: Option<Encoding>, _error: &mut dyn ErrorSink) {
|
||||
self.push(Event {
|
||||
kind: EventKind::SimpleKey,
|
||||
encoding,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn key_sep(&mut self, span: Span, _error: &mut dyn ErrorSink) {
|
||||
self.push(Event {
|
||||
kind: EventKind::KeySep,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn key_val_sep(&mut self, span: Span, _error: &mut dyn ErrorSink) {
|
||||
self.push(Event {
|
||||
kind: EventKind::KeyValSep,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn scalar(&mut self, span: Span, encoding: Option<Encoding>, _error: &mut dyn ErrorSink) {
|
||||
self.push(Event {
|
||||
kind: EventKind::Scalar,
|
||||
encoding,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn value_sep(&mut self, span: Span, _error: &mut dyn ErrorSink) {
|
||||
self.push(Event {
|
||||
kind: EventKind::ValueSep,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn whitespace(&mut self, span: Span, _error: &mut dyn ErrorSink) {
|
||||
self.push(Event {
|
||||
kind: EventKind::Whitespace,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn comment(&mut self, span: Span, _error: &mut dyn ErrorSink) {
|
||||
self.push(Event {
|
||||
kind: EventKind::Comment,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn newline(&mut self, span: Span, _error: &mut dyn ErrorSink) {
|
||||
self.push(Event {
|
||||
kind: EventKind::Newline,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
}
|
||||
fn error(&mut self, span: Span, _error: &mut dyn ErrorSink) {
|
||||
self.push(Event {
|
||||
kind: EventKind::Error,
|
||||
encoding: None,
|
||||
span,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl EventReceiver for () {}
|
||||
|
||||
/// Centralize validation for all whitespace-like content
|
||||
pub struct ValidateWhitespace<'r, 's> {
|
||||
receiver: &'r mut dyn EventReceiver,
|
||||
source: Source<'s>,
|
||||
}
|
||||
|
||||
impl<'r, 's> ValidateWhitespace<'r, 's> {
|
||||
pub fn new(receiver: &'r mut dyn EventReceiver, source: Source<'s>) -> Self {
|
||||
Self { receiver, source }
|
||||
}
|
||||
}
|
||||
|
||||
impl EventReceiver for ValidateWhitespace<'_, '_> {
|
||||
fn std_table_open(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.std_table_open(span, error);
|
||||
}
|
||||
fn std_table_close(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.std_table_close(span, error);
|
||||
}
|
||||
fn array_table_open(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.array_table_open(span, error);
|
||||
}
|
||||
fn array_table_close(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.array_table_close(span, error);
|
||||
}
|
||||
fn inline_table_open(&mut self, span: Span, error: &mut dyn ErrorSink) -> bool {
|
||||
self.receiver.inline_table_open(span, error)
|
||||
}
|
||||
fn inline_table_close(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.inline_table_close(span, error);
|
||||
}
|
||||
fn array_open(&mut self, span: Span, error: &mut dyn ErrorSink) -> bool {
|
||||
self.receiver.array_open(span, error)
|
||||
}
|
||||
fn array_close(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.array_close(span, error);
|
||||
}
|
||||
fn simple_key(&mut self, span: Span, encoding: Option<Encoding>, error: &mut dyn ErrorSink) {
|
||||
self.receiver.simple_key(span, encoding, error);
|
||||
}
|
||||
fn key_sep(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.key_sep(span, error);
|
||||
}
|
||||
fn key_val_sep(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.key_val_sep(span, error);
|
||||
}
|
||||
fn scalar(&mut self, span: Span, encoding: Option<Encoding>, error: &mut dyn ErrorSink) {
|
||||
self.receiver.scalar(span, encoding, error);
|
||||
}
|
||||
fn value_sep(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.value_sep(span, error);
|
||||
}
|
||||
fn whitespace(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
#[cfg(feature = "unsafe")] // SAFETY: callers must use valid span
|
||||
let raw = unsafe { self.source.get_unchecked(span) };
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
let raw = self.source.get(span).expect("token spans are valid");
|
||||
raw.decode_whitespace(error);
|
||||
|
||||
self.receiver.whitespace(span, error);
|
||||
}
|
||||
fn comment(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
#[cfg(feature = "unsafe")] // SAFETY: callers must use valid span
|
||||
let raw = unsafe { self.source.get_unchecked(span) };
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
let raw = self.source.get(span).expect("token spans are valid");
|
||||
raw.decode_comment(error);
|
||||
|
||||
self.receiver.comment(span, error);
|
||||
}
|
||||
fn newline(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
#[cfg(feature = "unsafe")] // SAFETY: callers must use valid span
|
||||
let raw = unsafe { self.source.get_unchecked(span) };
|
||||
#[cfg(not(feature = "unsafe"))]
|
||||
let raw = self.source.get(span).expect("token spans are valid");
|
||||
raw.decode_newline(error);
|
||||
|
||||
self.receiver.newline(span, error);
|
||||
}
|
||||
fn error(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.error(span, error);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RecursionGuard<'r> {
|
||||
receiver: &'r mut dyn EventReceiver,
|
||||
max_depth: u32,
|
||||
depth: i64,
|
||||
}
|
||||
|
||||
impl<'r> RecursionGuard<'r> {
|
||||
pub fn new(receiver: &'r mut dyn EventReceiver, max_depth: u32) -> Self {
|
||||
Self {
|
||||
receiver,
|
||||
max_depth,
|
||||
depth: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn within_depth(&self) -> bool {
|
||||
self.depth <= self.max_depth as i64
|
||||
}
|
||||
}
|
||||
|
||||
impl EventReceiver for RecursionGuard<'_> {
|
||||
fn std_table_open(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.std_table_open(span, error);
|
||||
}
|
||||
fn std_table_close(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.std_table_close(span, error);
|
||||
}
|
||||
fn array_table_open(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.array_table_open(span, error);
|
||||
}
|
||||
fn array_table_close(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.array_table_close(span, error);
|
||||
}
|
||||
fn inline_table_open(&mut self, span: Span, error: &mut dyn ErrorSink) -> bool {
|
||||
let allowed = self.receiver.inline_table_open(span, error);
|
||||
self.depth += 1;
|
||||
let within_depth = self.within_depth();
|
||||
if allowed && !within_depth {
|
||||
error.report_error(
|
||||
ParseError::new("cannot recurse further; max recursion depth met")
|
||||
.with_unexpected(span),
|
||||
);
|
||||
}
|
||||
allowed && within_depth
|
||||
}
|
||||
fn inline_table_close(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.depth -= 1;
|
||||
self.receiver.inline_table_close(span, error);
|
||||
}
|
||||
fn array_open(&mut self, span: Span, error: &mut dyn ErrorSink) -> bool {
|
||||
let allowed = self.receiver.array_open(span, error);
|
||||
self.depth += 1;
|
||||
let within_depth = self.within_depth();
|
||||
if allowed && !within_depth {
|
||||
error.report_error(
|
||||
ParseError::new("cannot recurse further; max recursion depth met")
|
||||
.with_unexpected(span),
|
||||
);
|
||||
}
|
||||
allowed && within_depth
|
||||
}
|
||||
fn array_close(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.depth -= 1;
|
||||
self.receiver.array_close(span, error);
|
||||
}
|
||||
fn simple_key(&mut self, span: Span, encoding: Option<Encoding>, error: &mut dyn ErrorSink) {
|
||||
self.receiver.simple_key(span, encoding, error);
|
||||
}
|
||||
fn key_sep(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.key_sep(span, error);
|
||||
}
|
||||
fn key_val_sep(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.key_val_sep(span, error);
|
||||
}
|
||||
fn scalar(&mut self, span: Span, encoding: Option<Encoding>, error: &mut dyn ErrorSink) {
|
||||
self.receiver.scalar(span, encoding, error);
|
||||
}
|
||||
fn value_sep(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.value_sep(span, error);
|
||||
}
|
||||
fn whitespace(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.whitespace(span, error);
|
||||
}
|
||||
fn comment(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.comment(span, error);
|
||||
}
|
||||
fn newline(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.newline(span, error);
|
||||
}
|
||||
fn error(&mut self, span: Span, error: &mut dyn ErrorSink) {
|
||||
self.receiver.error(span, error);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub struct Event {
|
||||
kind: EventKind,
|
||||
encoding: Option<Encoding>,
|
||||
span: Span,
|
||||
}
|
||||
|
||||
impl Event {
|
||||
pub fn new_unchecked(kind: EventKind, encoding: Option<Encoding>, span: Span) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
encoding,
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn kind(&self) -> EventKind {
|
||||
self.kind
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn encoding(&self) -> Option<Encoding> {
|
||||
self.encoding
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub enum EventKind {
|
||||
StdTableOpen,
|
||||
StdTableClose,
|
||||
ArrayTableOpen,
|
||||
ArrayTableClose,
|
||||
InlineTableOpen,
|
||||
InlineTableClose,
|
||||
ArrayOpen,
|
||||
ArrayClose,
|
||||
SimpleKey,
|
||||
KeySep,
|
||||
KeyValSep,
|
||||
Scalar,
|
||||
ValueSep,
|
||||
Whitespace,
|
||||
Comment,
|
||||
Newline,
|
||||
Error,
|
||||
}
|
||||
|
||||
impl EventKind {
|
||||
pub const fn description(&self) -> &'static str {
|
||||
match self {
|
||||
Self::StdTableOpen => "std-table open",
|
||||
Self::StdTableClose => "std-table close",
|
||||
Self::ArrayTableOpen => "array-table open",
|
||||
Self::ArrayTableClose => "array-table close",
|
||||
Self::InlineTableOpen => "inline-table open",
|
||||
Self::InlineTableClose => "inline-table close",
|
||||
Self::ArrayOpen => "array open",
|
||||
Self::ArrayClose => "array close",
|
||||
Self::SimpleKey => "key",
|
||||
Self::KeySep => "key separator",
|
||||
Self::KeyValSep => "key-value separator",
|
||||
Self::Scalar => "value",
|
||||
Self::ValueSep => "value separator",
|
||||
Self::Whitespace => "whitespace",
|
||||
Self::Comment => "comment",
|
||||
Self::Newline => "newline",
|
||||
Self::Error => "error",
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
//! A TOML push [parser][parse_document]
|
||||
//!
|
||||
//! This takes TOML [tokens][crate::lexer::Token] and [emits][EventReceiver] [events][Event].
|
||||
|
||||
mod document;
|
||||
mod event;
|
||||
|
||||
pub use document::parse_document;
|
||||
pub use document::parse_key;
|
||||
pub use document::parse_simple_key;
|
||||
pub use document::parse_value;
|
||||
pub use event::Event;
|
||||
pub use event::EventKind;
|
||||
pub use event::EventReceiver;
|
||||
pub use event::RecursionGuard;
|
||||
pub use event::ValidateWhitespace;
|
||||
Vendored
+394
@@ -0,0 +1,394 @@
|
||||
use crate::ErrorSink;
|
||||
use crate::Expected;
|
||||
use crate::decoder::Encoding;
|
||||
use crate::decoder::StringBuilder;
|
||||
use crate::lexer::Lexer;
|
||||
|
||||
/// Data encoded as TOML
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Source<'i> {
|
||||
input: &'i str,
|
||||
}
|
||||
|
||||
impl<'i> Source<'i> {
|
||||
pub fn new(input: &'i str) -> Self {
|
||||
Self { input }
|
||||
}
|
||||
|
||||
/// Start lexing the TOML encoded data
|
||||
pub fn lex(&self) -> Lexer<'i> {
|
||||
Lexer::new(self.input)
|
||||
}
|
||||
|
||||
/// Access the TOML encoded `&str`
|
||||
pub fn input(&self) -> &'i str {
|
||||
self.input
|
||||
}
|
||||
|
||||
/// Return a subslice of the input
|
||||
pub fn get(&self, span: impl SourceIndex) -> Option<Raw<'i>> {
|
||||
span.get(self)
|
||||
}
|
||||
|
||||
/// Return an unchecked subslice of the input
|
||||
///
|
||||
/// ## Safety
|
||||
///
|
||||
/// Callers of this function are responsible that these preconditions are satisfied:
|
||||
/// - The starting index must not exceed the ending index;
|
||||
/// - Indexes must be within bounds of the original slice;
|
||||
/// - Indexes must lie on UTF-8 sequence boundaries.
|
||||
///
|
||||
/// Or one of:
|
||||
/// - `span` came from [`Source::lex`]
|
||||
///
|
||||
/// Failing any of those, the returned string slice may reference invalid memory or violate the invariants communicated by `str` type.
|
||||
#[cfg(feature = "unsafe")]
|
||||
pub unsafe fn get_unchecked(&self, span: impl SourceIndex) -> Raw<'i> {
|
||||
// SAFETY: Same safety guarantees are required
|
||||
unsafe { span.get_unchecked(self) }
|
||||
}
|
||||
|
||||
/// Return a subslice of the input
|
||||
fn get_raw_str(&self, span: Span) -> Option<&'i str> {
|
||||
let index = span.start()..span.end();
|
||||
self.input.get(index)
|
||||
}
|
||||
|
||||
/// Return an unchecked subslice of the input
|
||||
///
|
||||
/// ## Safety
|
||||
///
|
||||
/// Callers of this function are responsible that these preconditions are satisfied:
|
||||
/// - The starting index must not exceed the ending index;
|
||||
/// - Indexes must be within bounds of the original slice;
|
||||
/// - Indexes must lie on UTF-8 sequence boundaries.
|
||||
///
|
||||
/// Or one of:
|
||||
/// - `span` came from [`Source::lex`]
|
||||
///
|
||||
/// Failing any of those, the returned string slice may reference invalid memory or violate the invariants communicated by `str` type.
|
||||
#[cfg(feature = "unsafe")]
|
||||
unsafe fn get_raw_str_unchecked(&self, span: Span) -> &'i str {
|
||||
let index = span.start()..span.end();
|
||||
// SAFETY: Same safety guarantees are required
|
||||
unsafe { self.input.get_unchecked(index) }
|
||||
}
|
||||
}
|
||||
|
||||
/// A slice of [`Source`]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Raw<'i> {
|
||||
raw: &'i str,
|
||||
encoding: Option<Encoding>,
|
||||
span: Span,
|
||||
}
|
||||
|
||||
impl<'i> Raw<'i> {
|
||||
pub fn new_unchecked(raw: &'i str, encoding: Option<Encoding>, span: Span) -> Self {
|
||||
Self {
|
||||
raw,
|
||||
encoding,
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decode_key(&self, output: &mut dyn StringBuilder<'i>, error: &mut dyn ErrorSink) {
|
||||
let mut error = |err: crate::ParseError| {
|
||||
error.report_error(err.rebase_spans(self.span.start));
|
||||
};
|
||||
match self.encoding {
|
||||
Some(Encoding::LiteralString) => {
|
||||
crate::decoder::string::decode_literal_string(*self, output, &mut error);
|
||||
}
|
||||
Some(Encoding::BasicString) => {
|
||||
crate::decoder::string::decode_basic_string(*self, output, &mut error);
|
||||
}
|
||||
Some(Encoding::MlLiteralString) => {
|
||||
error.report_error(
|
||||
crate::ParseError::new("keys cannot be multi-line literal strings")
|
||||
.with_expected(&[
|
||||
Expected::Description("basic string"),
|
||||
Expected::Description("literal string"),
|
||||
])
|
||||
.with_unexpected(Span::new_unchecked(0, self.len())),
|
||||
);
|
||||
crate::decoder::string::decode_ml_literal_string(*self, output, &mut error);
|
||||
}
|
||||
Some(Encoding::MlBasicString) => {
|
||||
error.report_error(
|
||||
crate::ParseError::new("keys cannot be multi-line basic strings")
|
||||
.with_expected(&[
|
||||
Expected::Description("basic string"),
|
||||
Expected::Description("literal string"),
|
||||
])
|
||||
.with_unexpected(Span::new_unchecked(0, self.len())),
|
||||
);
|
||||
crate::decoder::string::decode_ml_basic_string(*self, output, &mut error);
|
||||
}
|
||||
None => crate::decoder::string::decode_unquoted_key(*self, output, &mut error),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn decode_scalar(
|
||||
&self,
|
||||
output: &mut dyn StringBuilder<'i>,
|
||||
error: &mut dyn ErrorSink,
|
||||
) -> crate::decoder::scalar::ScalarKind {
|
||||
let mut error = |err: crate::ParseError| {
|
||||
error.report_error(err.rebase_spans(self.span.start));
|
||||
};
|
||||
match self.encoding {
|
||||
Some(Encoding::LiteralString) => {
|
||||
crate::decoder::string::decode_literal_string(*self, output, &mut error);
|
||||
crate::decoder::scalar::ScalarKind::String
|
||||
}
|
||||
Some(Encoding::BasicString) => {
|
||||
crate::decoder::string::decode_basic_string(*self, output, &mut error);
|
||||
crate::decoder::scalar::ScalarKind::String
|
||||
}
|
||||
Some(Encoding::MlLiteralString) => {
|
||||
crate::decoder::string::decode_ml_literal_string(*self, output, &mut error);
|
||||
crate::decoder::scalar::ScalarKind::String
|
||||
}
|
||||
Some(Encoding::MlBasicString) => {
|
||||
crate::decoder::string::decode_ml_basic_string(*self, output, &mut error);
|
||||
crate::decoder::scalar::ScalarKind::String
|
||||
}
|
||||
None => crate::decoder::scalar::decode_unquoted_scalar(*self, output, &mut error),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decode_whitespace(&self, _error: &mut dyn ErrorSink) {
|
||||
// whitespace is always valid
|
||||
}
|
||||
|
||||
pub fn decode_comment(&self, error: &mut dyn ErrorSink) {
|
||||
let mut error = |err: crate::ParseError| {
|
||||
error.report_error(err.rebase_spans(self.span.start));
|
||||
};
|
||||
crate::decoder::ws::decode_comment(*self, &mut error);
|
||||
}
|
||||
|
||||
pub fn decode_newline(&self, error: &mut dyn ErrorSink) {
|
||||
let mut error = |err: crate::ParseError| {
|
||||
error.report_error(err.rebase_spans(self.span.start));
|
||||
};
|
||||
crate::decoder::ws::decode_newline(*self, &mut error);
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &'i str {
|
||||
self.raw
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> &'i [u8] {
|
||||
self.raw.as_bytes()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.raw.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.raw.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Location within the [`Source`]
|
||||
#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Span {
|
||||
start: usize,
|
||||
end: usize,
|
||||
}
|
||||
|
||||
impl Span {
|
||||
pub fn new_unchecked(start: usize, end: usize) -> Self {
|
||||
Self { start, end }
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.end <= self.start
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.end - self.start
|
||||
}
|
||||
|
||||
pub fn start(&self) -> usize {
|
||||
self.start
|
||||
}
|
||||
|
||||
pub fn end(&self) -> usize {
|
||||
self.end
|
||||
}
|
||||
|
||||
pub fn before(&self) -> Self {
|
||||
Self::new_unchecked(self.start, self.start)
|
||||
}
|
||||
|
||||
pub fn after(&self) -> Self {
|
||||
Self::new_unchecked(self.end, self.end)
|
||||
}
|
||||
|
||||
/// Extend this `Raw` to the end of `after`
|
||||
#[must_use]
|
||||
pub fn append(&self, after: Self) -> Self {
|
||||
Self::new_unchecked(self.start, after.end)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for Span {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
(self.start..self.end).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Add<usize> for Span {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, offset: usize) -> Self::Output {
|
||||
Self::Output {
|
||||
start: self.start + offset,
|
||||
end: self.end + offset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Add<Span> for usize {
|
||||
type Output = Span;
|
||||
|
||||
fn add(self, span: Span) -> Self::Output {
|
||||
Self::Output {
|
||||
start: span.start + self,
|
||||
end: span.end + self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::AddAssign<usize> for Span {
|
||||
fn add_assign(&mut self, rhs: usize) {
|
||||
self.start += rhs;
|
||||
self.end += rhs;
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper trait used for indexing operations on [`Source`]
|
||||
pub trait SourceIndex: sealed::Sealed {
|
||||
/// Return a subslice of the input
|
||||
fn get<'i>(self, source: &Source<'i>) -> Option<Raw<'i>>;
|
||||
|
||||
/// Return an unchecked subslice of the input
|
||||
///
|
||||
/// ## Safety
|
||||
///
|
||||
/// Callers of this function are responsible that these preconditions are satisfied:
|
||||
/// - The starting index must not exceed the ending index;
|
||||
/// - Indexes must be within bounds of the original slice;
|
||||
/// - Indexes must lie on UTF-8 sequence boundaries.
|
||||
///
|
||||
/// Or one of:
|
||||
/// - `span` came from [`Source::lex`]
|
||||
///
|
||||
/// Failing any of those, the returned string slice may reference invalid memory or violate the invariants communicated by `str` type.
|
||||
#[cfg(feature = "unsafe")]
|
||||
unsafe fn get_unchecked<'i>(self, source: &Source<'i>) -> Raw<'i>;
|
||||
}
|
||||
|
||||
impl SourceIndex for Span {
|
||||
fn get<'i>(self, source: &Source<'i>) -> Option<Raw<'i>> {
|
||||
(&self).get(source)
|
||||
}
|
||||
|
||||
#[cfg(feature = "unsafe")]
|
||||
unsafe fn get_unchecked<'i>(self, source: &Source<'i>) -> Raw<'i> {
|
||||
// SAFETY: Same safety guarantees are required
|
||||
unsafe { (&self).get_unchecked(source) }
|
||||
}
|
||||
}
|
||||
|
||||
impl SourceIndex for &Span {
|
||||
fn get<'i>(self, source: &Source<'i>) -> Option<Raw<'i>> {
|
||||
let encoding = None;
|
||||
source
|
||||
.get_raw_str(*self)
|
||||
.map(|s| Raw::new_unchecked(s, encoding, *self))
|
||||
}
|
||||
|
||||
#[cfg(feature = "unsafe")]
|
||||
unsafe fn get_unchecked<'i>(self, source: &Source<'i>) -> Raw<'i> {
|
||||
let encoding = None;
|
||||
// SAFETY: Same safety guarantees are required
|
||||
let raw = unsafe { source.get_raw_str_unchecked(*self) };
|
||||
Raw::new_unchecked(raw, encoding, *self)
|
||||
}
|
||||
}
|
||||
|
||||
impl SourceIndex for crate::lexer::Token {
|
||||
fn get<'i>(self, source: &Source<'i>) -> Option<Raw<'i>> {
|
||||
(&self).get(source)
|
||||
}
|
||||
|
||||
#[cfg(feature = "unsafe")]
|
||||
unsafe fn get_unchecked<'i>(self, source: &Source<'i>) -> Raw<'i> {
|
||||
// SAFETY: Same safety guarantees are required
|
||||
unsafe { (&self).get_unchecked(source) }
|
||||
}
|
||||
}
|
||||
|
||||
impl SourceIndex for &crate::lexer::Token {
|
||||
fn get<'i>(self, source: &Source<'i>) -> Option<Raw<'i>> {
|
||||
let encoding = self.kind().encoding();
|
||||
source
|
||||
.get_raw_str(self.span())
|
||||
.map(|s| Raw::new_unchecked(s, encoding, self.span()))
|
||||
}
|
||||
|
||||
#[cfg(feature = "unsafe")]
|
||||
unsafe fn get_unchecked<'i>(self, source: &Source<'i>) -> Raw<'i> {
|
||||
let encoding = self.kind().encoding();
|
||||
// SAFETY: Same safety guarantees are required
|
||||
let raw = unsafe { source.get_raw_str_unchecked(self.span()) };
|
||||
Raw::new_unchecked(raw, encoding, self.span())
|
||||
}
|
||||
}
|
||||
|
||||
impl SourceIndex for crate::parser::Event {
|
||||
fn get<'i>(self, source: &Source<'i>) -> Option<Raw<'i>> {
|
||||
(&self).get(source)
|
||||
}
|
||||
|
||||
#[cfg(feature = "unsafe")]
|
||||
unsafe fn get_unchecked<'i>(self, source: &Source<'i>) -> Raw<'i> {
|
||||
// SAFETY: Same safety guarantees are required
|
||||
unsafe { (&self).get_unchecked(source) }
|
||||
}
|
||||
}
|
||||
|
||||
impl SourceIndex for &crate::parser::Event {
|
||||
fn get<'i>(self, source: &Source<'i>) -> Option<Raw<'i>> {
|
||||
let encoding = self.encoding();
|
||||
source
|
||||
.get_raw_str(self.span())
|
||||
.map(|s| Raw::new_unchecked(s, encoding, self.span()))
|
||||
}
|
||||
|
||||
#[cfg(feature = "unsafe")]
|
||||
unsafe fn get_unchecked<'i>(self, source: &Source<'i>) -> Raw<'i> {
|
||||
let encoding = self.encoding();
|
||||
// SAFETY: Same safety guarantees are required
|
||||
let raw = unsafe { source.get_raw_str_unchecked(self.span()) };
|
||||
Raw::new_unchecked(raw, encoding, self.span())
|
||||
}
|
||||
}
|
||||
|
||||
mod sealed {
|
||||
pub trait Sealed {}
|
||||
|
||||
impl Sealed for crate::Span {}
|
||||
impl Sealed for &crate::Span {}
|
||||
impl Sealed for crate::lexer::Token {}
|
||||
impl Sealed for &crate::lexer::Token {}
|
||||
impl Sealed for crate::parser::Event {}
|
||||
impl Sealed for &crate::parser::Event {}
|
||||
}
|
||||
Reference in new issue
Block a user