diff --git a/docsite/site/content/reference/expressions.md b/docsite/site/content/reference/expressions.md index 6cfd2a8..7efd118 100644 --- a/docsite/site/content/reference/expressions.md +++ b/docsite/site/content/reference/expressions.md @@ -320,6 +320,12 @@ let tpl = { "foo.bar.1 == @{item.foo.bar.1}" % tpl; ``` +The `@` symbol can be escaped with a double slash. + +``` +"https://username:password\\@@:@/" % (host, port) +``` + If the `%` operator is followed by a parenthesized expression it will be treated as the first form with one item. diff --git a/docsite/site/content/reference/types.md b/docsite/site/content/reference/types.md index 1f1e63b..6125ea3 100644 --- a/docsite/site/content/reference/types.md +++ b/docsite/site/content/reference/types.md @@ -47,6 +47,12 @@ Strings are any double quoted text. You can use the `\` to esacpe characters in "This is an escaped \"string\""; ``` +*Character Escapes* + +* '\n' is a new line +* '\r' is a carriage return +* '\t' is a tab + ### NULL or the Empty type NULL is the empty type. It represents the absence of a value. It is represented by the diff --git a/integration_tests/format_test.ucg b/integration_tests/format_test.ucg index 56c7c45..990f65a 100644 --- a/integration_tests/format_test.ucg +++ b/integration_tests/format_test.ucg @@ -48,4 +48,14 @@ assert t.equal{ assert t.equal{ left = "@{item.op()} is just great" % {op=func() => "BOB!"}, right = "BOB! is just great", +}; + +assert t.equal{ + left = "\\@@" % (NULL), + right = "@NULL", +}; + +assert t.equal{ + left = "\\@{foo}@{item.op()} is just great" % {op=func() => "BOB!"}, + right = "@{foo}BOB! is just great", }; \ No newline at end of file diff --git a/integration_tests/simple_values_test.ucg b/integration_tests/simple_values_test.ucg new file mode 100644 index 0000000..18324e8 --- /dev/null +++ b/integration_tests/simple_values_test.ucg @@ -0,0 +1,17 @@ +let t = import "std/testing.ucg"; + +assert t.equal{ + left = "foo\n", + right = "foo +", +}; + +assert t.equal{ + left = "foo\t", + right = "foo ", +}; + +assert t.not_equal{ + left = "foo\\t", + right = "foo\\" + "\t", +}; \ No newline at end of file diff --git a/src/build/format.rs b/src/build/format.rs index 1d4e3df..967abe1 100644 --- a/src/build/format.rs +++ b/src/build/format.rs @@ -67,16 +67,23 @@ impl + Clone> FormatRenderer for SimpleFormatter { 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; } } if self.args.len() != count { return Err(error::BuildError::with_pos( - "Too many arguments to string \ - formatter.", + format!( + "Too many arguments to string \ + formatter. Expected {} got {}", + count, + self.args.len() + ), error::ErrorType::FormatError, pos.clone(), ) @@ -201,10 +208,12 @@ where 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); diff --git a/src/convert/yaml.rs b/src/convert/yaml.rs index 5ad105b..22cc170 100644 --- a/src/convert/yaml.rs +++ b/src/convert/yaml.rs @@ -71,7 +71,11 @@ impl YamlConverter { Ok(yaml_val) } - fn merge_mapping_keys(&self, mut fs: &mut Vec<(String, Rc)>, m: &serde_yaml::Mapping) -> Result<(), Box> { + fn merge_mapping_keys( + &self, + mut fs: &mut Vec<(String, Rc)>, + m: &serde_yaml::Mapping, + ) -> Result<(), Box> { for (key, value) in m { // This is a little gross but since yaml allows maps to be keyed // by more than just a string it's necessary. @@ -90,7 +94,6 @@ impl YamlConverter { if let serde_yaml::Value::Mapping(merge_map) = value { self.merge_mapping_keys(&mut fs, merge_map)?; } - } else { fs.push((key, Rc::new(self.convert_yaml_val(&value)?))); } diff --git a/src/tokenizer/mod.rs b/src/tokenizer/mod.rs index 2c3b699..f3c178a 100644 --- a/src/tokenizer/mod.rs +++ b/src/tokenizer/mod.rs @@ -49,7 +49,7 @@ fn is_symbol_char<'a>(i: OffsetStrIter<'a>) -> Result, u8> { fn escapequoted<'a>(input: OffsetStrIter<'a>) -> Result, String> { // loop until we find a " that is not preceded by \. - // Collapse all \ to just char for escaping. + // Collapse all \ to just char for escaping exept for \n \r \t and \@. let mut frag = String::new(); let mut escape = false; let mut _input = input.clone(); @@ -58,6 +58,31 @@ fn escapequoted<'a>(input: OffsetStrIter<'a>) -> Result, Strin Some(c) => *c, None => break, }; + if escape { + match c as char { + 'n' => { + eprintln!("Pushing new line onto string"); + frag.push('\n'); + escape = false; + continue; + } + 'r' => { + eprintln!("Pushing carriage return onto string"); + frag.push('\r'); + escape = false; + continue; + } + 't' => { + eprintln!("Pushing tab onto string"); + frag.push('\t'); + escape = false; + continue; + } + _ => { + //noop + } + } + } if c == '\\' as u8 && !escape { // eat this slash and set our escaping sentinel escape = true;