Skip to content

Commit 36ad1f3

Browse files
committed
Double qouted string variables working
update readme
1 parent e53a590 commit 36ad1f3

File tree

5 files changed

+108
-45
lines changed

5 files changed

+108
-45
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ is a list of objectives for the near term. The to-do list is always growing as I
1818
- [ ] Tilde Expansion
1919
- [ ] Pathname Expansion
2020
- [x] Basic single quote strings
21-
- [ ] Double quote strings with substitutions
21+
- [X] Double quote strings with substitutions
2222
- [x] [File redirects](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_07)
2323
- [x] Redirect input with `<`
2424
- [ ] Explicit file descriptor redirects

src/expr.rs

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -772,24 +772,18 @@ impl Argument {
772772
}
773773

774774
fn evaluate_string(string: &str, state: &Rc<RefCell<State>>) -> Option<String> {
775-
let mut ret = String::default();
776-
let mut chars = string.chars().peekable();
777-
while let Some(c) = chars.next() {
778-
if c == '$' {
779-
let mut var = String::default();
780-
'varloop: while let Some(v) = chars.peek() {
781-
if *v != ' ' && *v != '$' {
782-
var.push(*v);
783-
chars.next();
784-
} else {
785-
break 'varloop;
786-
}
787-
}
788-
ret += &get_variable(var, state).unwrap_or_default();
789-
} else {
790-
ret.push(c);
791-
}
792-
}
775+
let mut parser = Parser::new(state.clone());
776+
let ret = match parser.parse_double_quoted_string(string) {
777+
Ok(args) => args
778+
.into_iter()
779+
.map(|a| a.eval(state))
780+
.reduce(|whole, next| whole + &next)
781+
.unwrap(),
782+
Err(e) => {
783+
println!("Slush Error {e}");
784+
String::default()
785+
}
786+
};
793787
Some(ret)
794788
}
795789

src/parser.rs

