Skip to main content

wasm_wave/
lex.rs

1//! Lexing types
2
3use core::fmt::Display;
4
5pub use logos::Span;
6
7/// A WAVE `logos::Lexer`
8pub type Lexer<'source> = logos::Lexer<'source, Token>;
9
10/// A WAVE token
11#[derive(Clone, Copy, Debug, PartialEq, Eq, logos::Logos)]
12#[logos(error = Option<Span>)]
13#[logos(skip r"[ \t\n\r]+")]
14#[logos(skip(r"//", skip_comment))]
15#[logos(subpattern first_label_word = r"[a-z][a-z0-9]*|[A-Z][A-Z0-9]*")]
16#[logos(subpattern label_word = r"[a-z0-9]+|[A-Z0-9]+")]
17#[logos(subpattern char_escape = r#"\\['"tnr\\]|\\u\{[0-9a-fA-F]{1,6}\}"#)]
18pub enum Token {
19    /// The `{` symbol
20    #[token("{")]
21    BraceOpen,
22    /// The `}` symbol
23    #[token("}")]
24    BraceClose,
25
26    /// The `(` symbol
27    #[token("(")]
28    ParenOpen,
29    /// The `)` symbol
30    #[token(")")]
31    ParenClose,
32
33    /// The `[` symbol
34    #[token("[")]
35    BracketOpen,
36    /// The `]` symbol
37    #[token("]")]
38    BracketClose,
39
40    /// The `:` symbol
41    #[token(":")]
42    Colon,
43
44    /// The `,` symbol
45    #[token(",")]
46    Comma,
47
48    /// A number literal
49    #[regex(r"-?(0|([1-9][0-9]*))(\.[0-9]+)?([eE][-+]?[0-9]+)?")]
50    #[token("-inf")]
51    Number,
52
53    /// A label or keyword
54    #[regex(r"%?(?&first_label_word)(-(?&label_word))*")]
55    LabelOrKeyword,
56
57    /// A char literal
58    #[regex(r#"'([^\\'\n]{1,4}|(?&char_escape))'"#, validate_char)]
59    Char,
60
61    /// A string literal
62    #[regex(r#""([^\\"\n]|(?&char_escape))*""#)]
63    String,
64
65    /// A multi-line string literal
66    #[token(r#"""""#, lex_multiline_string)]
67    MultilineString,
68}
69
70impl Display for Token {
71    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
72        write!(f, "{self:?}")
73    }
74}
75
76fn skip_comment(lex: &mut Lexer) {
77    let comment = lex.remainder().split_inclusive('\n').next().unwrap();
78    lex.bump(comment.len());
79}
80
81fn validate_char(lex: &mut Lexer) -> Result<(), Option<Span>> {
82    let s = &lex.slice()[1..lex.slice().len() - 1];
83    if s.starts_with('\\') || s.chars().count() == 1 {
84        Ok(())
85    } else {
86        Err(Some(lex.span()))
87    }
88}
89
90fn lex_multiline_string(lex: &mut Lexer) -> bool {
91    if let Some(end) = lex.remainder().find(r#"""""#) {
92        lex.bump(end + 3);
93        true
94    } else {
95        false
96    }
97}
98
99/// A WAVE keyword
100#[derive(Clone, Copy, Debug, PartialEq)]
101#[allow(missing_docs)]
102pub enum Keyword {
103    True,
104    False,
105    Some,
106    None,
107    Ok,
108    Err,
109    Inf,
110    Nan,
111}
112
113impl Keyword {
114    /// Returns any keyword exactly matching the given string.
115    pub fn decode(raw_label: &str) -> Option<Self> {
116        Some(match raw_label {
117            "true" => Self::True,
118            "false" => Self::False,
119            "some" => Self::Some,
120            "none" => Self::None,
121            "ok" => Self::Ok,
122            "err" => Self::Err,
123            "inf" => Self::Inf,
124            "nan" => Self::Nan,
125            _ => return None,
126        })
127    }
128}
129
130impl Display for Keyword {
131    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
132        f.write_str(match self {
133            Keyword::True => "true",
134            Keyword::False => "false",
135            Keyword::Some => "some",
136            Keyword::None => "none",
137            Keyword::Ok => "ok",
138            Keyword::Err => "err",
139            Keyword::Inf => "inf",
140            Keyword::Nan => "nan",
141        })
142    }
143}
144
145/// A lexer for function names. Different token type from WAVE values, because
146/// this is not part of the same context-free grammar.
147pub type FuncNameLexer<'source> = logos::Lexer<'source, FuncNameToken>;
148/// A function name token
149#[derive(Clone, Copy, Debug, PartialEq, Eq, logos::Logos)]
150#[logos(error = Option<logos::Span>)]
151#[logos(subpattern first_label_word = r"[a-z][a-z0-9]*|[A-Z][A-Z0-9]*")]
152#[logos(subpattern label_word = r"[a-z0-9]+|[A-Z0-9]+")]
153#[logos(subpattern label = r"(?&first_label_word)(\-(?&label_word))*")]
154#[logos(subpattern package_label = r"(?&label)\:(?&label)")]
155#[logos(subpattern semver = r"(0|([1-9][0-9]*))\.(0|([1-9][0-9]*))\.(0|([1-9][0-9]*))")]
156pub enum FuncNameToken {
157    /// A function inside an optional interface, with an optional package qualifier and
158    /// package version suffix
159    #[regex(r"((?&package_label)\/)?(?&label)(\.(?&label))?(\@(?&semver))?")]
160    FunctionName,
161}