diff --git a/Cargo.lock b/Cargo.lock index 52e93358..28bd6386 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -356,6 +356,19 @@ dependencies = [ "xparser", ] +[[package]] +name = "interpolate" +version = "0.1.0" +dependencies = [ + "ast", + "error", + "location", + "nom 7.1.1", + "nom_locate", + "symbol", + "xparser", +] + [[package]] name = "jinko" version = "0.3.0-jinx7" @@ -372,6 +385,7 @@ dependencies = [ "fire", "flatten", "include_code", + "interpolate", "lazy_static", "libc", "libffi", diff --git a/Cargo.toml b/Cargo.toml index 5549db76..5ef3c9d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ "location", "symbol", "ast", + "interpolate", "fir", "flatten", "name_resolve", @@ -33,6 +34,7 @@ members = [ [dependencies] ast = { path = "ast" } ast-sanitizer = { path = "ast-sanitizer" } +interpolate = { path = "interpolate" } fir = { path = "fir" } debug-fir = { path = "debug-fir" } flatten = { path = "flatten" } diff --git a/interpolate/Cargo.toml b/interpolate/Cargo.toml new file mode 100644 index 00000000..ce303d48 --- /dev/null +++ b/interpolate/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "interpolate" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ast = { path = "../ast" } +error = { path = "../error" } +symbol = { path = "../symbol" } +location = { path = "../location" } +xparser = { path = "../xparser" } +nom = "7.0" +nom_locate = "4.0" diff --git a/interpolate/src/lib.rs b/interpolate/src/lib.rs new file mode 100644 index 00000000..c68fb6a6 --- /dev/null +++ b/interpolate/src/lib.rs @@ -0,0 +1,186 @@ +use ast::{Ast, Node, Value, Visitor}; +use error::{ErrKind, Error}; +use location::{Location, Source, SpanTuple}; +use nom::multi::many0; +use xparser::{Error as ParseError, ParseInput, ParseResult}; + +use nom::character::complete::char; +use nom::{bytes::complete::take_until, combinator::opt}; +use nom_locate::{position, LocatedSpan}; + +struct Ctx; + +fn parse_expr(input: ParseInput) -> ParseResult { + let (input, _) = char('{')(input)?; + let (input, expr) = xparser::expr(input)?; + let (input, _) = char('}')(input)?; + + Ok((input, expr)) +} + +fn parse_str(input: ParseInput) -> ParseResult { + // how to handle reaching the end of the string? + take_until("{")(input) +} + +pub(crate) fn pos_to_loc( + input: ParseInput, + start: impl Into, + end: impl Into, +) -> SpanTuple { + SpanTuple::with_source_ref(input.extra, start.into(), end.into()) +} + +fn parse_inner(input: ParseInput) -> ParseResult { + let (input, start) = position(input)?; + let (input, s) = opt(parse_str)(input)?; + let (input, end) = position(input)?; + if let Some(s) = s { + return Ok(( + input, + Ast { + location: pos_to_loc(input, start, end), + node: Node::Constant(Value::Str(s.fragment().to_string())), + }, + )); + } + + let (input, s) = opt(parse_expr)(input)?; + let (input, end) = position(input)?; + if let Some(expr) = s { + return Ok((input, expr)); + } + + let loc = pos_to_loc(input, start, end); + + Error::new(ErrKind::Parsing) + .with_msg(format!("unexpected character in format string: {input}")) + .with_loc(Some(loc)) + .emit(); + + Err(nom::Err::Error(ParseError)) +} + +/// This function parses the following grammar: `double_quote ( .* ( '{' expr '}' )* )* double_quote` +fn parse_format_string(to_parse: &str) -> Result, Error> { + let input = LocatedSpan::new_extra(to_parse, Source::Input(to_parse)); + + let res = many0(parse_inner)(input); + + if let Ok((_, exprs)) = res { + Ok(exprs) + } else { + Err(Error::new(ErrKind::Parsing)) + } +} + +impl Visitor for Ctx { + fn visit_constant(&mut self, location: SpanTuple, value: Value) -> Result { + let s = match value { + Value::Str(s) => s, + any_const => { + return Ok(Ast { + location, + node: Node::Constant(any_const), + }) + } + }; + + Error::new(ErrKind::Hint) + .with_loc(Some(location.clone())) + .with_msg(format!("saw a string: {s}!")) + .emit_debug(); + + let exprs = parse_format_string(&s)?; + dbg!(exprs); + + Ok(Ast { + location, + node: Node::Constant(Value::Str(s)), + }) + } +} + +pub trait Interpolator: Sized { + fn interpolate(self) -> Result; +} + +impl Interpolator for Ast { + fn interpolate(self) -> Result { + Ctx.visit(self) + } +} + +pub fn add(left: usize, right: usize) -> usize { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + use ast::{Node::*, Operator, Value::*}; + + macro_rules! loc { + () => { + location::SpanTuple::with_source( + location::SourceOwned::Empty, + location::Location::new(1, 0), + location::Location::new(1, 0), + ) + }; + } + + macro_rules! ast { + ($n:expr) => { + Ast { + location: loc!(), + node: $n, + } + }; + } + + #[test] + fn parse_one() { + let s = "hello"; + + let expected = ast! { + Constant(Str(String::from("hello"))) + }; + + assert_eq!(parse_format_string(s).unwrap()[0].node, expected.node) + } + + #[test] + fn parse_one_expr() { + let s = "{15}"; + + let expected = ast! { + Constant(Integer(15)) + }; + + assert_eq!(parse_format_string(s).unwrap()[0].node, expected.node) + } + + #[test] + fn parse_one_expr_one_string() { + let s = "hello {15 + 4}"; + + let expected_s = ast! { + Constant(Str(String::from("hello "))) + }; + let expected_expr = ast! { + BinaryOp( + Operator::Add, + Box::new(ast! { + Constant(Integer(15)) + }), + Box::new(ast! { + Constant(Integer(4)) + }), + ) + }; + + assert_eq!(parse_format_string(s).unwrap()[0].node, expected_s.node); + assert_eq!(parse_format_string(s).unwrap()[1].node, expected_expr.node); + } +} diff --git a/interpreter/jinko.rs b/interpreter/jinko.rs index 92d07e75..6adfebac 100644 --- a/interpreter/jinko.rs +++ b/interpreter/jinko.rs @@ -11,6 +11,7 @@ use fire::instance::Instance; use fire::Interpret; use flatten::{FlattenAst, FlattenData}; use include_code::IncludeCode; +use interpolate::Interpolator; use loop_desugar::DesugarLoops; use name_resolve::NameResolve; @@ -123,6 +124,7 @@ fn experimental_pipeline(input: &str, file: &Path) -> InteractResult { let ast = x_try!(ast.desugar_loops()); let ast = x_try!(ast_sanitizer::only_while_loops(ast)); + let ast = x_try!(ast.interpolate()); let ast = x_try!(ast.resolve_includes()); let ast = x_try!(ast_sanitizer::no_incl(ast)); diff --git a/xparser/src/lib.rs b/xparser/src/lib.rs index 819be3fb..70e396c2 100644 --- a/xparser/src/lib.rs +++ b/xparser/src/lib.rs @@ -1,6 +1,9 @@ mod constructs; mod tokens; +pub use constructs::*; +pub use tokens::*; + use ast::Ast; use location::Source;