From c3cfcfe04b34982dfce37653dfafd9bd7e732e6a Mon Sep 17 00:00:00 2001 From: Ph0enixKM Date: Fri, 23 Dec 2022 17:43:30 +0100 Subject: [PATCH] feat: add silent keyword, fail, status, failed --- src/modules/builtin/echo.rs | 8 +- src/modules/builtin/mod.rs | 1 + src/modules/builtin/silent.rs | 45 +++++++++++ src/modules/command/expr.rs | 30 +++++-- src/modules/command/statement.rs | 22 ++++-- src/modules/condition/failed.rs | 86 ++++++++++++++++++++ src/modules/condition/mod.rs | 3 +- src/modules/expression/binop/add.rs | 2 +- src/modules/expression/expr.rs | 12 ++- src/modules/expression/literal/array.rs | 2 +- src/modules/expression/literal/mod.rs | 15 +++- src/modules/expression/literal/number.rs | 11 ++- src/modules/expression/literal/status.rs | 32 ++++++++ src/modules/expression/literal/text.rs | 2 +- src/modules/expression/unop/cast.rs | 11 ++- src/modules/function/declaration.rs | 5 +- src/modules/function/declaration_utils.rs | 7 +- src/modules/function/fail.rs | 82 +++++++++++++++++++ src/modules/function/invocation.rs | 28 ++++++- src/modules/function/mod.rs | 3 +- src/modules/main.rs | 2 + src/modules/mod.rs | 2 +- src/modules/statement/stmt.rs | 16 ++-- src/modules/variable/get.rs | 2 +- src/modules/variable/init.rs | 14 +++- src/modules/variable/mod.rs | 10 ++- src/rules.rs | 7 +- src/std/main.ab | 21 +++-- src/tests/validity.rs | 96 ++++++++++++++++++++--- src/utils/context.rs | 7 +- src/utils/function_interface.rs | 4 +- src/utils/metadata/translate.rs | 18 ++++- 32 files changed, 526 insertions(+), 80 deletions(-) create mode 100644 src/modules/builtin/silent.rs create mode 100644 src/modules/condition/failed.rs create mode 100644 src/modules/expression/literal/status.rs create mode 100644 src/modules/function/fail.rs diff --git a/src/modules/builtin/echo.rs b/src/modules/builtin/echo.rs index d6240492..86918550 100644 --- a/src/modules/builtin/echo.rs +++ b/src/modules/builtin/echo.rs @@ -27,12 +27,6 @@ impl SyntaxModule 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) } } diff --git a/src/modules/builtin/mod.rs b/src/modules/builtin/mod.rs index a1b829e5..2960ec8e 100644 --- a/src/modules/builtin/mod.rs +++ b/src/modules/builtin/mod.rs @@ -1 +1,2 @@ pub mod echo; +pub mod silent; \ No newline at end of file diff --git a/src/modules/builtin/silent.rs b/src/modules/builtin/silent.rs new file mode 100644 index 00000000..b9d03ab3 --- /dev/null +++ b/src/modules/builtin/silent.rs @@ -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 +} + +impl SyntaxModule 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 + } +} \ No newline at end of file diff --git a/src/modules/command/expr.rs b/src/modules/command/expr.rs index 935c3862..c52c5421 100644 --- a/src/modules/command/expr.rs +++ b/src/modules/command/expr.rs @@ -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; @@ -8,7 +8,8 @@ use crate::modules::expression::literal::{parse_interpolated_region, translate_i #[derive(Debug, Clone)] pub struct CommandExpr { strings: Vec, - interps: Vec + interps: Vec, + failed: Failed } impl Typed for CommandExpr { @@ -23,13 +24,22 @@ impl SyntaxModule 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) + } } } @@ -39,6 +49,16 @@ impl TranslateModule for CommandExpr { let interps = self.interps.iter() .map(|item| item.translate(meta)) .collect::>(); - 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}") + } } } \ No newline at end of file diff --git a/src/modules/command/statement.rs b/src/modules/command/statement.rs index d634ec3d..61cfc262 100644 --- a/src/modules/command/statement.rs +++ b/src/modules/command/statement.rs @@ -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; @@ -8,7 +8,8 @@ use crate::modules::expression::literal::{parse_interpolated_region, translate_i #[derive(Debug, Clone)] pub struct CommandStatement { strings: Vec, - interps: Vec + interps: Vec, + failed: Failed } impl Typed for CommandStatement { @@ -23,13 +24,22 @@ impl SyntaxModule 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) + } } } @@ -39,12 +49,14 @@ impl TranslateModule for CommandStatement { let interps = self.interps.iter() .map(|item| item.translate(meta)) .collect::>(); + 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() } } \ No newline at end of file diff --git a/src/modules/condition/failed.rs b/src/modules/condition/failed.rs new file mode 100644 index 00000000..a459c7bd --- /dev/null +++ b/src/modules/condition/failed.rs @@ -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 +} + +impl SyntaxModule 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() + } + } +} \ No newline at end of file diff --git a/src/modules/condition/mod.rs b/src/modules/condition/mod.rs index bdbab8c0..fcb07cac 100644 --- a/src/modules/condition/mod.rs +++ b/src/modules/condition/mod.rs @@ -1,3 +1,4 @@ pub mod ifcond; pub mod ifchain; -pub mod ternary; \ No newline at end of file +pub mod ternary; +pub mod failed; \ No newline at end of file diff --git a/src/modules/expression/binop/add.rs b/src/modules/expression/binop/add.rs index 778ba566..a42b499a 100644 --- a/src/modules/expression/binop/add.rs +++ b/src/modules/expression/binop/add.rs @@ -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(); diff --git a/src/modules/expression/expr.rs b/src/modules/expression/expr.rs index a262de7e..6ac77adc 100644 --- a/src/modules/expression/expr.rs +++ b/src/modules/expression/expr.rs @@ -8,7 +8,8 @@ use super::literal::{ text::Text, array::Array, range::Range, - null::Null + null::Null, + status::Status }; use super::binop::{ add::Add, @@ -63,7 +64,8 @@ pub enum ExprType { Array(Array), Range(Range), Null(Null), - Cast(Cast) + Cast(Cast), + Status(Status) } #[derive(Debug, Clone)] @@ -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(_))) } @@ -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 diff --git a/src/modules/expression/literal/array.rs b/src/modules/expression/literal/array.rs index ed2a2bdf..48ac81da 100644 --- a/src/modules/expression/literal/array.rs +++ b/src/modules/expression/literal/array.rs @@ -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::>().join(" "); - let quote = meta.quote(); + let quote = meta.gen_quote(); meta.stmt_queue.push_back(format!("{name}=({args})")); format!("{quote}${{{name}[@]}}{quote}") } diff --git a/src/modules/expression/literal/mod.rs b/src/modules/expression/literal/mod.rs index 11010d4d..c081bcc9 100644 --- a/src/modules/expression/literal/mod.rs +++ b/src/modules/expression/literal/mod.rs @@ -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, Vec), Failure> { let mut strings = vec![]; @@ -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('\\') => { diff --git a/src/modules/expression/literal/number.rs b/src/modules/expression/literal/number.rs index ce255750..b63bc63e 100644 --- a/src/modules/expression/literal/number.rs +++ b/src/modules/expression/literal/number.rs @@ -26,7 +26,16 @@ impl SyntaxModule 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(()) } } diff --git a/src/modules/expression/literal/status.rs b/src/modules/expression/literal/status.rs new file mode 100644 index 00000000..e3f347d0 --- /dev/null +++ b/src/modules/expression/literal/status.rs @@ -0,0 +1,32 @@ +use heraclitus_compiler::prelude::*; +use crate::{utils::metadata::ParserMetadata, modules::types::{Type, Typed}}; +use crate::translate::module::TranslateModule; +use crate::utils::TranslateMetadata; + +#[derive(Debug, Clone)] +pub struct Status; + +impl Typed for Status { + fn get_type(&self) -> Type { + Type::Num + } +} + +impl SyntaxModule for Status { + syntax_name!("Status"); + + fn new() -> Self { + Status + } + + fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + token(meta, "status")?; + Ok(()) + } +} + +impl TranslateModule for Status { + fn translate(&self, _meta: &mut TranslateMetadata) -> String { + format!("$?") + } +} \ No newline at end of file diff --git a/src/modules/expression/literal/text.rs b/src/modules/expression/literal/text.rs index d81cfbe3..70937476 100644 --- a/src/modules/expression/literal/text.rs +++ b/src/modules/expression/literal/text.rs @@ -39,7 +39,7 @@ impl TranslateModule for Text { let interps = self.interps.iter() .map(|item| item.translate(meta)) .collect::>(); - let quote = meta.quote(); + let quote = meta.gen_quote(); format!("{quote}{}{quote}", translate_interpolated_region(self.strings.clone(), interps, true)) } } \ No newline at end of file diff --git a/src/modules/expression/unop/cast.rs b/src/modules/expression/unop/cast.rs index 3fe3a9fe..054b2d31 100644 --- a/src/modules/expression/unop/cast.rs +++ b/src/modules/expression/unop/cast.rs @@ -36,9 +36,14 @@ impl SyntaxModule for Cast { let message = Message::new_warn_at_token(meta, tok) .message(format!("Casting a value of type '{l_type}' value to a '{r_type}' is not recommended")) .comment(format!("To suppress this warning, use #[{flag_name}] before the parent function declaration")); - match self.kind { - Type::Array(_) | Type::Null => meta.add_message(message), - Type::Num => if self.expr.get_type() == Type::Text { meta.add_message(message) }, + match (l_type, r_type) { + (Type::Array(left), Type::Array(right)) => { + if *left != *right && !matches!(*left, Type::Bool | Type::Num) && !matches!(*right, Type::Bool | Type::Num) { + meta.add_message(message); + } + }, + (Type::Array(_) | Type::Null, Type::Array(_) | Type::Null) => meta.add_message(message), + (Type::Text, Type::Num) => { meta.add_message(message) }, _ => {} } } diff --git a/src/modules/function/declaration.rs b/src/modules/function/declaration.rs index b2686921..661e524c 100644 --- a/src/modules/function/declaration.rs +++ b/src/modules/function/declaration.rs @@ -117,7 +117,7 @@ impl SyntaxModule for FunctionDeclaration { } // Parse the body token(meta, "{")?; - let (index_begin, index_end) = skip_function_body(meta); + let (index_begin, index_end, is_failable) = skip_function_body(meta); // Create a new context with the function body let expr = meta.context.expr[index_begin..index_end].to_vec(); let ctx = meta.context.clone().function_invocation(expr); @@ -130,7 +130,8 @@ impl SyntaxModule for FunctionDeclaration { arg_types: self.arg_types.clone(), arg_refs: self.arg_refs.clone(), returns: self.returns.clone(), - is_public: self.is_public + is_public: self.is_public, + is_failable: is_failable }, ctx)?; // Restore the compiler flags swap(&mut meta.context.cc_flags, &mut flags); diff --git a/src/modules/function/declaration_utils.rs b/src/modules/function/declaration_utils.rs index afa88e00..df69b2f1 100644 --- a/src/modules/function/declaration_utils.rs +++ b/src/modules/function/declaration_utils.rs @@ -6,20 +6,23 @@ use crate::utils::cc_flags::{CCFlags, get_ccflag_name}; use crate::utils::context::Context; use crate::utils::function_interface::FunctionInterface; -pub fn skip_function_body(meta: &mut ParserMetadata) -> (usize, usize) { +pub fn skip_function_body(meta: &mut ParserMetadata) -> (usize, usize, bool) { let index_begin = meta.get_index(); + let mut is_failable = false; let mut scope = 1; while let Some(tok) = meta.get_current_token() { match tok.word.as_str() { "{" => scope += 1, "}" => scope -= 1, + "fail" => is_failable = true, + "?" => is_failable = true, _ => {} } if scope == 0 { break } meta.increment_index(); } let index_end = meta.get_index(); - (index_begin, index_end) + (index_begin, index_end, is_failable) } pub fn handle_existing_function(meta: &mut ParserMetadata, tok: Option) -> Result<(), Failure> { diff --git a/src/modules/function/fail.rs b/src/modules/function/fail.rs new file mode 100644 index 00000000..40c74dde --- /dev/null +++ b/src/modules/function/fail.rs @@ -0,0 +1,82 @@ +use heraclitus_compiler::prelude::*; +use crate::modules::expression::expr::Expr; +use crate::modules::types::{Type, Typed}; +use crate::utils::metadata::{ParserMetadata, TranslateMetadata}; +use crate::translate::module::TranslateModule; + +#[derive(Debug, Clone)] +pub struct Fail { + pub expr: Expr, + pub code: String, + pub is_main: bool +} + +impl Typed for Fail { + fn get_type(&self) -> Type { + self.expr.get_type() + } +} + +impl SyntaxModule for Fail { + syntax_name!("Fail"); + + fn new() -> Self { + Fail { + expr: Expr::new(), + code: String::new(), + is_main: false + } + } + + fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + token(meta, "fail")?; + let tok = meta.get_current_token(); + if !meta.context.is_fun_ctx && !meta.context.is_main_ctx { + return error!(meta, tok => { + message: "Fail statement outside of function or main", + comment: "Fail statements can only be used inside of functions or the main block" + }); + } + self.is_main = meta.context.is_main_ctx; + match integer(meta, vec![]) { + Ok(value) => { + if value == "0" { + return error!(meta, tok => { + message: "Invalid exit code", + comment: "Fail status must be a non-zero integer" + }); + } + self.code = value; + }, + Err(_) => { + match syntax(meta, &mut self.expr) { + Ok(_) => { + if self.expr.get_type() != Type::Num { + return error!(meta, tok => { + message: "Invalid exit code", + comment: "Fail status must be a non-zero integer" + }); + } + }, + Err(_) => { + self.code = "1".to_string(); + } + } + } + } + Ok(()) + } +} + +impl TranslateModule for Fail { + fn translate(&self, meta: &mut TranslateMetadata) -> String { + let translate = self.code.is_empty() + .then(|| self.expr.translate(meta)) + .unwrap_or_else(|| self.code.clone()); + if self.is_main { + format!("exit {translate}") + } else { + format!("return {translate}") + } + } +} \ No newline at end of file diff --git a/src/modules/function/invocation.rs b/src/modules/function/invocation.rs index e0caec11..74f21075 100644 --- a/src/modules/function/invocation.rs +++ b/src/modules/function/invocation.rs @@ -1,11 +1,11 @@ use heraclitus_compiler::prelude::*; use itertools::izip; +use crate::modules::condition::failed::Failed; use crate::modules::types::{Type, Typed}; use crate::modules::variable::variable_name_extensions; use crate::utils::metadata::{ParserMetadata, TranslateMetadata}; use crate::translate::module::TranslateModule; use crate::modules::expression::expr::Expr; - use super::invocation_utils::*; #[derive(Debug, Clone)] @@ -15,7 +15,9 @@ pub struct FunctionInvocation { refs: Vec, kind: Type, variant_id: usize, - id: usize + id: usize, + failed: Failed, + is_failable: bool } impl Typed for FunctionInvocation { @@ -34,7 +36,9 @@ impl SyntaxModule for FunctionInvocation { refs: vec![], kind: Type::Null, variant_id: 0, - id: 0 + id: 0, + failed: Failed::new(), + is_failable: false } } @@ -58,6 +62,17 @@ impl SyntaxModule for FunctionInvocation { }; } let function_unit = meta.get_fun_declaration(&self.name).unwrap().clone(); + self.is_failable = function_unit.is_failable; + if self.is_failable { + match syntax(meta, &mut self.failed) { + Ok(_) => {}, + Err(Failure::Quiet(_)) => return error!(meta, tok => { + message: "This function can fail. Please handle the failure", + comment: "You can use '?' in the end to propagate the failure" + }), + Err(err) => return Err(err) + } + } let types = self.args.iter().map(|e| e.get_type()).collect::>(); let var_names = self.args.iter().map(|e| e.is_var()).collect::>(); self.refs = function_unit.arg_refs.clone(); @@ -69,6 +84,7 @@ impl SyntaxModule for FunctionInvocation { impl TranslateModule for FunctionInvocation { fn translate(&self, meta: &mut TranslateMetadata) -> String { let name = format!("{}__{}_v{}", self.name, self.id, self.variant_id); + let silent = meta.gen_silent(); let args = izip!(self.args.iter(), self.refs.iter()).map(| (arg, is_ref) | { if *is_ref { arg.get_var_translated_name().unwrap() @@ -80,7 +96,11 @@ impl TranslateModule for FunctionInvocation { .unwrap_or_else(|| translation) } }).collect::>().join(" "); - meta.stmt_queue.push_back(format!("{name} {args}")); + meta.stmt_queue.push_back(format!("{name} {args}{silent}")); + if self.is_failable { + let failed = self.failed.translate(meta); + meta.stmt_queue.push_back(failed); + } format!("${{__AMBER_FUN_{}{}_v{}}}", self.name, self.id, self.variant_id) } } \ No newline at end of file diff --git a/src/modules/function/mod.rs b/src/modules/function/mod.rs index d6209357..56605837 100644 --- a/src/modules/function/mod.rs +++ b/src/modules/function/mod.rs @@ -2,4 +2,5 @@ pub mod declaration; pub mod declaration_utils; pub mod invocation; pub mod invocation_utils; -pub mod ret; \ No newline at end of file +pub mod ret; +pub mod fail; \ No newline at end of file diff --git a/src/modules/main.rs b/src/modules/main.rs index a7d0ab04..ed9bd79d 100644 --- a/src/modules/main.rs +++ b/src/modules/main.rs @@ -36,6 +36,7 @@ impl SyntaxModule for Main { self.is_skipped = true; } context!({ + meta.context.is_main_ctx = true; if token(meta, "(").is_ok() { loop { if token(meta, ")").is_ok() { @@ -60,6 +61,7 @@ impl SyntaxModule for Main { // Remove the scope made for variables meta.pop_scope(); token(meta, "}")?; + meta.context.is_main_ctx = false; Ok(()) }, |pos| { error_pos!(meta, pos, "Undefined syntax in main block") diff --git a/src/modules/mod.rs b/src/modules/mod.rs index e1ee646a..45667813 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -39,4 +39,4 @@ macro_rules! handle_types { } } }; -} +} \ No newline at end of file diff --git a/src/modules/statement/stmt.rs b/src/modules/statement/stmt.rs index eaa3781e..e906a8da 100644 --- a/src/modules/statement/stmt.rs +++ b/src/modules/statement/stmt.rs @@ -28,13 +28,17 @@ use crate::modules::loops::{ }; use crate::modules::function::{ declaration::FunctionDeclaration, - ret::Return + ret::Return, + fail::Fail }; use crate::modules::imports::{ import::Import }; use crate::modules::main::Main; -use crate::modules::builtin::echo::Echo; +use crate::modules::builtin::{ + echo::Echo, + silent::Silent +}; #[derive(Debug, Clone)] pub enum StatementType { @@ -55,9 +59,11 @@ pub enum StatementType { Continue(Continue), FunctionDeclaration(FunctionDeclaration), Return(Return), + Fail(Fail), Import(Import), Main(Main), - Echo(Echo) + Echo(Echo), + Silent(Silent) } #[derive(Debug, Clone)] @@ -70,7 +76,7 @@ impl Statement { // Imports Import, // Functions - FunctionDeclaration, Main, Return, + FunctionDeclaration, Main, Return, Fail, // Loops InfiniteLoop, IterLoop, Break, Continue, // Conditions @@ -82,7 +88,7 @@ impl Statement { ShorthandMul, ShorthandDiv, ShorthandModulo, // Command - CommandStatement, Echo, + Silent, CommandStatement, Echo, // Expression Expr ]); diff --git a/src/modules/variable/get.rs b/src/modules/variable/get.rs index 539caadf..e3502b01 100644 --- a/src/modules/variable/get.rs +++ b/src/modules/variable/get.rs @@ -67,7 +67,7 @@ impl TranslateModule for VariableGet { let res = format!("${{{ref_prefix}{name}}}"); // Text variables need to be encapsulated in string literals // Otherwise, they will be "spreaded" into tokens - let quote = meta.quote(); + let quote = meta.gen_quote(); match (self.is_ref, &self.kind) { (false, Type::Array(_)) => match *self.index { Some(ref expr) => format!("{quote}${{{name}[{}]}}{quote}", expr.translate(meta)), diff --git a/src/modules/variable/init.rs b/src/modules/variable/init.rs index 1d5676c8..add24c57 100644 --- a/src/modules/variable/init.rs +++ b/src/modules/variable/init.rs @@ -9,7 +9,8 @@ use super::{variable_name_extensions, handle_identifier_name}; pub struct VariableInit { name: String, expr: Box, - global_id: Option + global_id: Option, + is_fun_ctx: bool } impl VariableInit { @@ -27,7 +28,8 @@ impl SyntaxModule for VariableInit { VariableInit { name: String::new(), expr: Box::new(Expr::new()), - global_id: None + global_id: None, + is_fun_ctx: false } } @@ -41,6 +43,7 @@ impl SyntaxModule for VariableInit { syntax(meta, &mut *self.expr)?; // Add a variable to the memory self.handle_add_variable(meta, &self.name.clone(), self.expr.get_type(), tok)?; + self.is_fun_ctx = meta.context.is_fun_ctx; Ok(()) }, |position| { error_pos!(meta, position, format!("Expected '=' after variable name '{}'", self.name)) @@ -54,10 +57,13 @@ impl TranslateModule for VariableInit { let mut expr = self.expr.translate(meta); if let Type::Array(_) = self.expr.get_type() { expr = format!("({expr})"); - } + } + let local = self.is_fun_ctx + .then(|| "local ") + .unwrap_or_else(|| ""); match self.global_id { Some(id) => format!("__{id}_{name}={expr}"), - None => format!("local {name}={expr}") + None => format!("{local}{name}={expr}") } } } \ No newline at end of file diff --git a/src/modules/variable/mod.rs b/src/modules/variable/mod.rs index 5a3770f9..1ef5c353 100644 --- a/src/modules/variable/mod.rs +++ b/src/modules/variable/mod.rs @@ -22,13 +22,15 @@ pub fn variable_name_keywords() -> Vec<&'static str> { // Control flow keywords "if", "then", "else", // Loop keywords - "loop", "break", "continue", + "loop", "break", "continue", "in", // Module keywords "pub", "import", "from", // Function keywords - "fun", "ret", "ref", "echo", + "fun", "return", "ref", "fail", "failed", // Types - "Text", "Number", "Bool", "Null" + "Text", "Number", "Bool", "Null", + // Misc + "echo", "silent", "status" ] } @@ -58,7 +60,7 @@ fn handle_similar_variable(meta: &mut ParserMetadata, name: &str) -> Option) -> Result<(), Failure> { // Validate if the variable name uses the reserved prefix - if name.chars().take(2).all(|chr| chr == '_') { + if name.chars().take(2).all(|chr| chr == '_') && name.len() > 2 { let new_name = name.get(1..).unwrap(); return error!(meta, tok => { message: format!("Indentifier '{name}' is not allowed"), diff --git a/src/rules.rs b/src/rules.rs index ba564e73..b9a603d0 100644 --- a/src/rules.rs +++ b/src/rules.rs @@ -49,7 +49,12 @@ pub fn get_rules() -> Rules { begin: "//", end: "\n", allow_left_open: true - }) + }), + reg!(interp as "interpolation" => { + begin: "{", + end: "}", + tokenize: true + } ref global) ]; Rules::new(symbols, compounds, region) } \ No newline at end of file diff --git a/src/std/main.ab b/src/std/main.ab index 2938d95f..59018b22 100644 --- a/src/std/main.ab +++ b/src/std/main.ab @@ -1,28 +1,33 @@ pub fun input() { $read$ - echo '$REPLY' + return '$REPLY' } pub fun replace_once(source, pattern, replacement) { - echo $echo "\$\{source/{pattern}/{replacement}}"$ + return $echo "\$\{source/{pattern}/{replacement}}"$ } pub fun replace(source, pattern, replacement) { - echo $echo "\$\{source//{pattern}/{replacement}}"$ + return $echo "\$\{source//{pattern}/{replacement}}"$ } pub fun replace_regex(source: Text, pattern: Text, replacement: Text): Text { - echo $echo "{source}" | sed -e "s/{pattern}/{replacement}/g"$ + return $echo "{source}" | sed -e "s/{pattern}/{replacement}/g"$ } pub fun file_read(path) { - echo $cat "{path}"$ + return $cat "{path}"$ } pub fun file_write(path, content) { - $echo "{content}" > "{path}"$ + return $echo "{content}" > "{path}"$ } pub fun file_append(path, content) { - $echo "{content}" >> "{path}"$ -} \ No newline at end of file + return $echo "{content}" >> "{path}"$ +} + +#[allow_absurd_cast] +pub fun sum(list: [Num]): Num { + return $echo "{list}" | awk '\{s=0; for (i=1; i<=NF; i++) s+=\$i; print s}'$ failed {} as Num +} diff --git a/src/tests/validity.rs b/src/tests/validity.rs index 54ab6d28..4606430f 100644 --- a/src/tests/validity.rs +++ b/src/tests/validity.rs @@ -111,7 +111,7 @@ fn paranthesis() { #[test] fn command_interpolation() { let code = " - echo $echo {$echo Hello World$}$ + echo $echo {$echo Hello World$ failed {}}$ failed {} "; test_amber!(code, "Hello World"); } @@ -119,7 +119,7 @@ fn command_interpolation() { #[test] fn command_inception() { let code = " - ${${${$echo Hello World$}$}$}$ + $echo {$echo {$echo {$echo Hello World$ failed {}}$ failed {}}$ failed {}}$ failed {} "; test_amber!(code, "Hello World"); } @@ -333,14 +333,16 @@ fn ternary_conditional_nested() { fn infinite_loop() { let code = " let a = 0 - loop { - a += 1 - if a == 5 { - continue - } - $printf \"{a} \"$ - if a == 10 { - break + main { + loop { + a += 1 + if a == 5 { + continue + } + $printf \"{a} \"$? + if a == 10 { + break + } } } "; @@ -637,3 +639,77 @@ fn null() { "; test_amber!(code, ""); } + +#[test] +fn failed() { + let code = " + import { sum } from 'std' + let requirements = [true, true, true] + + main { + silent { + $make -v$ failed => requirements[0] = false + $gcc -v$ failed => requirements[1] = false + $you don\\'t have this$ failed => requirements[2] = false + } + + if sum(requirements as [Num]) == 3 { + echo 'All requirements are met' + } else { + echo 'Requirements not met' + fail + } + } + "; + test_amber!(code, "Requirements not met"); +} + +#[test] +fn nested_failed() { + let code = " + fun inner_c() { + echo 'inner_c' + fail + } + + fun inner_b() { + inner_c()? + echo 'inner_b' + } + + fun inner_a() { + inner_b()? + echo 'inner_a' + } + + main { + inner_a()? + echo 'main' + } + "; + test_amber!(code, "inner_c"); +} + +#[test] +fn silent() { + let code = " + main { + silent { + echo 'Hello World' + $non-existant command$? + } + } + "; + test_amber!(code, "Hello World"); +} + +#[test] +fn status() { + let code = " + silent $non-existant command$ failed { + echo status + echo status + } + "; + test_amber!(code, "127\n0"); +} \ No newline at end of file diff --git a/src/utils/context.rs b/src/utils/context.rs index c5d880f8..872c23fd 100644 --- a/src/utils/context.rs +++ b/src/utils/context.rs @@ -13,6 +13,7 @@ pub struct FunctionDecl { pub returns: Type, pub is_args_typed: bool, pub is_public: bool, + pub is_failable: bool, pub id: usize } @@ -25,7 +26,8 @@ impl FunctionDecl { arg_types: self.arg_types, arg_refs: self.arg_refs, returns: self.returns, - is_public: self.is_public + is_public: self.is_public, + is_failable: self.is_failable } } } @@ -105,6 +107,8 @@ pub struct Context { pub is_fun_ctx: bool, /// Determines if the context is in a loop pub is_loop_ctx: bool, + /// Determines if the context is in the main block + pub is_main_ctx: bool, /// This is a list of ids of all the public functions in the file pub pub_funs: Vec, /// The return type of the currently parsed function @@ -124,6 +128,7 @@ impl Context { trace: vec![], is_fun_ctx: false, is_loop_ctx: false, + is_main_ctx: false, pub_funs: vec![], fun_ret_type: None, cc_flags: HashSet::new() diff --git a/src/utils/function_interface.rs b/src/utils/function_interface.rs index 69dd397d..cac051d1 100644 --- a/src/utils/function_interface.rs +++ b/src/utils/function_interface.rs @@ -11,7 +11,8 @@ pub struct FunctionInterface { pub arg_types: Vec, pub arg_refs: Vec, pub returns: Type, - pub is_public: bool + pub is_public: bool, + pub is_failable: bool, } impl FunctionInterface { @@ -25,6 +26,7 @@ impl FunctionInterface { returns: self.returns, is_args_typed, is_public: self.is_public, + is_failable: self.is_failable, id } } diff --git a/src/utils/metadata/translate.rs b/src/utils/metadata/translate.rs index b408cd64..41d8acd5 100644 --- a/src/utils/metadata/translate.rs +++ b/src/utils/metadata/translate.rs @@ -15,8 +15,12 @@ pub struct TranslateMetadata { pub fun_name: Option<(String, usize, usize)>, /// Array id is used to determine the array that is being evaluated. pub array_id: usize, + /// Value ID - used to store values in variables. + pub value_id: usize, /// Determines whether the current context is a context in bash's `eval`. pub eval_ctx: bool, + /// Determines whether the current context should be silenced. + pub silenced: bool, /// The current indentation level. pub indent: i64 } @@ -29,7 +33,9 @@ impl TranslateMetadata { fun_name: None, stmt_queue: VecDeque::new(), array_id: 0, + value_id: 0, eval_ctx: false, + silenced: false, indent: -1 } } @@ -52,9 +58,19 @@ impl TranslateMetadata { id } + pub fn gen_value_id(&mut self) -> usize { + let id = self.value_id; + self.value_id += 1; + id + } + + pub fn gen_silent(&self) -> &'static str { + self.silenced.then(|| " > /dev/null 2>&1").unwrap_or("") + } + // Returns the appropriate amount of quotes with escape symbols. // This helps to avoid problems with `eval` expressions. - pub fn quote(&self) -> &'static str { + pub fn gen_quote(&self) -> &'static str { self.eval_ctx .then(|| "\\\"") .unwrap_or("\"")