1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
//! Building blocks for expressions, statements, and declarations
use nom::{
IResult,
branch::alt,
bytes::complete::{tag, take_until, take_while1},
character::{self, complete::satisfy},
combinator::{map, not},
number,
sequence::delimited,
};
use crate::ast::{Identifier, IntegerLiteral, FloatLiteral, StringLiteral};
/// Parser builder for making keyword parsers
///
/// `keyword` is not a parser on its own. Instead it can be called to create and
/// return parsers that can be used to match keywords.
/// # Example:
/// ```rust
/// # use lualite::parser::atomic::keyword;
/// let function_kw_parser = keyword("function");
/// let while_kw_parser = keyword("while");
///
/// assert!(function_kw_parser("function f(x) ...").is_ok()); // succeeds, "function" is matched
/// assert!(while_kw_parser("function f(x) ...").is_err()); // fails, "while" not matched
/// assert!(function_kw_parser("x + 1").is_err()); // fails, "function" not matched
/// ```
/// # Lifetime:
/// **Note**: The lifetime of the resulting parsers are bound by the `'kw` lifetime of the
/// input string.
/// ```rust compile_fail
/// # use lualite::parser::atomic::keyword;
/// let test_parser = {
/// let test_kw: String = "test".to_owned(); // test_kw is dropped at the end of this block
/// keyword(&test_kw) // Borrow check error here: the returned parser would outlive `test_kw`
/// };
/// test_parser("test");
/// ```
pub fn keyword<'kw>(kw: &'kw str) -> impl Fn(&str) -> IResult<&str, &str> + 'kw {
move |s| {
let (remaining, matched) = word(s)?;
if matched == kw {
Ok((remaining, matched))
} else {
Err(nom::Err::Error(nom::error::Error { input: s, code: nom::error::ErrorKind::Tag }))
}
}
}
/// Matches any keyword
///
/// # Example:
/// ```rust
/// # use lualite::parser::atomic::any_keyword;
/// assert!(any_keyword("if").is_ok()); // succeeds
/// assert!(any_keyword("hello").is_err()); // fails, "hello" is not a keyword
/// assert!(any_keyword("1234").is_err()); // fails, 1234 is not a word
/// ```
pub fn any_keyword(s: &str) -> IResult<&str, &str> {
alt((
keyword("end"), keyword("function"), keyword("return"), keyword("if"), keyword("then"),
keyword("elseif"), keyword("else"), keyword("while"), keyword("do"), keyword("for"),
keyword("in"), keyword("nil"), keyword("and"), keyword("or"), keyword("not"),
keyword("true"), keyword("false")
))(s)
}
/// Parses a string consisting of only letters, numbers, and underscores
///
/// First character cannot be a digit.
pub fn word(s: &str) -> IResult<&str, &str> {
satisfy(|c: char| c.is_alphabetic() || c == '_')(s)?;
take_while1(|c: char| c.is_alphanumeric() || c == '_')(s)
}
/// Parser for identifiers
///
/// Ensures the first character is not a digit and the identifier is not a keyword.
/// # Example:
/// ```rust
/// # use lualite::parser::atomic::identifier;
/// assert!(identifier("x").is_ok()); // succeeds
/// assert!(identifier("foo_bar3").is_ok()); // succeeds
/// assert!(identifier("_unused_var").is_ok()); // succeeds
/// assert!(identifier("while").is_err()); // fails (while is a keyword)
/// assert!(identifier("7hello").is_err()); // fails (treated as integer literal)
/// ```
pub fn identifier(s: &str) -> IResult<&str, Identifier> {
not(any_keyword)(s)?;
map(word, |ident: &str| Identifier(ident.to_owned()))(s)
}
/// Parser for signed 64-bit integer literals
///
/// # Example:
/// ```rust
/// # use lualite::parser::atomic::integer;
/// use lualite::ast::IntegerLiteral;
///
/// assert_eq!(integer("537"), Ok(("", IntegerLiteral(537_i64))));
/// assert!(integer("abcd").is_err());
/// assert_eq!(integer("-11"), Ok(("", IntegerLiteral(-11_i64))));
/// ```
pub fn integer(s: &str) -> IResult<&str, IntegerLiteral> {
map(character::complete::i64, |int| IntegerLiteral(int))(s)
}
/// Parser for double-precision floating-point literals
///
/// Input must contain a `.` for this parser to succeed. Doing so prevents it from
/// matching integers.
pub fn float(s: &str) -> IResult<&str, FloatLiteral> {
let (_remaining, flt_str) = number::complete::recognize_float(s)?;
// without this check, float would match integers as well
if flt_str.contains('.') {
map(number::complete::double, |flt| FloatLiteral(flt))(s)
} else {
Err(nom::Err::Error(nom::error::Error { input: s, code: nom::error::ErrorKind::Float }))
}
}
/// Parser for string literals
pub fn string(s: &str) -> IResult<&str, StringLiteral> {
map(delimited(tag("\""), take_until("\""), tag("\"")), |string: &str| StringLiteral(string.to_owned()))(s)
}