Skip to content

Commit

Permalink
Merge pull request #53 from Ph0enixKM/A26
Browse files Browse the repository at this point in the history
A26 Force programmers to handle shell command and function failures
  • Loading branch information
Ph0enixKM committed Dec 23, 2022
2 parents 2fcaa46 + c3cfcfe commit e7ec916
Show file tree
Hide file tree
Showing 32 changed files with 526 additions and 80 deletions.
8 changes: 1 addition & 7 deletions src/modules/builtin/echo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,6 @@ impl SyntaxModule<ParserMetadata> for Echo {
impl TranslateModule for Echo {
fn translate(&self, meta: &mut TranslateMetadata) -> String {
let value = self.value.translate(meta);
// If it's a function invocation we want to strip down the inner command
// This way the newline characters in the stdout don't get lost
if self.value.is_child_process() && value.starts_with('$') {
value.get(2..value.len() - 1).unwrap().to_string()
} else {
format!("echo {}", value)
}
format!("echo {}", value)
}
}
1 change: 1 addition & 0 deletions src/modules/builtin/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod echo;
pub mod silent;
45 changes: 45 additions & 0 deletions src/modules/builtin/silent.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use heraclitus_compiler::prelude::*;
use crate::modules::block::Block;
use crate::modules::statement::stmt::Statement;
use crate::translate::module::TranslateModule;
use crate::utils::metadata::{ParserMetadata, TranslateMetadata};

#[derive(Debug, Clone)]
pub struct Silent {
block: Box<Block>
}

impl SyntaxModule<ParserMetadata> for Silent {
syntax_name!("Silent");

fn new() -> Self {
Silent {
block: Box::new(Block::new())
}
}

fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult {
token(meta, "silent")?;
match token(meta, "{") {
Ok(_) => {
syntax(meta, &mut *self.block)?;
token(meta, "}")?;
},
Err(_) => {
let mut statement = Statement::new();
syntax(meta, &mut statement)?;
self.block.push_statement(statement);
}
}
Ok(())
}
}

impl TranslateModule for Silent {
fn translate(&self, meta: &mut TranslateMetadata) -> String {
meta.silenced = true;
let translated = self.block.translate(meta);
meta.silenced = false;
translated
}
}
30 changes: 25 additions & 5 deletions src/modules/command/expr.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use heraclitus_compiler::prelude::*;
use crate::{utils::{ParserMetadata, TranslateMetadata}, modules::types::{Type, Typed}};
use crate::{utils::{ParserMetadata, TranslateMetadata}, modules::{types::{Type, Typed}, condition::failed::Failed}};
use crate::modules::expression::expr::Expr;
use crate::translate::module::TranslateModule;

Expand All @@ -8,7 +8,8 @@ use crate::modules::expression::literal::{parse_interpolated_region, translate_i
#[derive(Debug, Clone)]
pub struct CommandExpr {
strings: Vec<String>,
interps: Vec<Expr>
interps: Vec<Expr>,
failed: Failed
}

impl Typed for CommandExpr {
Expand All @@ -23,13 +24,22 @@ impl SyntaxModule<ParserMetadata> for CommandExpr {
fn new() -> Self {
CommandExpr {
strings: vec![],
interps: vec![]
interps: vec![],
failed: Failed::new()
}
}

fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult {
let tok = meta.get_current_token();
(self.strings, self.interps) = parse_interpolated_region(meta, '$')?;
Ok(())
match syntax(meta, &mut self.failed) {
Ok(_) => Ok(()),
Err(Failure::Quiet(_)) => error!(meta, tok => {
message: "Every command statement must handle failed execution",
comment: "You can use '?' in the end to fail the exit code of the command"
}),
Err(err) => Err(err)
}
}
}

Expand All @@ -39,6 +49,16 @@ impl TranslateModule for CommandExpr {
let interps = self.interps.iter()
.map(|item| item.translate(meta))
.collect::<Vec<String>>();
format!("$({})", translate_interpolated_region(self.strings.clone(), interps, false))
let failed = self.failed.translate(meta);
if failed.is_empty() {
format!("$({})", translate_interpolated_region(self.strings.clone(), interps, false))
} else {
let id = meta.gen_value_id();
let quote = meta.gen_quote();
let translation = translate_interpolated_region(self.strings.clone(), interps, false);
meta.stmt_queue.push_back(format!("__AMBER_VAL_{id}=$({translation})"));
meta.stmt_queue.push_back(failed);
format!("{quote}${{__AMBER_VAL_{id}}}{quote}")
}
}
}
22 changes: 17 additions & 5 deletions src/modules/command/statement.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use heraclitus_compiler::prelude::*;
use crate::{utils::{ParserMetadata, TranslateMetadata}, modules::types::{Type, Typed}};
use crate::{utils::{ParserMetadata, TranslateMetadata}, modules::{types::{Type, Typed}, condition::failed::Failed}};
use crate::modules::expression::expr::Expr;
use crate::translate::module::TranslateModule;

Expand All @@ -8,7 +8,8 @@ use crate::modules::expression::literal::{parse_interpolated_region, translate_i
#[derive(Debug, Clone)]
pub struct CommandStatement {
strings: Vec<String>,
interps: Vec<Expr>
interps: Vec<Expr>,
failed: Failed
}

impl Typed for CommandStatement {
Expand All @@ -23,13 +24,22 @@ impl SyntaxModule<ParserMetadata> for CommandStatement {
fn new() -> Self {
CommandStatement {
strings: vec![],
interps: vec![]
interps: vec![],
failed: Failed::new()
}
}

fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult {
let tok = meta.get_current_token();
(self.strings, self.interps) = parse_interpolated_region(meta, '$')?;
Ok(())
match syntax(meta, &mut self.failed) {
Ok(_) => Ok(()),
Err(Failure::Quiet(_)) => error!(meta, tok => {
message: "Every command statement must handle failed execution",
comment: "You can use '?' in the end to propagate the failure"
}),
Err(err) => Err(err)
}
}
}

Expand All @@ -39,12 +49,14 @@ impl TranslateModule for CommandStatement {
let interps = self.interps.iter()
.map(|item| item.translate(meta))
.collect::<Vec<String>>();
let failed = self.failed.translate(meta);
let mut translation = translate_interpolated_region(self.strings.clone(), interps, false);
let silent = meta.gen_silent();
// Strip down all the inner command interpolations [A32]
while translation.starts_with("$(") {
let end = translation.len() - 1;
translation = translation.get(2..end).unwrap().to_string();
}
translation
format!("{translation}{silent}\n{failed}").trim_end().to_string()
}
}
86 changes: 86 additions & 0 deletions src/modules/condition/failed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use heraclitus_compiler::prelude::*;
use crate::modules::block::Block;
use crate::modules::statement::stmt::Statement;
use crate::translate::module::TranslateModule;
use crate::utils::metadata::{ParserMetadata, TranslateMetadata};

#[derive(Debug, Clone)]
pub struct Failed {
is_parsed: bool,
is_question_mark: bool,
is_main: bool,
block: Box<Block>
}

impl SyntaxModule<ParserMetadata> for Failed {
syntax_name!("Failed Expression");

fn new() -> Self {
Failed {
is_parsed: false,
is_question_mark: false,
is_main: false,
block: Box::new(Block::new())
}
}

fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult {
let tok = meta.get_current_token();
if let Ok(_) = token(meta, "?") {
if !meta.context.is_fun_ctx && !meta.context.is_main_ctx {
return error!(meta, tok, "The '?' operator can only be used in the main block or function body")
}
self.is_question_mark = true;
self.is_main = meta.context.is_main_ctx;
self.is_parsed = true;
return Ok(())
}
token(meta, "failed")?;
match token(meta, "{") {
Ok(_) => {
syntax(meta, &mut *self.block)?;
token(meta, "}")?;
},
Err(_) => {
token(meta, "=>")?;
let mut statement = Statement::new();
syntax(meta, &mut statement)?;
self.block.push_statement(statement);
}
}
self.is_main = meta.context.is_main_ctx;
self.is_parsed = true;
Ok(())
}
}

impl TranslateModule for Failed {
fn translate(&self, meta: &mut TranslateMetadata) -> String {
if self.is_parsed {
let block = self.block.translate(meta);
let ret = self.is_main
.then(|| "exit $?")
.unwrap_or("return $?");
// the condition of '$?' clears the status code thus we need to store it in a variable
if self.is_question_mark {
vec![
"__AMBER_STATUS=$?;",
"if [ $__AMBER_STATUS != 0 ]; then",
&format!("$(exit $__AMBER_STATUS)"),
ret,
"fi"
].join("\n")
} else {
vec![
"__AMBER_STATUS=$?;",
"if [ $__AMBER_STATUS != 0 ]; then",
&format!("$(exit $__AMBER_STATUS)"),
&block,
"fi"
].join("\n")
}
} else {
String::new()
}
}
}
3 changes: 2 additions & 1 deletion src/modules/condition/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod ifcond;
pub mod ifchain;
pub mod ternary;
pub mod ternary;
pub mod failed;
2 changes: 1 addition & 1 deletion src/modules/expression/binop/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ impl TranslateModule for Add {
fn translate(&self, meta: &mut TranslateMetadata) -> String {
let left = self.left.translate_eval(meta, false);
let right = self.right.translate_eval(meta, false);
let quote = meta.quote();
let quote = meta.gen_quote();
match self.kind {
Type::Array(_) => {
let id = meta.gen_array_id();
Expand Down
12 changes: 5 additions & 7 deletions src/modules/expression/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use super::literal::{
text::Text,
array::Array,
range::Range,
null::Null
null::Null,
status::Status
};
use super::binop::{
add::Add,
Expand Down Expand Up @@ -63,7 +64,8 @@ pub enum ExprType {
Array(Array),
Range(Range),
Null(Null),
Cast(Cast)
Cast(Cast),
Status(Status)
}

#[derive(Debug, Clone)]
Expand All @@ -79,10 +81,6 @@ impl Typed for Expr {
}

impl Expr {
pub fn is_child_process(&self) -> bool {
matches!(self.value, Some(ExprType::CommandExpr(_)))
}

pub fn is_var(&self) -> bool {
matches!(self.value, Some(ExprType::VariableGet(_)))
}
Expand All @@ -107,7 +105,7 @@ impl Expr {
// Unary operators
Cast, Not,
// Literals
Range, Parenthesis, CommandExpr, Bool, Number, Text, Array, Null,
Range, Parenthesis, CommandExpr, Bool, Number, Text, Array, Null, Status,
// Function invocation
FunctionInvocation,
// Variable access
Expand Down
2 changes: 1 addition & 1 deletion src/modules/expression/literal/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ impl TranslateModule for Array {
fn translate(&self, meta: &mut TranslateMetadata) -> String {
let name = format!("__AMBER_ARRAY_{}", meta.gen_array_id());
let args = self.exprs.iter().map(|expr| expr.translate_eval(meta, false)).collect::<Vec<String>>().join(" ");
let quote = meta.quote();
let quote = meta.gen_quote();
meta.stmt_queue.push_back(format!("{name}=({args})"));
format!("{quote}${{{name}[@]}}{quote}")
}
Expand Down
15 changes: 13 additions & 2 deletions src/modules/expression/literal/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod text;
pub mod null;
pub mod array;
pub mod range;
pub mod status;

pub fn parse_interpolated_region(meta: &mut ParserMetadata, letter: char) -> Result<(Vec<String>, Vec<Expr>), Failure> {
let mut strings = vec![];
Expand Down Expand Up @@ -97,11 +98,21 @@ fn translate_escaped_string(string: String, is_str: bool) -> String {
chars.next();
},
Some('\'') => {
result.push('\'');
if is_str {
result.push('\'');
} else {
result.push('\\');
result.push('\'');
}
chars.next();
},
Some('\"') => {
result.push('\"');
if is_str {
result.push('\"');
} else {
result.push('\\');
result.push('\"');
}
chars.next();
},
Some('\\') => {
Expand Down
11 changes: 10 additions & 1 deletion src/modules/expression/literal/number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,16 @@ impl SyntaxModule<ParserMetadata> for Number {
if let Ok(sym) = token(meta, "-") {
self.value.push_str(&sym);
}
self.value += &number(meta, vec![])?;
if let Ok(value) = number(meta, vec![]) {
self.value.push_str(&value);
}
if let Ok(sym) = token(meta, ".") {
self.value.push_str(&sym);
self.value.push_str(&number(meta, vec![])?);
}
if self.value.is_empty() {
return Err(Failure::Quiet(PositionInfo::from_metadata(meta)))
}
Ok(())
}
}
Expand Down
Loading

0 comments on commit e7ec916

Please sign in to comment.