// Copyright 2017 Jeremy Wall // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // 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. //! The format string logic for ucg format expressions. use std::cell::RefCell; use std::clone::Clone; use std::error::Error; use std::str::Chars; use abortable_parser::iter::SliceIter; use abortable_parser::Result as ParseResult; use crate::ast::*; use crate::build::assets; use crate::build::{FileBuilder, Val}; use crate::error; use crate::iter; use crate::parse; use crate::tokenizer; pub trait FormatRenderer { fn render(&self, pos: &Position) -> Result>; } pub type TemplateResult = Result, Box>; pub trait TemplateParser { fn parse(&self, input: &str) -> TemplateResult; } 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, args: Vec, } impl + Clone> SimpleFormatter { /// Constructs a Formatter with a template and args. pub fn new>(tmpl: S, args: Vec) -> Self { SimpleFormatter { tmpl: tmpl.into(), args: args, } } } impl TemplateParser for SimpleTemplate { fn parse(&self, input: &str) -> TemplateResult { 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 { should_escape = true; continue; } else { buf.push(c); dbg!(&buf); } should_escape = false; } if buf.len() != 0 { result.push(TemplatePart::Str(buf)); } Ok(result) } } impl + Clone> FormatRenderer for SimpleFormatter { /// Renders a formatter to a string or returns an error. /// /// If the formatter has the wrong number of arguments for the number of replacements /// 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 count = 0; 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; } TemplatePart::Str(cs) => { buf.reserve(cs.len()); for c in cs { buf.push(c); } } TemplatePart::Expression(_) => unreachable!(), } } if self.args.len() != count { return Err(error::BuildError::with_pos( format!( "Too many arguments to string \ formatter. Expected {} got {}", count, self.args.len() ), error::ErrorType::FormatError, pos.clone(), ) .to_boxed()); } return Ok(buf); } } pub struct ExpressionTemplate(); impl ExpressionTemplate { pub fn new() -> Self { ExpressionTemplate() } fn consume_expr(&self, iter: &mut Chars) -> Result> { let mut result = String::new(); let mut brace_count = 0; loop { let c = match iter.next() { Some(c) => c, None => break, }; if c == '{' { brace_count += 1; // We ignore the starting brace if brace_count == 1 { continue; } } if c == '}' { brace_count -= 1; // We ignore the closing brace if brace_count == 0 { continue; } } if brace_count == 0 { break; } result.push(c); } let str_iter = iter::OffsetStrIter::new(&result); let toks = match tokenizer::tokenize(str_iter, None) { Ok(toks) => toks, Err(_e) => panic!("TODO(jwall): make this not a thing"), }; let i = SliceIter::new(&toks); match parse::expression(i) { ParseResult::Complete(_, expr) => Ok(expr), ParseResult::Abort(e) | ParseResult::Fail(e) => { panic!("TODO(jwall): make this not a thing") } ParseResult::Incomplete(_ei) => panic!("TODO(jwall): make this not a thing"), } } } impl TemplateParser for ExpressionTemplate { fn parse(&self, input: &str) -> TemplateResult { let mut parts = Vec::new(); let mut should_escape = false; let mut iter = input.chars(); let mut buf: Vec = Vec::new(); loop { let c = match iter.next() { Some(c) => c, None => break, }; if c == '@' && !should_escape { parts.push(TemplatePart::Str(buf)); buf = Vec::new(); // consume our expression here parts.push(TemplatePart::Expression(self.consume_expr(&mut iter)?)); } else if c == '\\' && !should_escape { should_escape = true; continue; } else { buf.push(c); } should_escape = false; } if buf.len() != 0 { parts.push(TemplatePart::Str(buf)); } Ok(parts) } } pub struct ExpressionFormatter<'a, C> where C: assets::Cache, { tmpl: String, builder: RefCell>, } impl<'a, C> ExpressionFormatter<'a, C> where C: assets::Cache, { pub fn new>(tmpl: S, builder: FileBuilder<'a, C>) -> Self { ExpressionFormatter { tmpl: tmpl.into(), builder: RefCell::new(builder), } } fn consume_expr( &self, builder: &mut FileBuilder<'a, C>, iter: &mut Chars, pos: &Position, ) -> Result> { // we expect the next char to be { or we error. let mut expr_string = String::new(); let mut brace_count = 0; match iter.next() { Some(c) => { if c == '{' { brace_count += 1; } else { return Err(error::BuildError::with_pos( format!( "Invalid syntax for format string expected '{{' but got {}", c ), error::ErrorType::FormatError, pos.clone(), ) .to_boxed()); } } None => { return Err(error::BuildError::with_pos( "Invalid syntax for format string expected '{' but string ended", error::ErrorType::FormatError, pos.clone(), ) .to_boxed()); } }; loop { let c = match iter.next() { Some(c) => c, None => break, }; if c == '{' { brace_count += 1; } if c == '}' { brace_count -= 1; // if brace_count is 0 then this is the end of expression. if brace_count != 0 { // if it is not zero then this character is just part of // the embedded expression. expr_string.push(c); continue; } // empty expressions are an error if expr_string.is_empty() { return Err(error::BuildError::with_pos( "Got an empty expression in format string", error::ErrorType::FormatError, pos.clone(), ) .to_boxed()); } if !expr_string.ends_with(";") { expr_string.push(';'); } // we are done and it is time to compute the expression and return it. return Ok(builder.eval_string(&expr_string)?.as_ref().clone()); } else { expr_string.push(c); } } return Err(error::BuildError::with_pos( "Expected '}' but got end of string", error::ErrorType::FormatError, pos.clone(), ) .to_boxed()); } } impl<'a, C> FormatRenderer for ExpressionFormatter<'a, C> where C: assets::Cache, { fn render(&self, pos: &Position) -> Result> { let mut buf = String::new(); let mut should_escape = false; let mut iter = self.tmpl.chars(); loop { let c = match iter.next() { Some(c) => c, None => break, }; if c == '@' && !should_escape { // This is kind of wasteful. Can we do better? let val = self.consume_expr(&mut self.builder.borrow_mut(), &mut iter, pos)?; let strval: String = val.into(); buf.push_str(&strval); should_escape = false; } else if c == '\\' && !should_escape { should_escape = true; } else { buf.push(c); should_escape = false; } } return Ok(buf); } } #[cfg(test)] mod test { use super::{FormatRenderer, SimpleFormatter}; use crate::ast::Position; #[test] fn test_format_happy_path() { let formatter = SimpleFormatter::new("foo @ @ \\@", vec!["bar", "quux"]); let pos = Position::new(0, 0, 0); assert_eq!(formatter.render(&pos).unwrap(), "foo bar quux @"); } #[test] fn test_format_happy_wrong_too_few_args() { let formatter = SimpleFormatter::new("foo @ @ \\@", vec!["bar"]); let pos = Position::new(0, 0, 0); assert!(formatter.render(&pos).is_err()); } #[test] fn test_format_happy_wrong_too_many_args() { let formatter = SimpleFormatter::new("foo @ @ \\@", vec!["bar", "quux", "baz"]); let pos = Position::new(0, 0, 0); assert!(formatter.render(&pos).is_err()); } }