use crate::lang::error::Error;
use crate::lang::error::ErrorKind;
use crate::value::span::Span;
use core::ops::Range;
use logos::Logos;
#[derive(PartialEq)]
pub(crate) struct Token<'a> {
pub(crate) kind: Kind,
pub(crate) span: Span,
pub(crate) data: &'a [u8],
}
impl core::fmt::Debug for Token<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Token")
.field("kind", &self.kind)
.field("span", &self.span)
.field("data", &String::from_utf8_lossy(self.data))
.finish()
}
}
impl From<Token<'_>> for Span {
fn from(value: Token<'_>) -> Self {
value.span
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum Kind {
Argument,
And,
BraceEnd,
BraceStart,
BracketEnd,
BracketStart,
Comma,
Comment,
Dot,
DotDot,
DotDotDot,
Label,
Number,
Or,
ParenEnd,
ParenStart,
Pipe,
Reserved,
Semicolon,
Space,
StringDQ,
StringSQ,
Tag,
}
impl From<&Lexeme<'_>> for Kind {
fn from(value: &Lexeme<'_>) -> Self {
match value {
Lexeme::Argument(_) => Kind::Argument,
Lexeme::And(_) => Kind::And,
Lexeme::BraceEnd(_) => Kind::BraceEnd,
Lexeme::BraceStart(_) => Kind::BraceStart,
Lexeme::BracketEnd(_) => Kind::BracketEnd,
Lexeme::BracketStart(_) => Kind::BracketStart,
Lexeme::Comma(_) => Kind::Comma,
Lexeme::Comment(_) => Kind::Comment,
Lexeme::Dot(_) => Kind::Dot,
Lexeme::DotDot(_) => Kind::DotDot,
Lexeme::DotDotDot(_) => Kind::DotDotDot,
Lexeme::Label(_) => Kind::Label,
Lexeme::Number(_) => Kind::Number,
Lexeme::Or(_) => Kind::Or,
Lexeme::ParenEnd(_) => Kind::ParenEnd,
Lexeme::ParenStart(_) => Kind::ParenStart,
Lexeme::Pipe(_) => Kind::Pipe,
Lexeme::Reserved(_) => Kind::Reserved,
Lexeme::Semicolon(_) => Kind::Semicolon,
Lexeme::Space(_) => Kind::Space,
Lexeme::StringDQ(_) => Kind::StringDQ,
Lexeme::StringSQ(_) => Kind::StringSQ,
Lexeme::Tag(_) => Kind::Tag,
}
}
}
impl Kind {
pub(crate) fn is_pipe(&self) -> bool {
matches!(self, Kind::Pipe | Kind::And | Kind::Or | Kind::Semicolon)
}
pub(crate) fn is_start(&self) -> bool {
matches!(
self,
Kind::BraceStart | Kind::BracketStart | Kind::ParenStart
)
}
pub(crate) fn is_end(&self) -> bool {
matches!(self, Kind::BraceEnd | Kind::BracketEnd | Kind::ParenEnd)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Logos)]
#[logos(utf8 = false)]
pub(crate) enum Lexeme<'a> {
#[regex(r#"--[^-]+"#)]
Argument(&'a [u8]),
#[token(b"}")]
BraceEnd(&'a [u8]),
#[token(b"{")]
BraceStart(&'a [u8]),
#[token(b"]")]
BracketEnd(&'a [u8]),
#[token(b"[")]
BracketStart(&'a [u8]),
#[token(b",")]
Comma(&'a [u8]),
#[regex(br"#[^\n]*", allow_greedy = true)]
Comment(&'a [u8]),
#[token(b".")]
Dot(&'a [u8]),
#[token(b"..")]
DotDot(&'a [u8]),
#[token(b"...")]
DotDotDot(&'a [u8]),
#[regex(br#"[!$%/=+?^*@<>]+"#)]
Reserved(&'a [u8]),
#[regex(r#"[\p{XID_Start}:_][\p{XID_Continue}/\.:_-]*"#)]
Label(&'a [u8]),
#[regex(br"-?\d+(\.\d+)?")]
Number(&'a [u8]),
#[token(b")")]
ParenEnd(&'a [u8]),
#[token(b"(")]
ParenStart(&'a [u8]),
#[token(b"|")]
Pipe(&'a [u8]),
#[token(b"||")]
Or(&'a [u8]),
#[token(b"&&")]
And(&'a [u8]),
#[token(b";")]
Semicolon(&'a [u8]),
#[regex(br"\s+")]
Space(&'a [u8]),
#[regex(br#""(?:[^"]|\\")*""#)]
StringDQ(&'a [u8]),
#[regex(br#"'(?:[^']|\\')*'"#)]
StringSQ(&'a [u8]),
#[regex(br#"<(?:[^>]|\\>)*>"#)]
Tag(&'a [u8]),
}
pub(crate) fn lex<'a>(
source: Option<&str>,
data: &'a [u8],
) -> impl Iterator<Item = Result<Token<'a>, Error>> {
Lexeme::lexer(data).spanned().map(move |l| match l {
(Err(()), range) => Err(Error {
kind: ErrorKind::Lex,
source: source.map(String::from),
span: range.clone().into(),
message: format_error_message(data, range),
}),
(Ok(Lexeme::Argument(_) | Lexeme::Reserved(_)), range) => Err(Error {
kind: ErrorKind::Lex,
source: source.map(String::from),
span: range.clone().into(),
message: format_reserved_message(data, range),
}),
(Ok(l), range) => Ok(Token {
span: range.clone().into(),
kind: Kind::from(&l),
data: &data[range],
}),
})
}
fn format_error_message(data: &[u8], range: Range<usize>) -> String {
format!(
r#"unexpected token: "{}""#,
String::from_utf8_lossy(data.get(range).unwrap_or_default())
)
}
fn format_reserved_message(data: &[u8], range: Range<usize>) -> String {
format!(
r#"reserved token: "{}""#,
String::from_utf8_lossy(data.get(range).unwrap_or_default())
)
}
#[cfg(test)]
mod test {
use super::Token;
use crate::lang::error::Error;
use crate::lang::lex::Kind;
use crate::value::span::Span;
#[test]
fn test_empty() {
assert_eq!(lex(b"").unwrap(), vec![])
}
#[test]
fn test_reserved() {
assert!(lex(b"!").is_err());
assert!(lex(b"a+b").is_err());
assert!(lex(b"a?b").is_err());
assert!(lex(b"bar%").is_err());
assert!(lex(b"$foo").is_err());
assert!(lex(b"@foo").is_err());
assert!(lex(b"f\xF3o").is_err());
}
#[test]
fn test_arguments() {
assert!(lex(b"-a").is_err());
assert!(lex(b"-a--b").is_err());
assert!(lex(b"--").is_err());
assert!(lex(b"--1").is_err());
assert!(lex(b"--a").is_err());
assert!(lex(b"---a").is_err());
assert!(lex(b"--a--b").is_err());
assert!(lex(b"---a---b").is_err());
assert!(lex(b"--foo").is_err());
assert!(lex(b"--f123").is_err());
assert!(lex(b"--!++").is_err());
assert!(!lex(b"-1").is_err());
assert!(!lex(b"a-b").is_err());
assert!(!lex(b"a-123").is_err());
assert!(!lex(b"a--b").is_err());
assert!(!lex(b"a---b").is_err());
}
#[test]
fn test_label() {
assert_eq!(
lex("æøå-ⴰⴻⵓⵍ-नमस्ते-こんにちは".as_bytes()).unwrap(),
vec![Token {
span: Span::new(0, 54),
kind: Kind::Label,
data: "æøå-ⴰⴻⵓⵍ-नमस्ते-こんにちは".as_bytes(),
}]
);
assert_eq!(
lex("0a".as_bytes()).unwrap(),
vec![
Token {
span: Span::new(0, 1),
kind: Kind::Number,
data: "0".as_bytes(),
},
Token {
span: Span::new(1, 2),
kind: Kind::Label,
data: "a".as_bytes(),
}
]
);
assert_eq!(
lex(b"x.1.2..x3").unwrap(),
vec![Token {
span: Span::new(0, 9),
kind: Kind::Label,
data: b"x.1.2..x3",
},]
);
assert_eq!(
lex(b"..a.b..c...d... .. ...").unwrap(),
vec![
Token {
span: Span::new(0, 2),
kind: Kind::DotDot,
data: b"..",
},
Token {
span: Span::new(2, 15),
kind: Kind::Label,
data: b"a.b..c...d...",
},
Token {
span: Span::new(15, 16),
kind: Kind::Space,
data: b" ",
},
Token {
span: Span::new(16, 18),
kind: Kind::DotDot,
data: b"..",
},
Token {
span: Span::new(18, 19),
kind: Kind::Space,
data: b" ",
},
Token {
span: Span::new(19, 22),
kind: Kind::DotDotDot,
data: b"...",
},
]
);
}
#[test]
fn test_nested_brackets() {
assert_eq!(
lex(b"[[[1]]]").unwrap(),
vec![
Token {
span: Span::new(0, 1),
kind: Kind::BracketStart,
data: b"[",
},
Token {
span: Span::new(1, 2),
kind: Kind::BracketStart,
data: b"[",
},
Token {
span: Span::new(2, 3),
kind: Kind::BracketStart,
data: b"[",
},
Token {
span: Span::new(3, 4),
kind: Kind::Number,
data: b"1",
},
Token {
span: Span::new(4, 5),
kind: Kind::BracketEnd,
data: b"]",
},
Token {
span: Span::new(5, 6),
kind: Kind::BracketEnd,
data: b"]",
},
Token {
span: Span::new(6, 7),
kind: Kind::BracketEnd,
data: b"]",
},
]
)
}
#[test]
fn test_angle_brackets() {
assert_eq!(
lex(b"<a>1</a>").unwrap(),
vec![
Token {
span: Span::new(0, 3),
kind: Kind::Tag,
data: b"<a>",
},
Token {
span: Span::new(3, 4),
kind: Kind::Number,
data: b"1",
},
Token {
span: Span::new(4, 8),
kind: Kind::Tag,
data: b"</a>",
},
]
)
}
#[test]
fn test_misc() {
assert_eq!(
lex(b"a | a[\"b[\"]c|a[bc].(d)|-2|'3'#a\nx;;<x>").unwrap(),
vec![
Token {
span: Span::new(0, 1),
kind: Kind::Label,
data: b"a",
},
Token {
span: Span::new(1, 2),
kind: Kind::Space,
data: b" ",
},
Token {
span: Span::new(2, 3),
kind: Kind::Pipe,
data: b"|",
},
Token {
span: Span::new(3, 6),
kind: Kind::Space,
data: b" ",
},
Token {
span: Span::new(6, 7),
kind: Kind::Label,
data: b"a",
},
Token {
span: Span::new(7, 8),
kind: Kind::BracketStart,
data: b"[",
},
Token {
span: Span::new(8, 12),
kind: Kind::StringDQ,
data: br#""b[""#,
},
Token {
span: Span::new(12, 13),
kind: Kind::BracketEnd,
data: b"]",
},
Token {
span: Span::new(13, 14),
kind: Kind::Label,
data: b"c",
},
Token {
span: Span::new(14, 15),
kind: Kind::Pipe,
data: b"|",
},
Token {
span: Span::new(15, 16),
kind: Kind::Label,
data: b"a",
},
Token {
span: Span::new(16, 17),
kind: Kind::BracketStart,
data: b"[",
},
Token {
span: Span::new(17, 19),
kind: Kind::Label,
data: b"bc",
},
Token {
span: Span::new(19, 20),
kind: Kind::BracketEnd,
data: b"]",
},
Token {
span: Span::new(20, 21),
kind: Kind::Dot,
data: b".",
},
Token {
span: Span::new(21, 22),
kind: Kind::ParenStart,
data: b"(",
},
Token {
span: Span::new(22, 23),
kind: Kind::Label,
data: b"d",
},
Token {
span: Span::new(23, 24),
kind: Kind::ParenEnd,
data: b")",
},
Token {
span: Span::new(24, 25),
kind: Kind::Pipe,
data: b"|",
},
Token {
span: Span::new(25, 27),
kind: Kind::Number,
data: b"-2",
},
Token {
span: Span::new(27, 28),
kind: Kind::Pipe,
data: b"|",
},
Token {
span: Span::new(28, 31),
kind: Kind::StringSQ,
data: br#"'3'"#,
},
Token {
span: Span::new(31, 33),
kind: Kind::Comment,
data: b"#a",
},
Token {
span: Span::new(33, 34),
kind: Kind::Space,
data: b"\n",
},
Token {
span: Span::new(34, 35),
kind: Kind::Label,
data: b"x",
},
Token {
span: Span::new(35, 36),
kind: Kind::Semicolon,
data: b";",
},
Token {
span: Span::new(36, 37),
kind: Kind::Semicolon,
data: b";",
},
Token {
span: Span::new(37, 40),
kind: Kind::Tag,
data: b"<x>",
},
]
)
}
#[track_caller]
fn lex(data: &[u8]) -> Result<Vec<Token<'_>>, Error> {
super::lex(None, data).collect::<Result<_, _>>()
}
}