use crate::{molt_err, util};
use crate::tokenizer::Tokenizer;
use crate::types::*;
use crate::value::Value;
use alloc::string::{String, ToString as _};
use alloc::vec::Vec;
pub(crate) fn get_list(str: &str) -> Result<MoltList, Exception> {
let mut ctx = Tokenizer::new(str);
parse_list(&mut ctx)
}
fn is_list_white(ch: char) -> bool {
match ch {
' ' => true,
'\n' => true,
'\r' => true,
'\t' => true,
'\x0B' => true, '\x0C' => true, _ => false,
}
}
fn parse_list(ctx: &mut Tokenizer) -> Result<MoltList, Exception> {
ctx.skip_while(is_list_white);
let mut items = Vec::new();
while !ctx.at_end() {
items.push(parse_item(ctx)?);
ctx.skip_while(is_list_white);
}
Ok(items)
}
fn parse_item(ctx: &mut Tokenizer) -> MoltResult {
if ctx.is('{') {
Ok(parse_braced_item(ctx)?)
} else if ctx.is('"') {
Ok(parse_quoted_item(ctx)?)
} else {
Ok(parse_bare_item(ctx)?)
}
}
fn parse_braced_item(ctx: &mut Tokenizer) -> MoltResult {
ctx.next();
let mut count = 1;
let mark = ctx.mark();
while let Some(c) = ctx.peek() {
if c == '\\' {
ctx.skip();
ctx.skip();
} else if c == '{' {
count += 1;
ctx.skip();
} else if c == '}' {
count -= 1;
if count > 0 {
ctx.skip();
} else {
let result = Ok(Value::from(ctx.token(mark).to_string()));
ctx.skip();
if ctx.at_end() || ctx.has(is_list_white) {
return result;
} else {
return molt_err!("extra characters after close-brace");
}
}
} else {
ctx.skip();
}
}
assert!(count > 0);
molt_err!("unmatched open brace in list")
}
fn parse_quoted_item(ctx: &mut Tokenizer) -> MoltResult {
ctx.skip();
let mut item = String::new();
let mut start = ctx.mark();
while !ctx.at_end() {
ctx.skip_while(|ch| ch != '"' && ch != '\\');
item.push_str(ctx.token(start));
match ctx.peek() {
Some('"') => {
ctx.skip();
return Ok(Value::from(item));
}
Some('\\') => {
item.push(ctx.backslash_subst());
start = ctx.mark();
}
_ => unreachable!(),
}
}
molt_err!("unmatched open quote in list")
}
fn parse_bare_item(ctx: &mut Tokenizer) -> MoltResult {
let mut item = String::new();
let mut start = ctx.mark();
while !ctx.at_end() {
ctx.skip_while(|ch| !is_list_white(ch) && ch != '\\');
item.push_str(ctx.token(start));
start = ctx.mark();
if ctx.has(is_list_white) {
break;
}
if ctx.is('\\') {
item.push(ctx.backslash_subst());
start = ctx.mark();
}
}
Ok(Value::from(item))
}
pub fn list_to_string(list: &[Value]) -> String {
let mut text = String::new();
let mut hash = !list.is_empty() && list[0].as_str().starts_with('#');
for item in list {
if !text.is_empty() {
text.push(' ');
}
let item = item.as_str();
match get_mode(item) {
Mode::AsIs => {
if hash {
brace_item(item, &mut text);
hash = false;
} else {
text.push_str(item)
}
}
Mode::Brace => {
brace_item(item, &mut text);
}
Mode::Escape => {
escape_item(hash, item, &mut text);
hash = false;
}
}
}
text
}
fn brace_item(item: &str, out: &mut String) {
out.push('{');
out.push_str(item);
out.push('}');
}
fn escape_item(hash: bool, item: &str, out: &mut String) {
if hash {
out.push('\\');
}
for ch in item.chars() {
if util::is_whitespace(ch) {
out.push('\\');
out.push(ch);
continue;
}
match ch {
'{' | ';' | '$' | '[' | ']' | '\\' => {
out.push('\\');
out.push(ch);
}
_ => out.push(ch),
}
}
}
#[derive(Eq, PartialEq, Debug)]
enum Mode {
AsIs,
Brace,
Escape,
}
fn get_mode(word: &str) -> Mode {
if word.is_empty() {
return Mode::Brace;
}
let mut mode = Mode::AsIs;
let mut brace_count = 0;
let mut iter = word.chars().peekable();
while let Some(ch) = iter.next() {
if util::is_whitespace(ch) {
mode = Mode::Brace;
continue;
}
match ch {
';' | '$' | '[' | ']' => {
mode = Mode::Brace;
}
'{' => brace_count += 1,
'}' => brace_count -= 1,
'\\' => {
if iter.peek() == Some(&'\n') {
return Mode::Escape;
} else {
mode = Mode::Brace;
}
}
_ => (),
}
}
if brace_count != 0 {
Mode::Escape
} else {
mode
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_list_to_string() {
assert_eq!(list_to_string(&[Value::from("a")]), "a");
assert_eq!(list_to_string(&[Value::from("a"), Value::from("b")]), "a b");
assert_eq!(
list_to_string(&[Value::from("a"), Value::from("b"), Value::from("c")]),
"a b c"
);
assert_eq!(
list_to_string(&[Value::from("a"), Value::from(" "), Value::from("c")]),
"a { } c"
);
assert_eq!(
list_to_string(&[Value::from("a"), Value::from(""), Value::from("c")]),
"a {} c"
);
assert_eq!(list_to_string(&[Value::from("a;b")]), "{a;b}");
assert_eq!(list_to_string(&[Value::from("a$b")]), "{a$b}");
assert_eq!(list_to_string(&[Value::from("a[b")]), "{a[b}");
assert_eq!(list_to_string(&[Value::from("a]b")]), "{a]b}");
assert_eq!(list_to_string(&[Value::from("a\\nb")]), "{a\\nb}");
assert_eq!(
list_to_string(&[Value::from("{ "), Value::from("abc")]),
r#"\{\ abc"#
);
}
#[test]
fn test_parse_braced_item() {
assert_eq!(pbi("{}"), "|".to_string());
assert_eq!(pbi("{abc}"), "abc|".to_string());
assert_eq!(pbi("{abc} "), "abc| ".to_string());
assert_eq!(pbi("{a{b}c}"), "a{b}c|".to_string());
assert_eq!(pbi("{a{b}{c}}"), "a{b}{c}|".to_string());
assert_eq!(pbi("{a{b}{c}d}"), "a{b}{c}d|".to_string());
assert_eq!(pbi("{a{b}{c}d} efg"), "a{b}{c}d| efg".to_string());
assert_eq!(pbi("{a\\{bc}"), "a\\{bc|".to_string());
}
fn pbi(input: &str) -> String {
let mut ctx = Tokenizer::new(input);
if let Ok(val) = parse_braced_item(&mut ctx) {
format!("{}|{}", val.as_str(), ctx.as_str())
} else {
String::from("Err")
}
}
#[test]
fn test_parse_quoted_item() {
assert_eq!(pqi("\"abc\""), "abc|".to_string());
assert_eq!(pqi("\"abc\" "), "abc| ".to_string());
assert_eq!(pqi("\"a\\x77-\""), "aw-|".to_string());
}
fn pqi(input: &str) -> String {
let mut ctx = Tokenizer::new(input);
if let Ok(val) = parse_quoted_item(&mut ctx) {
format!("{}|{}", val.as_str(), ctx.as_str())
} else {
String::from("Err")
}
}
#[test]
fn test_parse_bare_item() {
println!("test_parse_bare_item");
assert_eq!(pbare("abc"), "abc|".to_string());
assert_eq!(pbare("abc def"), "abc| def".to_string());
assert_eq!(pbare("abc\ndef"), "abc|\ndef".to_string());
assert_eq!(pbare("abc\rdef"), "abc|\rdef".to_string());
assert_eq!(pbare("abc\tdef"), "abc|\tdef".to_string());
assert_eq!(pbare("abc\x0Bdef"), "abc|\x0Bdef".to_string());
assert_eq!(pbare("abc\x0Cdef"), "abc|\x0Cdef".to_string());
assert_eq!(pbare("a\\x77-"), "aw-|".to_string());
assert_eq!(pbare("a\\x77- def"), "aw-| def".to_string());
assert_eq!(pbare("a\\x77"), "aw|".to_string());
assert_eq!(pbare("a\\x77 "), "aw| ".to_string());
}
fn pbare(input: &str) -> String {
let mut ctx = Tokenizer::new(input);
if let Ok(val) = parse_bare_item(&mut ctx) {
format!("{}|{}", val.as_str(), ctx.as_str())
} else {
String::from("Err")
}
}
#[test]
fn test_issue_43() {
let list = get_list("a ;b c").unwrap();
assert_eq!(list.len(), 3);
}
}