diff --git a/grimoire/src/parser/span.rs b/grimoire/src/parser/span.rs index ae08a5fff924cca8609d0eddf086aba0ded5dbe1..74a74c1a52af32665b797512a6955ed27c6c2c3a 100644 --- a/grimoire/src/parser/span.rs +++ b/grimoire/src/parser/span.rs @@ -1,5 +1,5 @@ use crate::ast::Location; -use nom::{Compare, Input, Offset as _}; +use nom::{Compare, FindSubstring, Input, Offset as _}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Span<'a> { @@ -135,3 +135,9 @@ impl Compare<&str> for Span<'_> { self.fragment().compare_no_case(t) } } + +impl FindSubstring<&str> for Span<'_> { + fn find_substring(&self, substr: &str) -> Option<usize> { + todo!() + } +} diff --git a/grimoire/src/parser/utils.rs b/grimoire/src/parser/utils.rs index ee5d028502b489cae41a4fdd7c6aa13f7075ed89..e528b9c3e93db3cd56cfb11645c6ee59c5a6b4a0 100644 --- a/grimoire/src/parser/utils.rs +++ b/grimoire/src/parser/utils.rs @@ -4,8 +4,14 @@ use crate::{ }; use nom::{ Parser, - bytes::complete::tag, - character::complete::{multispace0, multispace1}, + branch::alt, + bytes::{ + complete::{tag, take_while_m_n}, + take_until, + }, + character::complete::{char, digit1, multispace0, multispace1}, + combinator::{peek, value, verify}, + multi::fold, sequence::{delimited, preceded}, }; @@ -45,18 +51,118 @@ pub fn keyword<'a>( delimited(multispace1, tag(ident), multispace1) } -pub fn float(s: Span) -> ParserResult<f32> { - todo!() +pub fn float<'a>(s: Span<'a>) -> ParserResult<'a, f32> { + digit1 + .map_res(|s: Span<'a>| str::parse::<f32>(s.fragment())) + .parse(s) } -pub fn string(s: Span) -> ParserResult<String> { +pub fn identifier(s: Span) -> ParserResult<String> { todo!() } -pub fn identifier(s: Span) -> ParserResult<String> { - todo!() +pub fn number<'a>(s: Span<'a>) -> ParserResult<'a, i32> { + digit1 + .map_res(|s: Span<'a>| str::parse::<i32>(s.fragment())) + .parse(s) } -pub fn number(s: Span) -> ParserResult<i32> { - todo!() +pub fn string(span: Span) -> ParserResult<String> { + #[derive(Clone, Copy)] + enum StringFragment<'a> { + Literal(&'a str), + EscapedChar(char), + EscapedWhiteSpaces, + } + + #[derive(Clone, Copy)] + enum StringDelimiterQuote { + Single, + Double, + TripleSingle, + TripleDouble, + } + + impl StringDelimiterQuote { + fn as_str(&self) -> &'static str { + match self { + StringDelimiterQuote::Double => "\"", + StringDelimiterQuote::Single => "'", + StringDelimiterQuote::TripleSingle => "'''", + StringDelimiterQuote::TripleDouble => "\"\"\"", + } + } + + fn get_parser<'a>(self) -> impl Parser<Span<'a>, Output = Self, Error = ParserError> { + value(self, tag(self.as_str())) + } + } + + let span = multispace0(span)?.0; + let delimiter = peek(alt(( + StringDelimiterQuote::Double.get_parser(), + StringDelimiterQuote::Single.get_parser(), + StringDelimiterQuote::TripleSingle.get_parser(), + StringDelimiterQuote::TripleDouble.get_parser(), + ))) + .parse(span)? + .1; + + // Parse an escaped character: \n, \t, \r, \u{00AC}, etc. + let parse_unicode = delimited( + tag("u{"), + take_while_m_n(1, 6, |c: char| c.is_ascii_hexdigit()), + char('}'), + ) + .map(Span::into_fragment) + .map_res(move |hex: &str| u32::from_str_radix(hex, 16)) + .map_opt(std::char::from_u32); + + let parse_escaped_char = preceded( + char('\\'), + alt(( + parse_unicode, + // The `value` parser returns a fixed value (the first argument) if it parser (the + // second argument) succeeds. + value('\n', char('n')), + value('\r', char('r')), + value('\t', char('t')), + value('\u{08}', char('b')), + value('\u{0C}', char('f')), + value('\\', char('\\')), + value('/', char('/')), + value('"', char('"')), + value('\'', char('\'')), + )), + ); + + let parse_fragment = alt(( + // Take until the end of the string + verify(take_until(delimiter.as_str()), |s: &Span| !s.is_empty()) + .map(Span::into_fragment) + .map(StringFragment::Literal), + // Be aware of escaped characters + parse_escaped_char.map(StringFragment::EscapedChar), + // We handle escaped whitespaces + value( + StringFragment::EscapedWhiteSpaces, + preceded(char('\\'), multispace1), + ), + )); + + let from_fragments = |mut string: String, fragment| { + match fragment { + StringFragment::Literal(s) => string.push_str(s), + StringFragment::EscapedChar(c) => string.push(c), + StringFragment::EscapedWhiteSpaces => {} + } + string + }; + + delimited( + tag(delimiter.as_str()), + fold(0.., parse_fragment, String::default, from_fragments), + tag(delimiter.as_str()), + ) + .parse(span) }