Skip to content

Commit e7ec916

Browse files
authored
Merge pull request #53 from Ph0enixKM/A26
A26 Force programmers to handle shell command and function failures
2 parents 2fcaa46 + c3cfcfe commit e7ec916

File tree

32 files changed

+526
-80
lines changed

32 files changed

+526
-80
lines changed

src/modules/builtin/echo.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,6 @@ impl SyntaxModule<ParserMetadata> for Echo {
2727
impl TranslateModule for Echo {
2828
fn translate(&self, meta: &mut TranslateMetadata) -> String {
2929
let value = self.value.translate(meta);
30-
// If it's a function invocation we want to strip down the inner command
31-
// This way the newline characters in the stdout don't get lost
32-
if self.value.is_child_process() && value.starts_with('$') {
33-
value.get(2..value.len() - 1).unwrap().to_string()
34-
} else {
35-
format!("echo {}", value)
36-
}
30+
format!("echo {}", value)
3731
}
3832
}

src/modules/builtin/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
pub mod echo;
2+
pub mod silent;

src/modules/builtin/silent.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use heraclitus_compiler::prelude::*;
2+
use crate::modules::block::Block;
3+
use crate::modules::statement::stmt::Statement;
4+
use crate::translate::module::TranslateModule;
5+
use crate::utils::metadata::{ParserMetadata, TranslateMetadata};
6+
7+
#[derive(Debug, Clone)]
8+
pub struct Silent {
9+
block: Box<Block>
10+
}
11+
12+
impl SyntaxModule<ParserMetadata> for Silent {
13+
syntax_name!("Silent");
14+
15+
fn new() -> Self {
16+
Silent {
17+
block: Box::new(Block::new())
18+
}
19+
}
20+
21+
fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult {
22+
token(meta, "silent")?;
23+
match token(meta, "{") {
24+
Ok(_) => {
25+
syntax(meta, &mut *self.block)?;
26+
token(meta, "}")?;
27+
},
28+
Err(_) => {
29+
let mut statement = Statement::new();
30+
syntax(meta, &mut statement)?;
31+
self.block.push_statement(statement);
32+
}
33+
}
34+
Ok(())
35+
}
36+
}
37+
38+
impl TranslateModule for Silent {
39+
fn translate(&self, meta: &mut TranslateMetadata) -> String {
40+
meta.silenced = true;
41+
let translated = self.block.translate(meta);
42+
meta.silenced = false;
43+
translated
44+
}
45+
}

src/modules/command/expr.rs

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use heraclitus_compiler::prelude::*;
2-
use crate::{utils::{ParserMetadata, TranslateMetadata}, modules::types::{Type, Typed}};
2+
use crate::{utils::{ParserMetadata, TranslateMetadata}, modules::{types::{Type, Typed}, condition::failed::Failed}};
33
use crate::modules::expression::expr::Expr;
44
use crate::translate::module::TranslateModule;
55

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

1415
impl Typed for CommandExpr {
@@ -23,13 +24,22 @@ impl SyntaxModule<ParserMetadata> for CommandExpr {
2324
fn new() -> Self {
2425
CommandExpr {
2526
strings: vec![],
26-
interps: vec![]
27+
interps: vec![],
28+
failed: Failed::new()
2729
}
2830
}
2931

3032
fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult {
33+
let tok = meta.get_current_token();
3134
(self.strings, self.interps) = parse_interpolated_region(meta, '$')?;
32-
Ok(())
35+
match syntax(meta, &mut self.failed) {
36+
Ok(_) => Ok(()),
37+
Err(Failure::Quiet(_)) => error!(meta, tok => {
38+
message: "Every command statement must handle failed execution",
39+
comment: "You can use '?' in the end to fail the exit code of the command"
40+
}),
41+
Err(err) => Err(err)
42+
}
3343
}
3444
}
3545

@@ -39,6 +49,16 @@ impl TranslateModule for CommandExpr {
3949
let interps = self.interps.iter()
4050
.map(|item| item.translate(meta))
4151
.collect::<Vec<String>>();
42-
format!("$({})", translate_interpolated_region(self.strings.clone(), interps, false))
52+
let failed = self.failed.translate(meta);
53+
if failed.is_empty() {
54+
format!("$({})", translate_interpolated_region(self.strings.clone(), interps, false))
55+
} else {
56+
let id = meta.gen_value_id();
57+
let quote = meta.gen_quote();
58+
let translation = translate_interpolated_region(self.strings.clone(), interps, false);
59+
meta.stmt_queue.push_back(format!("__AMBER_VAL_{id}=$({translation})"));
60+
meta.stmt_queue.push_back(failed);
61+
format!("{quote}${{__AMBER_VAL_{id}}}{quote}")
62+
}
4363
}
4464
}

src/modules/command/statement.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use heraclitus_compiler::prelude::*;
2-
use crate::{utils::{ParserMetadata, TranslateMetadata}, modules::types::{Type, Typed}};
2+
use crate::{utils::{ParserMetadata, TranslateMetadata}, modules::{types::{Type, Typed}, condition::failed::Failed}};
33
use crate::modules::expression::expr::Expr;
44
use crate::translate::module::TranslateModule;
55

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

1415
impl Typed for CommandStatement {
@@ -23,13 +24,22 @@ impl SyntaxModule<ParserMetadata> for CommandStatement {
2324
fn new() -> Self {
2425
CommandStatement {
2526
strings: vec![],
26-
interps: vec![]
27+
interps: vec![],
28+
failed: Failed::new()
2729
}
2830
}
2931

3032
fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult {
33+
let tok = meta.get_current_token();
3134
(self.strings, self.interps) = parse_interpolated_region(meta, '$')?;
32-
Ok(())
35+
match syntax(meta, &mut self.failed) {
36+
Ok(_) => Ok(()),
37+
Err(Failure::Quiet(_)) => error!(meta, tok => {
38+
message: "Every command statement must handle failed execution",
39+
comment: "You can use '?' in the end to propagate the failure"
40+
}),
41+
Err(err) => Err(err)
42+
}
3343
}
3444
}
3545

@@ -39,12 +49,14 @@ impl TranslateModule for CommandStatement {
3949
let interps = self.interps.iter()
4050
.map(|item| item.translate(meta))
4151
.collect::<Vec<String>>();
52+
let failed = self.failed.translate(meta);
4253
let mut translation = translate_interpolated_region(self.strings.clone(), interps, false);
54+
let silent = meta.gen_silent();
4355
// Strip down all the inner command interpolations [A32]
4456
while translation.starts_with("$(") {
4557
let end = translation.len() - 1;
4658
translation = translation.get(2..end).unwrap().to_string();
4759
}
48-
translation
60+
format!("{translation}{silent}\n{failed}").trim_end().to_string()
4961
}
5062
}

src/modules/condition/failed.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use heraclitus_compiler::prelude::*;
2+
use crate::modules::block::Block;
3+
use crate::modules::statement::stmt::Statement;
4+
use crate::translate::module::TranslateModule;
5+
use crate::utils::metadata::{ParserMetadata, TranslateMetadata};
6+
7+
#[derive(Debug, Clone)]
8+
pub struct Failed {
9+
is_parsed: bool,
10+
is_question_mark: bool,
11+
is_main: bool,
12+
block: Box<Block>
13+
}
14+
15+
impl SyntaxModule<ParserMetadata> for Failed {
16+
syntax_name!("Failed Expression");
17+
18+
fn new() -> Self {
19+
Failed {
20+
is_parsed: false,
21+
is_question_mark: false,
22+
is_main: false,
23+
block: Box::new(Block::new())
24+
}
25+
}
26+
27+
fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult {
28+
let tok = meta.get_current_token();
29+
if let Ok(_) = token(meta, "?") {
30+
if !meta.context.is_fun_ctx && !meta.context.is_main_ctx {
31+
return error!(meta, tok, "The '?' operator can only be used in the main block or function body")
32+
}
33+
self.is_question_mark = true;
34+
self.is_main = meta.context.is_main_ctx;
35+
self.is_parsed = true;
36+
return Ok(())
37+
}
38+
token(meta, "failed")?;
39+
match token(meta, "{") {
40+
Ok(_) => {
41+
syntax(meta, &mut *self.block)?;
42+
token(meta, "}")?;
43+
},
44+
Err(_) => {
45+
token(meta, "=>")?;
46+
let mut statement = Statement::new();
47+
syntax(meta, &mut statement)?;
48+
self.block.push_statement(statement);
49+
}
50+
}
51+
self.is_main = meta.context.is_main_ctx;
52+
self.is_parsed = true;
53+
Ok(())
54+
}
55+
}
56+
57+
impl TranslateModule for Failed {
58+
fn translate(&self, meta: &mut TranslateMetadata) -> String {
59+
if self.is_parsed {
60+
let block = self.block.translate(meta);
61+
let ret = self.is_main
62+
.then(|| "exit $?")
63+
.unwrap_or("return $?");
64+
// the condition of '$?' clears the status code thus we need to store it in a variable
65+
if self.is_question_mark {
66+
vec![
67+
"__AMBER_STATUS=$?;",
68+
"if [ $__AMBER_STATUS != 0 ]; then",
69+
&format!("$(exit $__AMBER_STATUS)"),
70+
ret,
71+
"fi"
72+
].join("\n")
73+
} else {
74+
vec![
75+
"__AMBER_STATUS=$?;",
76+
"if [ $__AMBER_STATUS != 0 ]; then",
77+
&format!("$(exit $__AMBER_STATUS)"),
78+
&block,
79+
"fi"
80+
].join("\n")
81+
}
82+
} else {
83+
String::new()
84+
}
85+
}
86+
}

src/modules/condition/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
pub mod ifcond;
22
pub mod ifchain;
3-
pub mod ternary;
3+
pub mod ternary;
4+
pub mod failed;

src/modules/expression/binop/add.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ impl TranslateModule for Add {
4747
fn translate(&self, meta: &mut TranslateMetadata) -> String {
4848
let left = self.left.translate_eval(meta, false);
4949
let right = self.right.translate_eval(meta, false);
50-
let quote = meta.quote();
50+
let quote = meta.gen_quote();
5151
match self.kind {
5252
Type::Array(_) => {
5353
let id = meta.gen_array_id();

src/modules/expression/expr.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ use super::literal::{
88
text::Text,
99
array::Array,
1010
range::Range,
11-
null::Null
11+
null::Null,
12+
status::Status
1213
};
1314
use super::binop::{
1415
add::Add,
@@ -63,7 +64,8 @@ pub enum ExprType {
6364
Array(Array),
6465
Range(Range),
6566
Null(Null),
66-
Cast(Cast)
67+
Cast(Cast),
68+
Status(Status)
6769
}
6870

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

8183
impl Expr {
82-
pub fn is_child_process(&self) -> bool {
83-
matches!(self.value, Some(ExprType::CommandExpr(_)))
84-
}
85-
8684
pub fn is_var(&self) -> bool {
8785
matches!(self.value, Some(ExprType::VariableGet(_)))
8886
}
@@ -107,7 +105,7 @@ impl Expr {
107105
// Unary operators
108106
Cast, Not,
109107
// Literals
110-
Range, Parenthesis, CommandExpr, Bool, Number, Text, Array, Null,
108+
Range, Parenthesis, CommandExpr, Bool, Number, Text, Array, Null, Status,
111109
// Function invocation
112110
FunctionInvocation,
113111
// Variable access

src/modules/expression/literal/array.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ impl TranslateModule for Array {
8383
fn translate(&self, meta: &mut TranslateMetadata) -> String {
8484
let name = format!("__AMBER_ARRAY_{}", meta.gen_array_id());
8585
let args = self.exprs.iter().map(|expr| expr.translate_eval(meta, false)).collect::<Vec<String>>().join(" ");
86-
let quote = meta.quote();
86+
let quote = meta.gen_quote();
8787
meta.stmt_queue.push_back(format!("{name}=({args})"));
8888
format!("{quote}${{{name}[@]}}{quote}")
8989
}

0 commit comments

Comments
 (0)