diff --git a/src/build/format.rs b/src/build/format.rs index 967abe1..115c4e0 100644 --- a/src/build/format.rs +++ b/src/build/format.rs @@ -27,6 +27,25 @@ pub trait FormatRenderer { fn render(&self, pos: &Position) -> Result>; } +#[derive(Debug)] +pub enum TemplatePart<'a> { + Str(Vec), + PlaceHolder(usize), + Expression(&'a str), +} + +pub trait TemplateParser { + fn parse<'a>(&self, input: &'a str) -> Vec>; +} + +pub struct SimpleTemplate(); + +impl SimpleTemplate { + pub fn new() -> Self { + Self() + } +} + /// Implements the logic for format strings in UCG format expressions. pub struct SimpleFormatter + Clone> { tmpl: String, @@ -43,6 +62,36 @@ impl + Clone> SimpleFormatter { } } +impl TemplateParser for SimpleTemplate { + fn parse<'a>(&self, input: &'a str) -> Vec> { + let mut result = Vec::new(); + let mut count = 0; + let mut should_escape = false; + let mut buf: Vec = Vec::new(); + for c in dbg!(input).chars() { + if c == '@' && !should_escape { + result.push(TemplatePart::Str(buf)); + buf = Vec::new(); + // This is a placeholder in our template. + result.push(TemplatePart::PlaceHolder(count)); + count += 1; + } else if c == '\\' && !should_escape { + eprintln!("escaping next character"); + should_escape = true; + continue; + } else { + buf.push(c); + dbg!(&buf); + } + should_escape = false; + } + if buf.len() != 0 { + result.push(TemplatePart::Str(buf)); + } + result + } +} + impl + Clone> FormatRenderer for SimpleFormatter { /// Renders a formatter to a string or returns an error. /// @@ -50,30 +99,33 @@ impl + Clone> FormatRenderer for SimpleFormatter { /// it will return an error. Otherwise it will return the formatted string. fn render(&self, pos: &Position) -> Result> { let mut buf = String::new(); - let mut should_escape = false; let mut count = 0; - for c in self.tmpl.chars() { - if c == '@' && !should_escape { - if count == self.args.len() { - return Err(error::BuildError::with_pos( - "Too few arguments to string \ - formatter.", - error::ErrorType::FormatError, - pos.clone(), - ) - .to_boxed()); + let parser = SimpleTemplate::new(); + let parts = parser.parse(&self.tmpl); + for p in parts { + match p { + TemplatePart::PlaceHolder(idx) => { + if idx == self.args.len() { + return Err(error::BuildError::with_pos( + "Too few arguments to string \ + formatter.", + error::ErrorType::FormatError, + pos.clone(), + ) + .to_boxed()); + } + let arg = self.args[count].clone(); + let strval = arg.into(); + buf.push_str(&strval); + count += 1; } - let arg = self.args[count].clone(); - let strval = arg.into(); - buf.push_str(&strval); - count += 1; - should_escape = false; - } else if c == '\\' && !should_escape { - eprintln!("found an escape char {}", self.tmpl); - should_escape = true; - } else { - buf.push(c); - should_escape = false; + TemplatePart::Str(cs) => { + buf.reserve(cs.len()); + for c in cs { + buf.push(c); + } + } + TemplatePart::Expression(_) => unreachable!(), } } if self.args.len() != count { diff --git a/src/build/opcode/error.rs b/src/build/opcode/error.rs index 65e7308..634e0f4 100644 --- a/src/build/opcode/error.rs +++ b/src/build/opcode/error.rs @@ -24,4 +24,4 @@ where // FIXME(jwall): This should really have more information for debugging Error {} } -} \ No newline at end of file +} diff --git a/src/build/opcode/mod.rs b/src/build/opcode/mod.rs index dcf4960..8fc1eac 100644 --- a/src/build/opcode/mod.rs +++ b/src/build/opcode/mod.rs @@ -202,8 +202,7 @@ pub enum Op { // Runtime hooks Runtime(Hook), // TODO(jwall): TRACE instruction - // TODO(jwall): Format instruction - Format, + Render, } use super::ir::Val; @@ -230,7 +229,7 @@ impl TryFrom<&Value> for Val { let mut flds = Vec::new(); for &(ref k, ref v) in fs.iter() { let v = v.clone(); - // TODO(jwall): The RC for a Val should no longer be required. + // TODO(jwall): The Rc for a Val should no longer be required. flds.push((k.clone(), Rc::new(v.try_into()?))); } Val::Tuple(flds) diff --git a/src/build/opcode/test.rs b/src/build/opcode/test.rs index b6f008d..641ef8e 100644 --- a/src/build/opcode/test.rs +++ b/src/build/opcode/test.rs @@ -16,8 +16,8 @@ use std::rc::Rc; use super::scope::Stack; use super::Composite::{List, Tuple}; use super::Op::{ - Add, Bang, Bind, Cp, DeRef, Div, Element, Equal, FCall, Field, Format, Func, Index, InitList, - InitThunk, InitTuple, Jump, JumpIfFalse, JumpIfTrue, Module, Mul, Noop, Pop, Return, + Add, Bang, Bind, Cp, DeRef, Div, Element, Equal, FCall, Field, Func, Index, InitList, + InitThunk, InitTuple, Jump, JumpIfFalse, JumpIfTrue, Module, Mul, Noop, Pop, Render, Return, SelectJump, Sub, Sym, Typ, Val, }; use super::Primitive::{Bool, Empty, Float, Int, Str}; @@ -634,16 +634,43 @@ fn simple_not_expr() { } #[test] -fn simple_format_expr() { +fn simple_render_operation() { assert_cases![ vec![ - InitList, Val(Int(1)), - Element, - Val(Int(2)), - Element, - Val(Str("@@".to_owned())), - Format, - ] => P(Str("12".to_owned())), + Render, + ] => P(Str("1".to_owned())), + vec![ + Val(Float(1.1)), + Render, + ] => P(Str("1.1".to_owned())), + vec![ + Val(Bool(true)), + Render, + ] => P(Str("true".to_owned())), + vec![ + Val(Bool(false)), + Render, + ] => P(Str("false".to_owned())), + vec![ + Val(Empty), + Render, + ] => P(Str("NULL".to_owned())), + vec![ + Val(Str("foo".to_owned())), + Render, + ] => P(Str("foo".to_owned())), + ]; +} + +#[test] +fn simple_format_expressions() { + assert_parse_cases![ + "\"@\" % (1);" => P(Str("1".to_owned())), + "\"@\" % (1.1);" => P(Str("1.1".to_owned())), + "\"@\" % (\"foo\");" => P(Str("foo".to_owned())), + "\"@\" % (NULL);" => P(Str("NULL".to_owned())), + "\"@ @ @\" % (1, 2, 3);" => P(Str("1 2 3".to_owned())), + "\"@ \\\\@\" % (1);" => P(Str("1 @".to_owned())), ]; } diff --git a/src/build/opcode/translate.rs b/src/build/opcode/translate.rs index b9eff95..c040d8a 100644 --- a/src/build/opcode/translate.rs +++ b/src/build/opcode/translate.rs @@ -11,7 +11,11 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use crate::ast::{BinaryExprType, Expression, Statement, Value, FormatArgs}; +use crate::ast::Position; +use crate::ast::{BinaryExprType, Expression, FormatArgs, Statement, Value}; +use crate::build::format::{ + ExpressionFormatter, FormatRenderer, SimpleTemplate, TemplateParser, TemplatePart, +}; use crate::build::opcode::Primitive; use crate::build::opcode::Value::{C, F, M, P, T}; use crate::build::opcode::{Hook, Op}; @@ -169,22 +173,33 @@ impl AST { } Expression::Fail(_) => unimplemented!("Fail expressions are not implmented yet"), Expression::Format(def) => { + // TODO(jwall): It would actually be safer if this was happening + // when we create the format def instead of here. match def.args { - FormatArgs::List(elems) => { - ops.push(Op::InitList); - for e in elems { - Self::translate_expr(e, &mut ops); - ops.push(Op::Element); + FormatArgs::List(mut elems) => { + let formatter = SimpleTemplate::new(); + let mut parts = dbg!(formatter.parse(&def.template)); + // We need to push process these in reverse order for the + // vm to process things correctly; + elems.reverse(); + parts.reverse(); + let mut elems_iter = elems.drain(0..); + let mut parts_iter = parts.drain(0..); + Self::translate_template_part( + parts_iter.next().unwrap(), + &mut elems_iter, + &mut ops, + true, + ); + for p in parts_iter { + Self::translate_template_part(p, &mut elems_iter, &mut ops, true); + ops.push(Op::Add); } } FormatArgs::Single(e) => { - ops.push(Op::InitList); - Self::translate_expr(*e, &mut ops); - ops.push(Op::Element); + // TODO(jwall): Use expression formatter here. } } - ops.push(Op::Val(Primitive::Str(def.template))); - ops.push(Op::Format); } Expression::Func(_) => unimplemented!("Func expressions are not implmented yet"), Expression::FuncOp(_) => unimplemented!("FuncOp expressions are not implmented yet"), @@ -203,6 +218,36 @@ impl AST { } } + fn translate_template_part>( + part: TemplatePart, + elems: &mut EI, + mut ops: &mut Vec, + place_holder: bool, + ) { + match part { + TemplatePart::Str(s) => { + ops.push(Op::Val(Primitive::Str(s.into_iter().collect()))); + } + TemplatePart::PlaceHolder(_idx) => { + if !place_holder { + // In theory this should never be reachable + unreachable!(); + } else { + Self::translate_expr(dbg!(elems.next().unwrap()), &mut ops); + ops.push(Op::Render); + } + } + TemplatePart::Expression(_expr) => { + // TODO(jwall): We need to parse this. + if place_holder { + unreachable!(); + } else { + unimplemented!("Expression Formatters are unimmplemented"); + } + } + } + } + fn translate_value(value: Value, mut ops: &mut Vec) { match value { Value::Int(i) => ops.push(Op::Val(Primitive::Int(i.val))), diff --git a/src/build/opcode/vm.rs b/src/build/opcode/vm.rs index 9a26ae1..2c2ee8d 100644 --- a/src/build/opcode/vm.rs +++ b/src/build/opcode/vm.rs @@ -24,8 +24,6 @@ use super::Primitive::{Bool, Empty, Float, Int, Str}; use super::Value::{C, F, M, P, S, T}; use super::{Error, Op, Primitive, Value}; use super::{Func, Module}; -use crate::ast::Position; -use crate::build::format::{ExpressionFormatter, FormatRenderer, SimpleFormatter}; pub struct VM { stack: Vec>, @@ -124,7 +122,7 @@ impl<'a> VM { } Op::Typ => self.op_typ()?, Op::Runtime(h) => self.op_runtime(h)?, - Op::Format => self.op_format()?, + Op::Render => self.op_render()?, }; } Ok(()) @@ -722,27 +720,9 @@ impl<'a> VM { .handle(&self.path, h, &mut self.stack) } - fn op_format(&mut self) -> Result<(), Error> { - let template = self.pop()?; - let template = match template.as_ref() { - &P(Str(ref s)) => s.clone(), - _ => return Err(dbg!(Error {})), - }; - let args = self.pop()?; - let args: Vec<&Value> = match args.as_ref() { - &C(List(ref elems)) => elems.iter().map(|v| v.as_ref()).collect(), - _ => return Err(dbg!(Error {})), - }; - let f = if args.len() == 1 { - unimplemented!("Expression formatters are not implemented yet"); - } else { - SimpleFormatter::new(template, args) - }; - // TODO(jwall): Position should be valid here. - match dbg!(f.render(&Position::new(0, 0, 0))) { - Err(_e) => return Err(dbg!(Error {})), - Ok(s) => self.push(Rc::new(P(Str(s))))?, - } + fn op_render(&mut self) -> Result<(), Error> { + let val = self.pop()?; + self.push(Rc::new(P(Str(val.as_ref().into()))))?; Ok(()) } }