initial commit

This commit is contained in:
Alain Emilia Anna Zscheile 2023-12-24 02:23:23 +01:00
commit d4ff37a828
9 changed files with 922 additions and 0 deletions

12
.gitignore vendored Normal file
View 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
View 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
View file

@ -0,0 +1,7 @@
[workspace]
members = ["crates/*"]
resolver = "2"
[profile.release]
debug = 1
lto = "thin"

View 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"

View file

@ -0,0 +1,3 @@
# wafl-parser
A variable prefixed with `@` is a global variable. Note that these live on a stack of hashmaps.

View 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",
}

View 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 }),
)
}
}

View 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),
})
}
}

View 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)))
}
}