Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse string escape sequences #16

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ They are surrounded by double quotes:
```
str = "The dog says \"woof\"."
```
As you can see, double quotes have to be escaped if used in basic strings.
As you can see, double quotes have to be escaped if used in basic strings. Valid escape sequences are quotes with `\"`, newlines with `\n` and two digit hexadecimal characters with `\x00`.

### Multi-line
They are surrounded by three double quotes on each side.
Expand Down
49 changes: 48 additions & 1 deletion parser/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,53 @@ struct ParserState<'a> {
path: Option<&'a Path>,
}

fn escape_string (s: &str) -> Result<String, Error> {

fn next <T: Iterator<Item=char>> (iter: &mut T) -> Result<char, Error> {
iter.next().ok_or_else(||
Error::InvalidSyntax("Unfinished escape sequence".to_string())
)
}

fn digit_from_char (ch: char) -> Result<u32, Error> {
ch.to_digit(16).ok_or_else(||
Error::InvalidSyntax("Invalid hexadecimal digit".to_string())
)
}

let mut result = String::with_capacity(s.len() - 2);

let mut chars = s[1 .. s.len()-1].chars();

while let Some(c) = chars.next() {
if c == '\\' {
let c = next(&mut chars)?;
match c {
'\\' => result.push('\\'),
'"' => result.push('"'),
'n' => result.push('\n'),
'r' => result.push('\r'),
'x' => {
let a = digit_from_char(next(&mut chars)?)?;
let b = digit_from_char(next(&mut chars)?)?;
let n = a*16 + b;
if n > 127 {
return Err(Error::InvalidSyntax("Not an ASCII value".to_string()));
}
result.push(n as u8 as char);
}
c => return Err(Error::InvalidSyntax(
format!("Invalid escape sequence: \\{}", c)
))
}
} else {
result.push(c);
}
}

Ok(result)
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function will need to return a Result seeing the number of things that can panic, unless they are all cases prevented by the parser.


impl<'a> ParserState<'a> {
// TODO: error on different cast/default type
fn parse_env_var(&self, pair: Pair<Rule>) -> Value {
Expand Down Expand Up @@ -136,7 +183,7 @@ impl<'a> ParserState<'a> {
"false" => Value::Boolean(false),
_ => unreachable!(),
},
Rule::string => Value::String(pair.as_str().replace("\"", "").to_string()),
Rule::string => Value::String(escape_string(pair.as_str()).unwrap()),
Rule::multiline_string => {
let text = pair.as_str().replace("\"\"\"", "");
if text.starts_with('\n') {
Expand Down
2 changes: 1 addition & 1 deletion parser/src/scl.pest
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ comments = _{ "#" ~ (!line_end ~ any)* ~ line_end }

/// LITERALS
boolean = { "true" | "false" }
string = @{ "\"" ~ (!("\"") ~ any)* ~ "\"" }
string = @{ "\"" ~ ( "\\" ~ any | !("\"" | "\\") ~ any)* ~ "\"" }
multiline_string = @{ "\""{3} ~ (!("\""{3}) ~ any)* ~ "\""{3} }

// normal digits or a set of readable one (ie preceded by an underscore)
Expand Down
4 changes: 4 additions & 0 deletions parser/src/tests/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ fn parse_simple_key_value() {
("val = true", Value::Boolean(true)),
("val = false", Value::Boolean(false)),
(r#"val = "a string""#, Value::String("a string".to_string())),
(
r#"val = "a \n\r\"\x21 \\string""#,
Value::String("a \n\r\"\x21 \\string".to_string()),
),
(
r#"val = """a \n\r "'string""""#,
Value::String(r#"a \n\r "'string"#.to_string()),
Expand Down
1 change: 1 addition & 0 deletions parser/tests/valid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ fn test_basic() {
"ho" => V::Array(vec![V::Integer(1), V::Integer(2), V::Integer(3)]),
"hey" => V::Array(vec![V::Integer(1), V::Integer(2)]),
"max_upload_size" => V::Integer(10000000),
"escaped" => V::String("this \"is\" an\nescaped string\x21".to_string()),
"db" => V::Dict(btreemap!(
"url" => V::String("blabla".to_string()),
"password" => V::String("****".to_string()),
Expand Down
2 changes: 2 additions & 0 deletions parser/tests/valid/basic.scl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ hey = [
]
max_upload_size = 10MB

escaped = "this \"is\" an\nescaped string\x21"

# other = include "other.scl"


Expand Down