diff --git a/src/main.rs b/src/main.rs index 0316b42..e4170fa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -58,12 +58,7 @@ use crate::base::*; pub fn main() { let src = " -fn foo { - \"this is a docstring\" - () -> :foo - (_) -> :bar -} -doc (foo) +\"{} {foobar}\" "; let (tokens, lex_errs) = lexer().parse(src).into_output_errors(); if !lex_errs.is_empty() { @@ -78,13 +73,13 @@ doc (foo) let (ast, _) = parser() .parse(Stream::from_iter(to_parse).map((0..src.len()).into(), |(t, s)| (t, s))) .unwrap(); - // println!("{}", ast); + println!("{}", ast); - let mut ctx = base(); + // let mut ctx = base(); - let result = eval(&ast, &mut ctx).unwrap(); + // let result = eval(&ast, &mut ctx).unwrap(); - println!("{}", result); + // println!("{}", result); // struct_scalpel::print_dissection_info::() // struct_scalpel::print_dissection_info::(); diff --git a/src/parser.rs b/src/parser.rs index 97fb05c..c009eb2 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -34,16 +34,18 @@ impl<'src> fmt::Display for MatchClause<'src> { } #[derive(Clone, Debug, PartialEq, Eq)] -pub enum StringPart<'src> { - Data(&'src str), - Word(&'src str), +pub enum StringPart { + Data(String), + Word(String), + Inline(String), } -impl<'src> fmt::Display for StringPart<'src> { - fn fmt(self: &StringPart<'src>, f: &mut fmt::Formatter) -> fmt::Result { +impl fmt::Display for StringPart { + fn fmt(self: &StringPart, f: &mut fmt::Formatter) -> fmt::Result { let rep = match self { - StringPart::Data(s) => format!("{{{s}}}"), - StringPart::Word(s) => s.to_string(), + StringPart::Word(s) => format!("{{{s}}}"), + StringPart::Data(s) => s.to_string(), + StringPart::Inline(s) => s.to_string(), }; write!(f, "{}", rep) } @@ -59,7 +61,7 @@ pub enum Ast<'src> { Keyword(&'src str), Word(&'src str), String(&'src str), - Interpolated(Vec>>), + Interpolated(Vec>), Block(Vec>), If(Box>, Box>, Box>), Tuple(Vec>), @@ -91,7 +93,7 @@ impl fmt::Display for Ast<'_> { Ast::Interpolated(strs) => { write!( f, - "String: \"{}\"", + "Interpolated: \"{}\"", strs.iter() .map(|(s, _)| s.to_string()) .collect::>() @@ -251,6 +253,7 @@ pub enum Pattern<'src> { Boolean(bool), Number(f64), String(&'src str), + Interpolated(Vec), Keyword(&'src str), Word(&'src str), As(&'src str, &'src str), @@ -300,10 +303,26 @@ impl fmt::Display for Pattern<'_> { .join(", ") ), Pattern::Pair(key, value) => write!(f, ":{} {}", key, value.0), + Pattern::Interpolated(strprts) => write!( + f, + "\"{}\"", + strprts + .iter() + .map(|part| part.to_string()) + .collect::>() + .join("") + ), } } } +fn is_word_char(c: char) -> bool { + if c.is_ascii_alphanumeric() { + return true; + }; + matches!(c, '_' | '/' | '?' | '!') +} + // TODO: write this // 1. we need an enum for a return type // either a string part or a word part @@ -311,8 +330,81 @@ impl fmt::Display for Pattern<'_> { // 3. this should loop through the string and allow for escaping braces // consider using Rust-style escapes: {{}}, rather than \{\} // {{{foo}}} -pub fn parse_string<'src>(s: &'src str) -> Vec> { - vec![] +fn parse_string(s: &str, span: SimpleSpan) -> Result>, String> { + let mut parts = vec![]; + let mut current_part = String::new(); + let mut start = span.start; + let mut is_word = false; + + let mut chars = s.char_indices(); + while let Some((i, char)) = chars.next() { + match char { + '{' => { + if is_word { + return Err("interpolations must only contain words".to_string()); + }; + match chars.next() { + None => return Err("unclosed brace".to_string()), + Some((_, '{')) => current_part.push('{'), + Some((i, c)) => { + if !current_part.is_empty() { + parts.push(( + StringPart::Data(current_part), + SimpleSpan::new(start, start + i), + )); + }; + current_part = String::new(); + start = i; + is_word = true; + if c.is_ascii_lowercase() { + current_part.push(c); + } else { + return Err("interpolations must only contain words".to_string()); + } + } + } + } + '}' => { + if is_word { + parts.push(( + StringPart::Word(current_part), + SimpleSpan::new(start, start + i), + )); + current_part = String::new(); + start = i; + is_word = false; + } else { + match chars.next() { + None => return Err("unclosed brace".to_string()), + Some((_, c)) => current_part.push(c), + } + } + } + _ => { + if is_word { + if is_word_char(char) { + current_part.push(char) + } else { + return Err("interpolations must only contain words".to_string()); + } + } else { + current_part.push(char) + } + } + } + } + + parts.push(( + if is_word { + StringPart::Word(current_part) + } else if current_part == s.to_string() { + StringPart::Inline(current_part) + } else { + StringPart::Data(current_part) + }, + SimpleSpan::new(start, span.end), + )); + Ok(parts) } pub fn parser<'src, I>( @@ -448,7 +540,16 @@ where } .map_with(|v, e| (v, e.span())); - let string = select! {Token::String(s) => Ast::String(s)}.map_with(|s, e| (s, e.span())); + let string = select! {Token::String(s) => s}.try_map_with(|s, e| { + let parsed = parse_string(s, e.span()); + match parsed { + Ok(parts) => match parts[0] { + (StringPart::Inline(_), _) => Ok((Ast::String(s), e.span())), + _ => Ok((Ast::Interpolated(parts), e.span())), + }, + Err(msg) => Err(Rich::custom(e.span(), msg)), + } + }); let tuple = simple .clone()