initial commit
This commit is contained in:
commit
d4ff37a828
9 changed files with 922 additions and 0 deletions
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
# SPDX-FileCopyrightText: 2023 Alain Zscheile <fogti+devel@ytrizja.de>
|
||||
#
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
.#*
|
||||
*.pdf
|
||||
build
|
||||
/target
|
||||
result
|
||||
result-*
|
||||
perf.data*
|
||||
flamegraph.svg
|
331
Cargo.lock
generated
Normal file
331
Cargo.lock
generated
Normal file
|
@ -0,0 +1,331 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miette"
|
||||
version = "5.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e"
|
||||
dependencies = [
|
||||
"miette-derive",
|
||||
"once_cell",
|
||||
"thiserror",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miette-derive"
|
||||
version = "5.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "new_debug_unreachable"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "precomputed-hash"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.193"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.193"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
|
||||
|
||||
[[package]]
|
||||
name = "string_cache"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b"
|
||||
dependencies = [
|
||||
"new_debug_unreachable",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"phf_shared",
|
||||
"precomputed-hash",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b7d0a2c048d661a1a59fcd7355baa232f7ed34e0ee4df2eef3c1c1c0d3852d8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
|
||||
|
||||
[[package]]
|
||||
name = "wafl-syntax"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"miette",
|
||||
"string_cache",
|
||||
"thiserror",
|
||||
"unicode-ident",
|
||||
"unicode-normalization",
|
||||
"yz-string-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "yz-string-utils"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5df07aec85c71914730e9bd51ad5be56054e1bea77999b523b74f585d641862"
|
7
Cargo.toml
Normal file
7
Cargo.toml
Normal file
|
@ -0,0 +1,7 @@
|
|||
[workspace]
|
||||
members = ["crates/*"]
|
||||
resolver = "2"
|
||||
|
||||
[profile.release]
|
||||
debug = 1
|
||||
lto = "thin"
|
13
crates/wafl-syntax/Cargo.toml
Normal file
13
crates/wafl-syntax/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "wafl-syntax"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
miette = "5.10"
|
||||
string_cache = "0.8"
|
||||
thiserror = "1.0"
|
||||
unicode-ident = "1.0"
|
||||
unicode-normalization = "0.1"
|
||||
yz-string-utils = "0.3.1"
|
3
crates/wafl-syntax/README.md
Normal file
3
crates/wafl-syntax/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# wafl-parser
|
||||
|
||||
A variable prefixed with `@` is a global variable. Note that these live on a stack of hashmaps.
|
139
crates/wafl-syntax/src/error.rs
Normal file
139
crates/wafl-syntax/src/error.rs
Normal file
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Alain Zscheile <fogti+devel@ytrizja.de>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
use core::{fmt, result::Result as CoreResult};
|
||||
use miette::{Diagnostic, SourceSpan};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Error {
|
||||
pub span: SourceSpan,
|
||||
pub kind: ErrorKind,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if f.alternate() {
|
||||
write!(f, "{:?}: ", self.span)?;
|
||||
}
|
||||
fmt::Display::fmt(&self.kind, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {
|
||||
#[inline(always)]
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
self.kind.source()
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = CoreResult<T, Error>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FullError {
|
||||
pub span: SourceSpan,
|
||||
pub kind: ErrorKind,
|
||||
pub code: miette::NamedSource,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
#[inline]
|
||||
pub fn with_code(self, code: miette::NamedSource) -> FullError {
|
||||
FullError {
|
||||
span: self.span,
|
||||
kind: self.kind,
|
||||
code,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FullError {
|
||||
#[inline(always)]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.kind, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for FullError {
|
||||
#[inline(always)]
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
self.kind.source()
|
||||
}
|
||||
}
|
||||
|
||||
impl miette::Diagnostic for FullError {
|
||||
#[inline(always)]
|
||||
fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
|
||||
self.kind.code()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn severity(&self) -> Option<miette::Severity> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
|
||||
Some(&self.code)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
|
||||
use miette::LabeledSpan as Lsp;
|
||||
Some(Box::new(
|
||||
Some(Lsp::new(None, self.span.offset(), self.span.len())).into_iter(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Diagnostic, thiserror::Error)]
|
||||
pub enum ErrorKind {
|
||||
#[error("expected {0}")]
|
||||
#[diagnostic(code(wafl::parser::expected))]
|
||||
Expected(ErrorCtx),
|
||||
|
||||
#[error("end of file encountered inside {0}")]
|
||||
#[diagnostic(code(wafl::parser::unexpected_eof))]
|
||||
UnexpectedEof(ErrorCtx),
|
||||
|
||||
#[error("unhandled character: '{0}'")]
|
||||
#[diagnostic(code(wafl::lexer::unhandled_char))]
|
||||
UnhandledChar(char),
|
||||
|
||||
#[error(transparent)]
|
||||
#[diagnostic(code(wafl::lexer::invalid_int))]
|
||||
InvalidInt(#[from] core::num::ParseIntError),
|
||||
|
||||
#[error("comment nesting overflow")]
|
||||
#[diagnostic(code(wafl::lexer::comment_nest_overflow))]
|
||||
CommentNestOverflow,
|
||||
|
||||
#[error("duplicated record field identifier {0:?}")]
|
||||
#[diagnostic(code(wafl::parser::record_dup_ident))]
|
||||
RecordDupIdent(Box<str>),
|
||||
}
|
||||
|
||||
macro_rules! error_ctx {
|
||||
($($a:ident => $b:expr),* $(,)?) => {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ErrorCtx {
|
||||
$($a,)*
|
||||
}
|
||||
|
||||
impl fmt::Display for ErrorCtx {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(match self {
|
||||
$(ErrorCtx::$a => $b,)*
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
error_ctx! {
|
||||
Comment => "comment",
|
||||
String => "string",
|
||||
Record => "record",
|
||||
}
|
287
crates/wafl-syntax/src/lex.rs
Normal file
287
crates/wafl-syntax/src/lex.rs
Normal file
|
@ -0,0 +1,287 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Alain Zscheile <fogti+devel@ytrizja.de>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
use alloc::{boxed::Box, string::ToString};
|
||||
use core::fmt;
|
||||
use miette::SourceSpan;
|
||||
use yz_string_utils::StrLexerBase;
|
||||
|
||||
use crate::{Error, ErrorCtx, ErrorKind, Result};
|
||||
|
||||
macro_rules! define_kws {
|
||||
($($a:ident => $b:expr),* $(,)?) => {
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Kw {
|
||||
$($a,)*
|
||||
}
|
||||
|
||||
impl core::str::FromStr for Kw {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> core::result::Result<Self, ()> {
|
||||
#[allow(unreachable_code)]
|
||||
Ok(match s {
|
||||
$($a => $b,)*
|
||||
_ => return Err(()),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
define_kws! {}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum TokenKind {
|
||||
Ident(Box<str>),
|
||||
DotIdent(Box<str>),
|
||||
GlobalIdent(Box<str>),
|
||||
|
||||
String(Box<str>),
|
||||
Integer(usize),
|
||||
|
||||
LParen,
|
||||
RParen,
|
||||
LBrace,
|
||||
RBrace,
|
||||
Dot,
|
||||
SemiColon,
|
||||
Assign,
|
||||
|
||||
Kw(Kw),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Token {
|
||||
pub kind: TokenKind,
|
||||
pub span: SourceSpan,
|
||||
}
|
||||
|
||||
impl fmt::Display for Token {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "@ {:?}: {:?}", self.span, self.kind)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Lexer<'a> {
|
||||
inner: StrLexerBase<'a>,
|
||||
}
|
||||
|
||||
impl Lexer<'_> {
|
||||
pub fn consume_ident(&mut self) -> Box<str> {
|
||||
use unicode_normalization::UnicodeNormalization;
|
||||
let s = self
|
||||
.inner
|
||||
.consume_select(unicode_ident::is_xid_continue)
|
||||
.nfc()
|
||||
.to_string();
|
||||
assert!(!s.is_empty());
|
||||
s.into()
|
||||
}
|
||||
|
||||
pub fn try_consume_ident(&mut self) -> Option<Box<str>> {
|
||||
if self
|
||||
.inner
|
||||
.inp
|
||||
.chars()
|
||||
.next()
|
||||
.map(unicode_ident::is_xid_start)
|
||||
== Some(true)
|
||||
{
|
||||
Some(self.consume_ident())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn offset(&self) -> usize {
|
||||
self.inner.offset
|
||||
}
|
||||
|
||||
pub fn peek(&self) -> Option<Result<Token>> {
|
||||
self.clone().next()
|
||||
}
|
||||
|
||||
pub fn peek_span(&self) -> SourceSpan {
|
||||
let mut this = self.clone();
|
||||
match this.next() {
|
||||
Some(Ok(Token { span, .. })) => span,
|
||||
Some(Err(Error { span, .. })) => span,
|
||||
None => (this.inner.offset..this.inner.offset).into(),
|
||||
}
|
||||
}
|
||||
|
||||
// handle EOF as error
|
||||
pub fn next_in_noeof(&mut self, ctx: ErrorCtx) -> Result<Token> {
|
||||
let offset = self.offset();
|
||||
self.next().unwrap_or_else(|| {
|
||||
Err(Error {
|
||||
span: (offset..offset).into(),
|
||||
kind: ErrorKind::UnexpectedEof(ctx),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// consume token if it is expected (for optional tokens)
|
||||
pub fn got(&mut self, xkind: TokenKind) -> Option<SourceSpan> {
|
||||
let mut nxt = self.clone();
|
||||
match nxt.next() {
|
||||
Some(Ok(Token { span, kind })) if xkind == kind => {
|
||||
*self = nxt;
|
||||
Some(span)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// like `got`, but produce a proper error message if it is not there
|
||||
pub fn expect(&mut self, xkind: TokenKind, ctx: ErrorCtx) -> Result<SourceSpan> {
|
||||
let mut nxt = self.clone();
|
||||
let Token { span, kind } = nxt.next_in_noeof(ctx)?;
|
||||
if xkind == kind {
|
||||
*self = nxt;
|
||||
Ok(span)
|
||||
} else {
|
||||
Err(Error {
|
||||
span,
|
||||
kind: ErrorKind::Expected(ctx),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Lexer<'a> {
|
||||
#[inline]
|
||||
pub fn new(inp: &'a str) -> Self {
|
||||
Self {
|
||||
inner: StrLexerBase { inp, offset: 0 },
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn inp(&self) -> &'a str {
|
||||
self.inner.inp
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Lexer<'_> {
|
||||
type Item = Result<Token>;
|
||||
|
||||
fn next(&mut self) -> Option<Result<Token>> {
|
||||
use TokenKind as Tk;
|
||||
let mut offset;
|
||||
let tmp = 'lvl: loop {
|
||||
// handle whitespace
|
||||
self.inner.consume_select(|i| i.is_whitespace());
|
||||
if self.inp().is_empty() {
|
||||
return None;
|
||||
}
|
||||
offset = self.offset();
|
||||
break match self.inp().chars().next()? {
|
||||
'0'..='9' => {
|
||||
let s = self.inner.consume_select(|i| i.is_ascii_digit());
|
||||
debug_assert!(!s.is_empty());
|
||||
s.parse().map(TokenKind::Integer).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
'"' => {
|
||||
let mut escape = false;
|
||||
let mut it = self.inp().chars().peekable();
|
||||
let mut res = String::new();
|
||||
loop {
|
||||
let x = match it.next() {
|
||||
None => break 'lvl Err(ErrorKind::UnexpectedEof(ErrorCtx::String)),
|
||||
Some(x) => x,
|
||||
};
|
||||
self.inner.consume(x.len_utf8());
|
||||
if escape {
|
||||
res.push(match x {
|
||||
'a' => '\x07',
|
||||
'b' => '\x08',
|
||||
'e' => '\x1b',
|
||||
'f' => '\x0c',
|
||||
'n' => '\n',
|
||||
'r' => '\r',
|
||||
't' => '\t',
|
||||
'v' => '\x0b',
|
||||
_ => break 'lvl Err(ErrorKind::UnhandledChar(x)),
|
||||
});
|
||||
escape = false;
|
||||
res.push(x);
|
||||
} else {
|
||||
match x {
|
||||
'"' => break,
|
||||
'\\' => escape = true,
|
||||
_ => res.push(x),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Tk::String(res.into_boxed_str()))
|
||||
}
|
||||
|
||||
c if unicode_ident::is_xid_start(c) => {
|
||||
// identifier
|
||||
let s = self.consume_ident();
|
||||
// handle keywords
|
||||
Ok(match s.parse::<Kw>() {
|
||||
Ok(x) => Tk::Kw(x),
|
||||
Err(_) => Tk::Ident(s),
|
||||
})
|
||||
}
|
||||
|
||||
c => {
|
||||
self.inner.consume(c.len_utf8());
|
||||
Ok(match c {
|
||||
'.' => Tk::DotIdent(self.try_consume_ident().unwrap_or_else(|| String::new().into_boxed_str())),
|
||||
'@' => break 'lvl self.try_consume_ident().map(Tk::GlobalIdent).ok_or_else(|| ErrorKind::UnhandledChar(c)),
|
||||
';' => Tk::SemiColon,
|
||||
'{' => Tk::LBrace,
|
||||
'}' => Tk::RBrace,
|
||||
'(' /* ) */ => {
|
||||
if self.inp().starts_with('*') {
|
||||
// comment
|
||||
let mut lvl = 1u32;
|
||||
let mut it = self.inp().chars().peekable();
|
||||
while lvl > 0 {
|
||||
let c = match it.next() {
|
||||
Some(c) => c,
|
||||
None => break 'lvl Err(ErrorKind::UnexpectedEof(ErrorCtx::Comment)),
|
||||
};
|
||||
self.inner.consume(c.len_utf8());
|
||||
let c2 = it.peek().copied();
|
||||
match (c, c2) {
|
||||
('(', Some('*')) => lvl = match lvl.checked_add(1) {
|
||||
Some(x) => x,
|
||||
None => break 'lvl Err(ErrorKind::CommentNestOverflow),
|
||||
},
|
||||
('*', Some(')')) => {
|
||||
lvl -= 1;
|
||||
it.next();
|
||||
self.inner.consume(1);
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
Tk::LParen
|
||||
}
|
||||
}
|
||||
/* ( */ ')' => Tk::RParen,
|
||||
_ => break 'lvl Err(ErrorKind::UnhandledChar(c)),
|
||||
})
|
||||
}
|
||||
};
|
||||
};
|
||||
let span = (offset..self.offset()).into();
|
||||
Some(
|
||||
tmp.map(|kind| Token { span, kind })
|
||||
.map_err(|kind| Error { span, kind }),
|
||||
)
|
||||
}
|
||||
}
|
62
crates/wafl-syntax/src/lib.rs
Normal file
62
crates/wafl-syntax/src/lib.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Alain Zscheile <fogti+devel@ytrizja.de>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
extern crate alloc;
|
||||
|
||||
mod error;
|
||||
pub use error::{Error, ErrorCtx, ErrorKind, Result};
|
||||
|
||||
pub mod lex;
|
||||
|
||||
pub mod record;
|
||||
|
||||
pub use string_cache::DefaultAtom as Atom;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! none_up {
|
||||
($x:expr) => {
|
||||
match $x {
|
||||
None => return Ok(None),
|
||||
Some(x) => x,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Env<'a> {
|
||||
pub lxr: lex::Lexer<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Env<'a> {
|
||||
pub fn new(lxr: lex::Lexer<'a>) -> Self {
|
||||
Self { lxr }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Parse: Sized {
|
||||
fn parse(env: &mut Env<'_>) -> Result<Self>;
|
||||
}
|
||||
|
||||
pub trait MaybeParse: Sized {
|
||||
const DFL_CTX: ErrorCtx;
|
||||
/// this function allows to clearly differentiate between
|
||||
/// recoverable failures and unrecoverable ones.
|
||||
fn maybe_parse(env: &mut Env<'_>) -> Result<Option<Self>>;
|
||||
}
|
||||
|
||||
impl<T: MaybeParse> Parse for T {
|
||||
fn parse(env: &mut Env<'_>) -> Result<Self> {
|
||||
//let knamcnt = env.names.len();
|
||||
let span = env.lxr.peek_span();
|
||||
let mres = T::maybe_parse(env);
|
||||
//assert_eq!(env.names.len(), knamcnt);
|
||||
mres?.ok_or_else(|| Error {
|
||||
span,
|
||||
kind: ErrorKind::Expected(<T as MaybeParse>::DFL_CTX),
|
||||
})
|
||||
}
|
||||
}
|
68
crates/wafl-syntax/src/record.rs
Normal file
68
crates/wafl-syntax/src/record.rs
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Alain Zscheile <fogti+devel@ytrizja.de>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
use miette::SourceSpan;
|
||||
|
||||
use crate::{
|
||||
lex::{Token, TokenKind as Tk},
|
||||
none_up, Atom, Env, Error, ErrorCtx as PeCtx, ErrorKind as Pek, MaybeParse, Parse,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Record<V>(pub Vec<(SourceSpan, Option<Atom>, V)>);
|
||||
|
||||
impl<V: Parse> MaybeParse for Record<V> {
|
||||
const DFL_CTX: PeCtx = PeCtx::Record;
|
||||
|
||||
fn maybe_parse(env: &mut Env<'_>) -> Result<Option<Self>, Error> {
|
||||
none_up!(env.lxr.got(Tk::LBrace));
|
||||
let mut fields: Vec<(_, Option<Atom>, _)> = Vec::new();
|
||||
|
||||
// warning: this code executes in O(|fields|²)
|
||||
loop {
|
||||
let span_start;
|
||||
let name = {
|
||||
let mut lxrnxt = env.lxr.clone();
|
||||
let Token { kind, span } = match lxrnxt.next_in_noeof(PeCtx::Record) {
|
||||
Err(e) => {
|
||||
env.lxr = lxrnxt;
|
||||
return Err(e);
|
||||
}
|
||||
Ok(x) => x,
|
||||
};
|
||||
span_start = span.offset();
|
||||
|
||||
match kind {
|
||||
Tk::DotIdent(i) => {
|
||||
let i_ = Atom::from(&*i);
|
||||
if env.lxr.expect(Tk::Assign, PeCtx::Record).is_ok() {
|
||||
if fields.iter().any(|(_, j, _)| j.as_ref() == Some(&i_)) {
|
||||
return Err(Error {
|
||||
span,
|
||||
kind: Pek::RecordDupIdent(i),
|
||||
});
|
||||
}
|
||||
// "fronttrack"
|
||||
env.lxr = lxrnxt;
|
||||
Some(i_)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Tk::RBrace => break,
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
let expr = V::parse(env)?;
|
||||
let span_end = env.lxr.offset();
|
||||
env.lxr.expect(Tk::SemiColon, PeCtx::Record)?;
|
||||
fields.push(((span_start..span_end).into(), name, expr));
|
||||
}
|
||||
|
||||
env.lxr.expect(Tk::RBrace, PeCtx::Record)?;
|
||||
Ok(Some(Record(fields)))
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue