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

Declaration utility #437

Closed
wants to merge 3 commits into from
Closed
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
6 changes: 5 additions & 1 deletion yash-semantics/src/command/simple_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,11 @@ use yash_syntax::syntax::Assign;
/// detail semantics may differ in other shell implementations.
impl Command for syntax::SimpleCommand {
async fn execute(&self, env: &mut Env) -> Result {
let (fields, exit_status) = match expand_words(env, &self.words).await {
let words = self.words.iter().map(|word| match word {
syntax::SimpleCommandWord::Regular(word) => word,
syntax::SimpleCommandWord::Assign(_) => todo!("expand assignment word"),
});
let (fields, exit_status) = match expand_words(env, words).await {
Ok(result) => result,
Err(error) => return error.handle(env).await,
};
Expand Down
5 changes: 2 additions & 3 deletions yash-syntax/src/parser/compound_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,7 @@ mod tests {
use crate::alias::{AliasSet, EmptyGlossary, HashEntry};
use crate::source::Location;
use crate::source::Source;
use crate::syntax::Command;
use crate::syntax::SimpleCommand;
use crate::syntax::{Command, SimpleCommand, SimpleCommandWord};
use assert_matches::assert_matches;
use futures_util::FutureExt;

Expand Down Expand Up @@ -253,7 +252,7 @@ mod tests {
let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
let c = SimpleCommand {
assigns: vec![],
words: vec!["foo".parse().unwrap()],
words: vec![SimpleCommandWord::Regular("foo".parse().unwrap())],
redirs: vec![].into(),
};

Expand Down
6 changes: 5 additions & 1 deletion yash-syntax/src/parser/from_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,13 @@ impl FromStr for Assign {
match c.assigns.pop() {
Some(last) if c.assigns.is_empty() => {
if let Some(word) = c.words.pop() {
let location = match word {
SimpleCommandWord::Regular(word) => word.location,
SimpleCommandWord::Assign(assign) => assign.location,
};
Err(Some(Error {
cause: ErrorCause::Syntax(SyntaxError::RedundantToken),
location: word.location,
location,
}))
} else if let Some(redir) = c.redirs.first() {
Err(Some(Error {
Expand Down
14 changes: 9 additions & 5 deletions yash-syntax/src/parser/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use super::lex::TokenId::{Operator, Token};
use crate::syntax::Command;
use crate::syntax::FunctionDefinition;
use crate::syntax::SimpleCommand;
use crate::syntax::SimpleCommandWord;
use std::rc::Rc;

impl Parser<'_, '_> {
Expand Down Expand Up @@ -56,6 +57,9 @@ impl Parser<'_, '_> {

let name = intro.words.pop().unwrap();
debug_assert!(intro.is_empty());
let SimpleCommandWord::Regular(name) = name else {
todo!("what if the name is not a regular word?");
};
// TODO reject invalid name if POSIXly-correct

loop {
Expand Down Expand Up @@ -124,7 +128,7 @@ mod tests {
let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
let c = SimpleCommand {
assigns: vec![],
words: vec!["foo".parse().unwrap()],
words: vec![SimpleCommandWord::Regular("foo".parse().unwrap())],
redirs: vec![].into(),
};

Expand All @@ -141,7 +145,7 @@ mod tests {
let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
let c = SimpleCommand {
assigns: vec![],
words: vec!["foo".parse().unwrap()],
words: vec![SimpleCommandWord::Regular("foo".parse().unwrap())],
redirs: vec![].into(),
};

Expand All @@ -163,7 +167,7 @@ mod tests {
let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
let c = SimpleCommand {
assigns: vec![],
words: vec!["foo".parse().unwrap()],
words: vec![SimpleCommandWord::Regular("foo".parse().unwrap())],
redirs: vec![].into(),
};

Expand All @@ -185,7 +189,7 @@ mod tests {
let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
let c = SimpleCommand {
assigns: vec![],
words: vec!["foo".parse().unwrap()],
words: vec![SimpleCommandWord::Regular("foo".parse().unwrap())],
redirs: vec![].into(),
};

Expand Down Expand Up @@ -304,7 +308,7 @@ mod tests {
let mut parser = Parser::new(&mut lexer, &aliases);
let c = SimpleCommand {
assigns: vec![],
words: vec!["f".parse().unwrap()],
words: vec![SimpleCommandWord::Regular("f".parse().unwrap())],
redirs: vec![].into(),
};

Expand Down
7 changes: 4 additions & 3 deletions yash-syntax/src/parser/simple_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@ use crate::syntax::Assign;
use crate::syntax::Redir;
use crate::syntax::Scalar;
use crate::syntax::SimpleCommand;
use crate::syntax::SimpleCommandWord;
use crate::syntax::Word;

/// Simple command builder.
#[derive(Default)]
struct Builder {
assigns: Vec<Assign>,
words: Vec<Word>,
words: Vec<SimpleCommandWord>,
redirs: Vec<Redir>,
}

Expand Down Expand Up @@ -123,13 +124,13 @@ impl Parser<'_, '_> {

// Tell assignment from word
if !result.words.is_empty() {
result.words.push(token.word);
result.words.push(SimpleCommandWord::Regular(token.word));
continue;
}
let mut assign = match Assign::try_from(token.word) {
Ok(assign) => assign,
Err(word) => {
result.words.push(word);
result.words.push(SimpleCommandWord::Regular(word));
continue;
}
};
Expand Down
30 changes: 28 additions & 2 deletions yash-syntax/src/syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -581,14 +581,40 @@ impl Redir {
}
}

/// Word of a simple command
///
/// This enum represents a word that appears in a [`SimpleCommand`]. Most words
/// are regular words, but some words are expanded as if they were assignments.
/// For example, in the command `export PATH FOO=bar`, `PATH` is a regular word
/// and `FOO=bar` is an assignment word.
///
/// Note that any assignments preceding the command words are represented as
/// [`Assign`]s in the [`SimpleCommand`] struct, not as [`SimpleCommandWord`]s.
/// For example, in the command `FOO=bar printenv FOO`, `FOO=bar` is an
/// assignment and `printenv` and `FOO` are command words.
///
/// A word is considered an assignment word if it can be converted to an
/// [`Assign`] and if it follows another word that names a declaration utility.
/// (TODO: What commands are declaration utilities?)
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum SimpleCommandWord {
/// Normal word
Regular(Word),
/// Word to be expanded in assignment context
Assign(Assign),
}

/// Command that involves assignments, redirections, and word expansions
///
/// In the shell language syntax, a valid simple command must contain at least one of assignments,
/// redirections, and words. The parser must not produce a completely empty simple command.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SimpleCommand {
/// Assignments
pub assigns: Vec<Assign>,
pub words: Vec<Word>,
/// Main command words
pub words: Vec<SimpleCommandWord>,
/// Redirections
pub redirs: Rc<Vec<Redir>>,
}

Expand All @@ -607,7 +633,7 @@ impl SimpleCommand {
/// Tests whether the first word of the simple command is a keyword.
#[must_use]
fn first_word_is_keyword(&self) -> bool {
let Some(word) = self.words.first() else {
let Some(SimpleCommandWord::Regular(word)) = self.words.first() else {
return false;
};
let Some(literal) = word.to_string_if_literal() else {
Expand Down
28 changes: 25 additions & 3 deletions yash-syntax/src/syntax/impl_display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,15 @@ impl fmt::Display for Redir {
}
}

impl fmt::Display for SimpleCommandWord {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SimpleCommandWord::Regular(word) => word.fmt(f),
SimpleCommandWord::Assign(assign) => assign.fmt(f),
}
}
}

impl fmt::Display for SimpleCommand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let i1 = self.assigns.iter().map(|x| x as &dyn fmt::Display);
Expand Down Expand Up @@ -723,6 +732,15 @@ mod tests {
assert_eq!(redir.to_string(), "9<<END");
}

#[test]
fn simple_command_word_display() {
let word = SimpleCommandWord::Regular(Word::from_str("echo").unwrap());
assert_eq!(word.to_string(), "echo");

let word = SimpleCommandWord::Assign(Assign::from_str("foo=bar").unwrap());
assert_eq!(word.to_string(), "foo=bar");
}

#[test]
fn simple_command_display() {
let mut command = SimpleCommand {
Expand All @@ -742,10 +760,14 @@ mod tests {
.push(Assign::from_str("hello=world").unwrap());
assert_eq!(command.to_string(), "name=value hello=world");

command.words.push(Word::from_str("echo").unwrap());
command
.words
.push(SimpleCommandWord::Regular(Word::from_str("echo").unwrap()));
assert_eq!(command.to_string(), "name=value hello=world echo");

command.words.push(Word::from_str("foo").unwrap());
command
.words
.push(SimpleCommandWord::Regular(Word::from_str("foo").unwrap()));
assert_eq!(command.to_string(), "name=value hello=world echo foo");

Rc::make_mut(&mut command.redirs).push(Redir {
Expand Down Expand Up @@ -782,7 +804,7 @@ mod tests {
fn simple_command_display_with_keyword() {
let command = SimpleCommand {
assigns: vec![],
words: vec!["if".parse().unwrap()],
words: vec![SimpleCommandWord::Regular("if".parse().unwrap())],
redirs: vec!["<foo".parse().unwrap()].into(),
};
assert_eq!(command.to_string(), "<foo if");
Expand Down
Loading