1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
//! this mod achieves the transformation of a string containing
//! one or several commands into a vec of parsed commands, using
//! the verbstore to try guess what part is an argument and what
//! part is a filter
use crate::app_context::AppContext;
use crate::commands::Command;
use crate::errors::ProgramError;
use crate::verb_store::PrefixSearchResult;
#[derive(Debug)]
enum CommandSequenceToken {
Standard(String), // one or several words, not starting with a ':'. May be a filter or a verb argument
VerbKey(String), // a verb (the ':' isn't given)
}
struct CommandSequenceTokenizer {
chars: Vec<char>,
pos: usize,
}
impl CommandSequenceTokenizer {
pub fn from(sequence: &str) -> CommandSequenceTokenizer {
CommandSequenceTokenizer {
chars: sequence.chars().collect(),
pos: 0,
}
}
}
impl Iterator for CommandSequenceTokenizer {
type Item = CommandSequenceToken;
fn next(&mut self) -> Option<CommandSequenceToken> {
if self.pos >= self.chars.len() {
return None;
}
let is_verb = if self.chars[self.pos] == ':' {
self.pos = self.pos + 1;
true
} else {
false
};
let mut end = self.pos;
let mut between_quotes = false;
while end < self.chars.len() {
if self.chars[end] == '"' {
between_quotes = !between_quotes;
} else if self.chars[end] == ' ' && !between_quotes {
break;
}
end += 1;
}
let token: String = self.chars[self.pos..end].iter().collect();
self.pos = end + 1;
Some(if is_verb {
CommandSequenceToken::VerbKey(token)
} else {
CommandSequenceToken::Standard(token)
})
}
}
/// parse a string which is meant as a sequence of commands.
/// Note that this is inherently flawed as packing several commands
/// into a string without hard separator is ambiguous in the general
/// case.
///
/// In the future I might introduce a way to define a variable hard separator
/// (for example "::sep=#:some_filter#:some command with three arguments#a_filter")
///
/// The current parsing try to be the least possible flawed by
/// giving verbs the biggest sequence of tokens accepted by their
/// execution pattern.
pub fn parse_command_sequence(
sequence: &str,
con: &AppContext,
) -> Result<Vec<Command>, ProgramError> {
let mut tokenizer = CommandSequenceTokenizer::from(sequence);
let mut commands: Vec<Command> = Vec::new();
let mut leftover: Option<CommandSequenceToken> = None;
loop {
let first_token = if let Some(token) = leftover.take().or_else(|| tokenizer.next()) {
token
} else {
break;
};
let raw = match first_token {
CommandSequenceToken::VerbKey(key) => {
let verb = match con.verb_store.search(&key) {
PrefixSearchResult::NoMatch => {
return Err(ProgramError::UnknownVerb { key });
}
PrefixSearchResult::TooManyMatches => {
return Err(ProgramError::AmbiguousVerbKey { key });
}
PrefixSearchResult::Match(verb) => verb,
};
let mut raw = format!(":{}", key);
if let Some(args_regex) = &verb.args_parser {
let mut args: Vec<String> = Vec::new();
let mut nb_valid_args = 0;
// we'll try to consume as many tokens as possible
while let Some(token) = tokenizer.next() {
match token {
CommandSequenceToken::VerbKey(_) => {
leftover = Some(token);
break;
}
CommandSequenceToken::Standard(raw) => {
args.push(raw);
if args_regex.is_match(&args.join(" ")) {
nb_valid_args = args.len();
}
}
}
}
if nb_valid_args == 0 && !args_regex.is_match("") {
return Err(ProgramError::UnmatchingVerbArgs { key });
}
for (i, arg) in args.drain(..).enumerate() {
if i < nb_valid_args {
raw.push(' ');
raw.push_str(&arg);
} else {
commands.push(Command::from(arg));
}
}
}
raw
}
CommandSequenceToken::Standard(raw) => raw,
};
commands.push(Command::from(raw));
}
if let Some(token) = leftover.take() {
commands.push(Command::from(match token {
CommandSequenceToken::Standard(raw) => raw,
CommandSequenceToken::VerbKey(raw) => format!(":{}", raw),
}));
}
Ok(commands)
}