Lines changed: 82 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ impl Parser {
3636

3737
pub fn parse(&mut self, line: &str) {
3838
self.err = "".to_string();
39-
self.token = match tokens(line) {
39+
self.token = match tokens(line, false) {
4040
Ok(t) => t,
4141
Err(e) => {
4242
self.err += &e;
@@ -55,6 +55,31 @@ impl Parser {
5555
}
5656
}
5757

58+
pub fn parse_double_quoted_string(&mut self, line: &str) -> Result<Vec<Argument>, String> {
59+
self.err = "".to_string();
60+
self.token = match tokens(line, true) {
61+
Ok(t) => t,
62+
Err(e) => {
63+
self.err += &e;
64+
Vec::new()
65+
}
66+
};
67+
68+
let mut stringies: Vec<Argument> = Vec::new();
69+
while !self.current_is(ShTokenType::EndOfFile) {
70+
let a = if self.current_is(ShTokenType::WhiteSpace) {
71+
let r = Argument::Name(self.current().lexeme.clone());
72+
self.next_token();
73+
r
74+
} else {
75+
self.parse_double_argument()?
76+
.unwrap_or_else(|| Argument::Name(String::default()))
77+
};
78+
stringies.push(a);
79+
}
80+
Ok(stringies)
81+
}
82+
5883
// the results are a left-associative no precedence
5984
// list of and / or expressions.
6085
fn parse_andor_list(&mut self) -> Result<AndOrNode, String> {
@@ -465,33 +490,34 @@ impl Parser {
465490
Ok(argument)
466491
}
467492

493+
fn parse_variable_name(&mut self) -> Result<Option<Argument>, String> {
494+
match self.current().token_type {
495+
ShTokenType::Name => Ok(Some(Argument::Variable(VariableLookup {
496+
name: self.consume_current().lexeme.clone(),
497+
}))),
498+
ShTokenType::LeftParen => Ok(Some(Argument::SubShell(SubShellExpr {
499+
shell: self.collect_matching(ShTokenType::LeftParen, ShTokenType::RightParen)?,
500+
}))),
501+
ShTokenType::LeftBrace => Ok(Some(Argument::Expansion(self.parse_expansion()?))),
502+
ShTokenType::DollarSign
503+
| ShTokenType::Bang
504+
| ShTokenType::Star
505+
| ShTokenType::Pound
506+
| ShTokenType::AtSign => Ok(Some(Argument::Variable(VariableLookup {
507+
name: self.consume_current().lexeme.clone(),
508+
}))),
509+
_ => Err("Expected some value after '$'".to_string()),
510+
}
511+
}
512+
468513
// helper function for parse_argument that gets exactly one 'primitive' argument
469514
// which may be strung together to create
470515
fn _parse_argument(&mut self) -> Result<Option<Argument>, String> {
471516
match self.current().token_type {
472517
ShTokenType::Name => Ok(Some(Argument::Name(self.consume_current().lexeme.clone()))),
473518
ShTokenType::DollarSign => {
474519
self.consume(ShTokenType::DollarSign)?;
475-
match self.current().token_type {
476-
ShTokenType::Name => Ok(Some(Argument::Variable(VariableLookup {
477-
name: self.consume_current().lexeme.clone(),
478-
}))),
479-
ShTokenType::LeftParen => Ok(Some(Argument::SubShell(SubShellExpr {
480-
shell: self
481-
.collect_matching(ShTokenType::LeftParen, ShTokenType::RightParen)?,
482-
}))),
483-
ShTokenType::LeftBrace => {
484-
Ok(Some(Argument::Expansion(self.parse_expansion()?)))
485-
}
486-
ShTokenType::DollarSign
487-
| ShTokenType::Bang
488-
| ShTokenType::Star
489-
| ShTokenType::Pound
490-
| ShTokenType::AtSign => Ok(Some(Argument::Variable(VariableLookup {
491-
name: self.consume_current().lexeme.clone(),
492-
}))),
493-
_ => Err("Expected some value after '$'".to_string()),
494-
}
520+
self.parse_variable_name()
495521
}
496522

497523
ShTokenType::BackTickStr => Ok(Some(Argument::SubShell(SubShellExpr {
@@ -518,6 +544,41 @@ impl Parser {
518544
}
519545
}
520546

547+
pub fn parse_double_argument(&mut self) -> Result<Option<Argument>, String> {
548+
let mut argument: Option<Argument> = None;
549+
while let Some(a) = self._parse_double_argument()? {
550+
// while we still have tokens in the assignment we need
551+
// to construct it at run time by creating MergeExprss
552+
// of the various types of arguments strung together.
553+
if argument.is_some() {
554+
let l = argument.take().unwrap();
555+
argument = Some(Argument::Merge(MergeExpr {
556+
left: Box::new(l),
557+
right: Box::new(a),
558+
}));
559+
} else {
560+
argument = Some(a);
561+
}
562+
}
563+
Ok(argument)
564+
}
565+
566+
// helper function for parse_argument that gets exactly one 'primitive' argument
567+
// which may be strung together to create
568+
fn _parse_double_argument(&mut self) -> Result<Option<Argument>, String> {
569+
match self.current().token_type {
570+
ShTokenType::Name => Ok(Some(Argument::Name(self.consume_current().lexeme.clone()))),
571+
ShTokenType::DollarSign => {
572+
self.consume(ShTokenType::DollarSign)?;
573+
self.parse_variable_name()
574+
}
575+
ShTokenType::BackTickStr => Ok(Some(Argument::SubShell(SubShellExpr {
576+
shell: self.consume_current().lexeme.clone(),
577+
}))),
578+
ShTokenType::EndOfFile => Ok(None),
579+
_ => Ok(Some(Argument::Name(self.consume_current().lexeme.clone()))),
580+
}
581+
}
521582
fn skip_whitespace(&mut self) {
522583
while self.current_is(ShTokenType::WhiteSpace) {
523584
self.next_token();

src/parser/tokenizer.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ pub fn is_delimiter(c: char) -> bool {
6767
delimiter_set.contains(&c)
6868
}
6969

70-
pub fn tokens(st: &str) -> Result<Vec<Token>, String> {
70+
pub fn tokens(st: &str, in_quoted_string: bool) -> Result<Vec<Token>, String> {
7171
let mut tokens: Vec<Token> = Vec::new();
7272
let mut current;
7373
let token_map: HashMap<&str, ShTokenType> = HashMap::from([
@@ -149,7 +149,12 @@ pub fn tokens(st: &str) -> Result<Vec<Token>, String> {
149149
token_type: ShTokenType::WhiteSpace,
150150
},
151151
'\\' => {
152-
if let Some(cc) = it.next() {
152+
if in_quoted_string {
153+
Token {
154+
lexeme: String::from("\\"),
155+
token_type: ShTokenType::Name,
156+
}
157+
} else if let Some(cc) = it.next() {
153158
match cc {
154159
'\n' | ' ' => {
155160
continue;
@@ -385,7 +390,7 @@ mod tests {
385390
#[test]
386391
fn basic_tokens() {
387392
let reference_string = "| || > >> < [ [[ ] ]] &&&~${}@*";
388-
let toks = tokens(reference_string).unwrap();
393+
let toks = tokens(reference_string, false).unwrap();
389394
let good_graces = [
390395
Token {
391396
lexeme: String::from("|"),
@@ -498,7 +503,7 @@ mod tests {
498503
#[test]
499504
fn wordy_tokes() {
500505
let reference_string = "if elif else fi while";
501-
let toks = tokens(reference_string).unwrap();
506+
let toks = tokens(reference_string, false).unwrap();
502507
let good_graces = [
503508
Token {
504509
lexeme: String::from("if"),
@@ -543,7 +548,7 @@ mod tests {
543548
#[test]
544549
fn words_adjacent_to_singles() {
545550
let reference_string = "if|while{ elif";
546-
let toks = tokens(reference_string).unwrap();
551+
let toks = tokens(reference_string, false).unwrap();
547552
let good_graces = [
548553
Token {
549554
lexeme: String::from("if"),

test_scripts/variables.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,6 @@ echo $SOME
1111

1212
echo "$SOME$SOME" "$FOO" hello " $VAR"
1313

14+
echo "Hello, ${USER}"
15+
echo "I am, `whoami`; or you may call me $(whoami)"
16+
echo "Hello $SOME$SOME"

0 commit comments

Comments
 (0)