use crate::check_args;
use crate::eval_ptr::EvalPtr;
use crate::interp::Interp;
use crate::types::ContextID;
use crate::types::Exception;
use crate::types::MoltResult;
use crate::types::VarName;
use crate::util::is_varname_char;
use crate::value::Value;
#[derive(Debug, PartialEq)]
pub(crate) struct Script {
commands: Vec<WordVec>,
}
impl Script {
fn new() -> Self {
Self {
commands: Vec::new(),
}
}
pub fn commands(&self) -> &[WordVec] {
&self.commands
}
}
#[derive(Debug, PartialEq)]
pub(crate) struct WordVec {
words: Vec<Word>,
}
impl WordVec {
fn new() -> Self {
Self { words: Vec::new() }
}
pub fn words(&self) -> &[Word] {
&self.words
}
}
#[derive(Debug, PartialEq)]
pub(crate) enum Word {
Value(Value),
VarRef(String),
ArrayRef(String, Box<Word>),
Script(Script),
Tokens(Vec<Word>),
Expand(Box<Word>),
String(String),
}
pub(crate) fn parse(input: &str) -> Result<Script, Exception> {
let mut ctx = EvalPtr::new(input);
parse_script(&mut ctx)
}
pub(crate) fn parse_script(ctx: &mut EvalPtr) -> Result<Script, Exception> {
let mut script = Script::new();
while !ctx.at_end_of_script() {
script.commands.push(parse_command(ctx)?);
}
Ok(script)
}
fn parse_command(ctx: &mut EvalPtr) -> Result<WordVec, Exception> {
let mut cmd: WordVec = WordVec::new();
while !ctx.at_end_of_script() {
ctx.skip_block_white();
if !ctx.skip_comment() {
break;
}
}
while !ctx.at_end_of_command() {
cmd.words.push(parse_next_word(ctx)?);
ctx.skip_line_white();
}
if ctx.next_is(';') {
ctx.next();
}
Ok(cmd)
}
fn parse_next_word(ctx: &mut EvalPtr) -> Result<Word, Exception> {
if ctx.next_is('{') {
if ctx.tok().as_str().starts_with("{*}") {
ctx.skip();
ctx.skip();
ctx.skip();
if ctx.at_end() || ctx.next_is_block_white() {
return Ok(Word::Value(Value::from("*")));
} else {
return Ok(Word::Expand(Box::new(parse_next_word(ctx)?)));
}
}
parse_braced_word(ctx)
} else if ctx.next_is('"') {
parse_quoted_word(ctx)
} else {
parse_bare_word(ctx, false)
}
}
pub(crate) fn parse_braced_word(ctx: &mut EvalPtr) -> Result<Word, Exception> {
ctx.skip_char('{');
let mut count = 1;
let mut text = String::new();
let mut start = ctx.mark();
while !ctx.at_end() {
if ctx.next_is('{') {
count += 1;
ctx.skip();
} else if ctx.next_is('}') {
count -= 1;
if count > 0 {
ctx.skip();
} else {
text.push_str(ctx.token(start));
let result = Ok(Word::Value(Value::from(text)));
ctx.skip();
if ctx.at_end_of_command() || ctx.next_is_line_white() {
return result;
} else {
return molt_err!("extra characters after close-brace");
}
}
} else if ctx.next_is('\\') {
text.push_str(ctx.token(start));
ctx.skip();
if let Some(ch) = ctx.next() {
if ch == '\n' {
text.push(' ');
} else {
text.push('\\');
text.push(ch);
}
}
start = ctx.mark();
} else {
ctx.skip();
}
}
molt_err!("missing close-brace")
}
pub(crate) fn parse_quoted_word(ctx: &mut EvalPtr) -> Result<Word, Exception> {
ctx.next();
let mut tokens = Tokens::new();
let mut start = ctx.mark();
while !ctx.at_end() {
if ctx.next_is('[') {
if start != ctx.mark() {
tokens.push_str(ctx.token(start));
}
tokens.push(Word::Script(parse_brackets(ctx)?));
start = ctx.mark();
} else if ctx.next_is('$') {
if start != ctx.mark() {
tokens.push_str(ctx.token(start));
}
parse_dollar(ctx, &mut tokens)?;
start = ctx.mark();
} else if ctx.next_is('\\') {
if start != ctx.mark() {
tokens.push_str(ctx.token(start));
}
tokens.push_char(ctx.backslash_subst());
start = ctx.mark();
} else if ctx.next_is('"') {
if start != ctx.mark() {
tokens.push_str(ctx.token(start));
}
ctx.skip_char('"');
if !ctx.at_end_of_command() && !ctx.next_is_line_white() {
return molt_err!("extra characters after close-quote");
} else {
return Ok(tokens.take());
}
} else {
ctx.skip();
}
}
molt_err!("missing \"")
}
fn parse_bare_word(ctx: &mut EvalPtr, index_flag: bool) -> Result<Word, Exception> {
let mut tokens = Tokens::new();
let mut start = ctx.mark();
while !ctx.at_end_of_command() && !ctx.next_is_line_white() {
if index_flag && ctx.next_is(')') {
break;
} else if ctx.next_is('[') {
if start != ctx.mark() {
tokens.push_str(ctx.token(start));
}
tokens.push(Word::Script(parse_brackets(ctx)?));
start = ctx.mark();
} else if ctx.next_is('$') {
if start != ctx.mark() {
tokens.push_str(ctx.token(start));
}
parse_dollar(ctx, &mut tokens)?;
start = ctx.mark();
} else if ctx.next_is('\\') {
if start != ctx.mark() {
tokens.push_str(ctx.token(start));
}
tokens.push_char(ctx.backslash_subst());
start = ctx.mark();
} else {
ctx.skip();
}
}
if start != ctx.mark() {
tokens.push_str(ctx.token(start));
}
Ok(tokens.take())
}
fn parse_brackets(ctx: &mut EvalPtr) -> Result<Script, Exception> {
ctx.skip_char('[');
let old_flag = ctx.is_bracket_term();
ctx.set_bracket_term(true);
let result = parse_script(ctx);
ctx.set_bracket_term(old_flag);
if result.is_ok() {
if ctx.next_is(']') {
ctx.next();
} else {
return molt_err!("missing close-bracket");
}
}
result
}
fn parse_dollar(ctx: &mut EvalPtr, tokens: &mut Tokens) -> Result<(), Exception> {
ctx.skip_char('$');
if !ctx.next_is_varname_char() && !ctx.next_is('{') {
tokens.push_char('$');
} else {
tokens.push(parse_varname(ctx)?);
}
Ok(())
}
pub(crate) fn parse_varname(ctx: &mut EvalPtr) -> Result<Word, Exception> {
if ctx.next_is('{') {
ctx.skip_char('{');
let start = ctx.mark();
ctx.skip_while(|ch| *ch != '}');
if ctx.at_end() {
return molt_err!("missing close-brace for variable name");
}
let var_name = parse_varname_literal(ctx.token(start));
ctx.skip_char('}');
match var_name.index() {
Some(index) => Ok(Word::ArrayRef(
var_name.name().into(),
Box::new(Word::String(index.into())),
)),
None => Ok(Word::VarRef(var_name.name().into())),
}
} else {
let start = ctx.mark();
ctx.skip_while(|ch| is_varname_char(*ch));
let name = ctx.token(start).to_string();
if !ctx.next_is('(') {
Ok(Word::VarRef(name))
} else {
ctx.skip();
let index = parse_bare_word(ctx, true)?;
ctx.skip_char(')');
Ok(Word::ArrayRef(name, Box::new(index)))
}
}
}
pub(crate) fn parse_varname_literal(literal: &str) -> VarName {
let mut ctx = EvalPtr::new(literal);
let start = ctx.mark();
ctx.skip_while(|ch| *ch != '(');
if ctx.at_end() {
return VarName::scalar(literal.into());
}
let name = ctx.token(start).to_string();
ctx.skip_char('(');
if ctx.tok().as_str().is_empty() {
return VarName::scalar(literal.into());
}
let start = ctx.mark();
let chars_left = ctx.tok().as_str().len() - 1;
for _ in 0..chars_left {
ctx.skip();
}
if ctx.next_is(')') {
VarName::array(name, ctx.token(start).to_string())
} else {
VarName::scalar(literal.into())
}
}
struct Tokens {
list: Vec<Word>,
got_string: bool,
string: String,
}
impl Tokens {
fn new() -> Self {
Self {
list: Vec::new(),
got_string: false,
string: String::new(),
}
}
fn push(&mut self, word: Word) {
if self.got_string {
let string = std::mem::replace(&mut self.string, String::new());
self.list.push(Word::String(string));
self.got_string = false;
}
self.list.push(word);
}
fn push_str(&mut self, str: &str) {
self.string.push_str(str);
self.got_string = true;
}
fn push_char(&mut self, ch: char) {
self.string.push(ch);
self.got_string = true;
}
fn take(mut self) -> Word {
if self.got_string {
if self.list.is_empty() {
return Word::Value(Value::from(self.string));
} else {
let string = std::mem::replace(&mut self.string, String::new());
self.list.push(Word::String(string));
}
}
if self.list.is_empty() {
Word::Value(Value::empty())
} else if self.list.len() == 1 {
self.list.pop().unwrap()
} else {
Word::Tokens(self.list)
}
}
}
pub fn cmd_parse(_interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
check_args(1, argv, 2, 2, "script")?;
let script = &argv[1];
molt_ok!(format!("{:?}", parse(script.as_str())?))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tokens() {
let tokens = Tokens::new();
assert_eq!(tokens.take(), Word::Value(Value::empty()));
let mut tokens = Tokens::new();
tokens.push(Word::Value(Value::from("abc")));
assert_eq!(tokens.take(), Word::Value(Value::from("abc")));
let mut tokens = Tokens::new();
tokens.push_str("xyz");
assert_eq!(tokens.take(), Word::Value(Value::from("xyz")));
let mut tokens = Tokens::new();
tokens.push_str("abc");
tokens.push_str("def");
assert_eq!(tokens.take(), Word::Value(Value::from("abcdef")));
let mut tokens = Tokens::new();
tokens.push_str("abc");
tokens.push_char('/');
tokens.push_str("def");
assert_eq!(tokens.take(), Word::Value(Value::from("abc/def")));
let mut tokens = Tokens::new();
tokens.push(Word::VarRef("a".into()));
tokens.push(Word::String("xyz".into()));
assert_eq!(
tokens.take(),
Word::Tokens(vec![Word::VarRef("a".into()), Word::String("xyz".into())])
);
let mut tokens = Tokens::new();
tokens.push_str("a");
tokens.push_str("b");
tokens.push(Word::VarRef("xyz".into()));
tokens.push_str("c");
tokens.push_str("d");
assert_eq!(
tokens.take(),
Word::Tokens(vec![
Word::String("ab".into()),
Word::VarRef("xyz".into()),
Word::String("cd".into())
])
);
}
#[test]
fn test_parse() {
assert!(parse("").unwrap().commands.is_empty());
let cmds = parse("a").unwrap().commands;
assert_eq!(cmds.len(), 1);
assert_eq!(cmds[0].words, vec![Word::Value(Value::from("a"))]);
let cmds = parse("a\nb").unwrap().commands;
assert_eq!(cmds.len(), 2);
assert_eq!(cmds[0].words, vec![Word::Value(Value::from("a"))]);
assert_eq!(cmds[1].words, vec![Word::Value(Value::from("b"))]);
let cmds = parse("a;b").unwrap().commands;
assert_eq!(cmds.len(), 2);
assert_eq!(cmds[0].words, vec![Word::Value(Value::from("a"))]);
assert_eq!(cmds[1].words, vec![Word::Value(Value::from("b"))]);
let cmds = parse(" a ; b ").unwrap().commands;
assert_eq!(cmds.len(), 2);
assert_eq!(cmds[0].words, vec![Word::Value(Value::from("a"))]);
assert_eq!(cmds[1].words, vec![Word::Value(Value::from("b"))]);
assert_eq!(parse("a {"), molt_err!("missing close-brace"));
}
#[test]
fn test_parse_next_word() {
assert_eq!(
pword("{abc}"),
Ok((Word::Value(Value::from("abc")), "".into()))
);
assert_eq!(pword("{*}"), Ok((Word::Value(Value::from("*")), "".into())));
assert_eq!(
pword("{*} "),
Ok((Word::Value(Value::from("*")), " ".into()))
);
assert_eq!(
pword("{*}abc "),
Ok((
Word::Expand(Box::new(Word::Value(Value::from("abc")))),
" ".into()
))
);
assert_eq!(
pword("\"abc\""),
Ok((Word::Value(Value::from("abc")), "".into()))
);
assert_eq!(
pword("abc"),
Ok((Word::Value(Value::from("abc")), "".into()))
);
}
fn pword(input: &str) -> Result<(Word, String), Exception> {
let mut ctx = EvalPtr::new(input);
let word = parse_next_word(&mut ctx)?;
Ok((word, ctx.tok().as_str().to_string()))
}
#[test]
fn test_parse_braced_word() {
assert_eq!(
pbrace("{abc}"),
Ok((Word::Value(Value::from("abc")), "".into()))
);
assert_eq!(
pbrace("{abc} "),
Ok((Word::Value(Value::from("abc")), " ".into()))
);
assert_eq!(
pbrace("{a b c} "),
Ok((Word::Value(Value::from("a b c")), " ".into()))
);
assert_eq!(
pbrace("{a $b [c]} "),
Ok((Word::Value(Value::from("a $b [c]")), " ".into()))
);
assert_eq!(
pbrace("{a{b}c} "),
Ok((Word::Value(Value::from("a{b}c")), " ".into()))
);
assert_eq!(
pbrace("{a\\{bc} "),
Ok((Word::Value(Value::from("a\\{bc")), " ".into()))
);
assert_eq!(
pbrace("{ab\\}c} "),
Ok((Word::Value(Value::from("ab\\}c")), " ".into()))
);
assert_eq!(
pbrace("{ab\\\nc} "),
Ok((Word::Value(Value::from("ab c")), " ".into()))
);
assert_eq!(pbrace("{abc"), molt_err!("missing close-brace"));
assert_eq!(pbrace("{a{b}c"), molt_err!("missing close-brace"));
}
fn pbrace(input: &str) -> Result<(Word, String), Exception> {
let mut ctx = EvalPtr::new(input);
let word = parse_braced_word(&mut ctx)?;
Ok((word, ctx.tok().as_str().to_string()))
}
#[test]
fn test_parse_quoted_word() {
assert_eq!(
pqw("\"abc\""),
Ok((Word::Value(Value::from("abc")), "".into()))
);
assert_eq!(
pqw("\"abc\" "),
Ok((Word::Value(Value::from("abc")), " ".into()))
);
assert_eq!(
pqw("\"\\x77-\" "),
Ok((Word::Value(Value::from("w-")), " ".into()))
);
assert_eq!(
pqw("\"-\\x77-\" "),
Ok((Word::Value(Value::from("-w-")), " ".into()))
);
assert_eq!(
pqw("\"-\\x77\" "),
Ok((Word::Value(Value::from("-w")), " ".into()))
);
assert_eq!(
pqw("\"a$x.b\" "),
Ok((
Word::Tokens(vec![
Word::String("a".into()),
Word::VarRef("x".into()),
Word::String(".b".into()),
]),
" ".into()
))
);
assert_eq!(
pqw("\"a${x}b\" "),
Ok((
Word::Tokens(vec![
Word::String("a".into()),
Word::VarRef("x".into()),
Word::String("b".into()),
]),
" ".into()
))
);
assert_eq!(
pqw("\"a$.b\" "),
Ok((Word::Value(Value::from("a$.b")), " ".into()))
);
assert_eq!(
pqw("\"a[list b]c\" "),
Ok((
Word::Tokens(vec![
Word::String("a".into()),
Word::Script(pbrack("[list b]").unwrap()),
Word::String("c".into()),
]),
" ".into()
))
);
assert_eq!(pqw("\"abc"), molt_err!("missing \""));
assert_eq!(
pqw("\"abc\"x "),
molt_err!("extra characters after close-quote")
);
}
fn pqw(input: &str) -> Result<(Word, String), Exception> {
let mut ctx = EvalPtr::new(input);
let word = parse_quoted_word(&mut ctx)?;
Ok((word, ctx.tok().as_str().to_string()))
}
#[test]
fn test_parse_bare_word() {
assert_eq!(
pbare("abc", false),
Ok((Word::Value(Value::from("abc")), "".into()))
);
assert_eq!(
pbare("abc ", false),
Ok((Word::Value(Value::from("abc")), " ".into()))
);
assert_eq!(
pbare("\\x77- ", false),
Ok((Word::Value(Value::from("w-")), " ".into()))
);
assert_eq!(
pbare("-\\x77- ", false),
Ok((Word::Value(Value::from("-w-")), " ".into()))
);
assert_eq!(
pbare("-\\x77 ", false),
Ok((Word::Value(Value::from("-w")), " ".into()))
);
assert_eq!(
pbare("a$x.b ", false),
Ok((
Word::Tokens(vec![
Word::String("a".into()),
Word::VarRef("x".into()),
Word::String(".b".into()),
]),
" ".into()
))
);
assert_eq!(
pbare("a${x}b ", false),
Ok((
Word::Tokens(vec![
Word::String("a".into()),
Word::VarRef("x".into()),
Word::String("b".into()),
]),
" ".into()
))
);
assert_eq!(
pbare("a$.b ", false),
Ok((Word::Value(Value::from("a$.b")), " ".into()))
);
assert_eq!(
pbare("a[list b]c ", false),
Ok((
Word::Tokens(vec![
Word::String("a".into()),
Word::Script(pbrack("[list b]").unwrap()),
Word::String("c".into()),
]),
" ".into()
))
);
assert_eq!(
pbare("a)b", true),
Ok((Word::Value(Value::from("a")), ")b".into()))
);
}
fn pbare(input: &str, index_flag: bool) -> Result<(Word, String), Exception> {
let mut ctx = EvalPtr::new(input);
let word = parse_bare_word(&mut ctx, index_flag)?;
Ok((word, ctx.tok().as_str().to_string()))
}
#[test]
fn test_parse_brackets() {
let script = pbrack("[set a 5]").unwrap();
assert_eq!(script.commands.len(), 1);
let cmd = &script.commands[0];
assert_eq!(
cmd.words,
vec![
Word::Value(Value::from("set")),
Word::Value(Value::from("a")),
Word::Value(Value::from("5")),
]
);
assert_eq!(pbrack("[incomplete"), molt_err!("missing close-bracket"));
}
fn pbrack(input: &str) -> Result<Script, Exception> {
let mut ctx = EvalPtr::new(input);
parse_brackets(&mut ctx)
}
#[test]
fn test_parse_dollar() {
assert_eq!(pvar("$a"), Ok((Word::VarRef("a".into()), "".into())));
assert_eq!(pvar("$abc"), Ok((Word::VarRef("abc".into()), "".into())));
assert_eq!(pvar("$abc."), Ok((Word::VarRef("abc".into()), ".".into())));
assert_eq!(pvar("$a.bc"), Ok((Word::VarRef("a".into()), ".bc".into())));
assert_eq!(
pvar("$a1_.bc"),
Ok((Word::VarRef("a1_".into()), ".bc".into()))
);
assert_eq!(
pvar("$a(1)"),
Ok((
Word::ArrayRef("a".into(), Box::new(Word::Value(Value::from("1")))),
"".into()
))
);
assert_eq!(pvar("${a}b"), Ok((Word::VarRef("a".into()), "b".into())));
assert_eq!(
pvar("${ab"),
molt_err!("missing close-brace for variable name")
);
assert_eq!(
pvar("${a(1)}"),
Ok((
Word::ArrayRef("a".into(), Box::new(Word::String("1".into()))),
"".into()
))
);
assert_eq!(pvar("$"), Ok((Word::Value(Value::from("$")), "".into())));
assert_eq!(pvar("$."), Ok((Word::Value(Value::from("$")), ".".into())));
}
fn pvar(input: &str) -> Result<(Word, String), Exception> {
let mut ctx = EvalPtr::new(input);
let mut tokens = Tokens::new();
parse_dollar(&mut ctx, &mut tokens)?;
Ok((tokens.take(), ctx.tok().as_str().to_string()))
}
#[test]
fn test_parse_varname_literal() {
assert_eq!(parse_varname_literal(""), scalar(""));
assert_eq!(parse_varname_literal("a"), scalar("a"));
assert_eq!(parse_varname_literal("a(b"), scalar("a(b"));
assert_eq!(parse_varname_literal("("), scalar("("));
assert_eq!(parse_varname_literal(")"), scalar(")"));
assert_eq!(parse_varname_literal("a(b)c"), scalar("a(b)c"));
assert_eq!(parse_varname_literal("(b)c"), scalar("(b)c"));
assert_eq!(parse_varname_literal("a(b)"), array("a", "b"));
assert_eq!(parse_varname_literal("a({)"), array("a", "{"));
assert_eq!(parse_varname_literal("()"), array("", ""));
assert_eq!(parse_varname_literal("(b)"), array("", "b"));
assert_eq!(parse_varname_literal("a()"), array("a", ""));
assert_eq!(parse_varname_literal("%(()"), array("%", "("));
assert_eq!(parse_varname_literal("%())"), array("%", ")"));
}
fn scalar(name: &str) -> VarName {
VarName::scalar(name.into())
}
fn array(name: &str, index: &str) -> VarName {
VarName::array(name.into(), index.into())
}
}