diff --git a/docs.ab b/docs.ab old mode 100644 new mode 100755 index 42c3e8c8..e90e8997 --- a/docs.ab +++ b/docs.ab @@ -1,3 +1,5 @@ +#!/usr/bin/env amber + import { file_glob } from "std/fs" import { echo_colored } from "std/env" diff --git a/src/modules/expression/binop/range.rs b/src/modules/expression/binop/range.rs index e4d3d65b..cfc6bb02 100644 --- a/src/modules/expression/binop/range.rs +++ b/src/modules/expression/binop/range.rs @@ -74,6 +74,7 @@ impl TranslateModule for Range { } impl Range { + // ShellCheck SC2046: We use `paste -sd " " -` to ensure space-separated output for compliance /// Generate a range at compile time when both operands are numeric literals fn generate_compile_time_range(&self, from_val: isize, to_val: isize) -> FragmentKind { if self.neq && from_val == to_val { @@ -102,19 +103,19 @@ impl Range { let expr = if self.neq { fragments!( "if [ ", from_var.clone(), " -gt ", to_var.clone(), " ]; then ", - "seq -f \"%.0f\" -- ", from_var.clone(), " -1 ", reverse_to.clone(), " || seq -- ", from_var.clone(), " -1 ", reverse_to, "; ", + "(seq -f \"%.0f\" -- ", from_var.clone(), " -1 ", reverse_to.clone(), " || seq -- ", from_var.clone(), " -1 ", reverse_to, ") | paste -sd \" \" -; ", "elif [ ", from_var.clone(), " -lt ", to_var.clone(), " ]; then ", - "seq -f \"%.0f\" -- ", from_var.clone(), " ", forward_to.clone(), " || seq -- ", from_var.clone(), " ", forward_to, "; fi" + "(seq -f \"%.0f\" -- ", from_var.clone(), " ", forward_to.clone(), " || seq -- ", from_var.clone(), " ", forward_to, ") | paste -sd \" \" -; fi" ) } else { fragments!( "if [ ", from_var.clone(), " -gt ", to_var.clone(), " ]; then ", - "seq -f \"%.0f\" -- ", from_var.clone(), " -1 ", reverse_to.clone(), " || seq -- ", from_var.clone(), " -1 ", reverse_to, "; ", - "else seq -f \"%.0f\" -- ", from_var.clone(), " ", forward_to.clone(), " || seq -- ", from_var.clone(), " ", forward_to, "; fi" + "(seq -f \"%.0f\" -- ", from_var.clone(), " -1 ", reverse_to.clone(), " || seq -- ", from_var.clone(), " -1 ", reverse_to, ") | paste -sd \" \" -; ", + "else (seq -f \"%.0f\" -- ", from_var.clone(), " ", forward_to.clone(), " || seq -- ", from_var.clone(), " ", forward_to, ") | paste -sd \" \" -; fi" ) }; - SubprocessFragment::new(expr).with_quotes(false).to_frag() + SubprocessFragment::new(expr).to_frag() } /// Check if this is a reverse range (start > end or equal with exclusive operator) @@ -126,20 +127,22 @@ impl Range { fn generate_forward_seq(&self, from_val: isize, to_val: isize) -> FragmentKind { let to_adjusted = if self.neq { to_val - 1 } else { to_val }; let expr = fragments!( - "seq -f \"%.0f\" -- ", raw_fragment!("{}", from_val), " ", raw_fragment!("{}", to_adjusted), - " || seq -- ", raw_fragment!("{}", from_val), " ", raw_fragment!("{}", to_adjusted) + "(seq -f \"%.0f\" -- ", raw_fragment!("{}", from_val), " ", raw_fragment!("{}", to_adjusted), + " || seq -- ", raw_fragment!("{}", from_val), " ", raw_fragment!("{}", to_adjusted), + ") | paste -sd \" \" -" ); - SubprocessFragment::new(expr).with_quotes(false).to_frag() + SubprocessFragment::new(expr).to_frag() } /// Generate a reverse seq command for compile-time ranges fn generate_reverse_seq(&self, from_val: isize, to_val: isize) -> FragmentKind { let to_adjusted = if self.neq { to_val + 1 } else { to_val }; let expr = fragments!( - "seq -f \"%.0f\" -- ", raw_fragment!("{}", from_val), " -1 ", raw_fragment!("{}", to_adjusted), - " || seq -- ", raw_fragment!("{}", from_val), " -1 ", raw_fragment!("{}", to_adjusted) + "(seq -f \"%.0f\" -- ", raw_fragment!("{}", from_val), " -1 ", raw_fragment!("{}", to_adjusted), + " || seq -- ", raw_fragment!("{}", from_val), " -1 ", raw_fragment!("{}", to_adjusted), + ") | paste -sd \" \" -" ); - SubprocessFragment::new(expr).with_quotes(false).to_frag() + SubprocessFragment::new(expr).to_frag() } /// Adjust the end value for forward ranges (runtime) diff --git a/src/modules/expression/literal/text.rs b/src/modules/expression/literal/text.rs index 3540a2ad..b5a6db3a 100644 --- a/src/modules/expression/literal/text.rs +++ b/src/modules/expression/literal/text.rs @@ -48,7 +48,18 @@ impl TranslateModule for Text { fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind { // Translate all interpolations let interps = self.interps.iter() - .map(|item| item.translate(meta).with_quotes(false)) + .map(|item| { + let frag = item.translate(meta).with_quotes(false); + // ShellCheck SC2145: Use [*] for array interpolation to treat array as single string argument + if let FragmentKind::VarExpr(mut var) = frag { + if var.kind.is_array() { + var = var.with_array_to_string(true); + } + var.to_frag() + } else { + frag + } + }) .collect::>(); InterpolableFragment::new(self.strings.clone(), interps, InterpolableRenderType::StringLiteral).to_frag() } diff --git a/src/tests/snapshots/amber__tests__compiling__iter_loop_range_optimized_dynamic.ab.snap b/src/tests/snapshots/amber__tests__compiling__iter_loop_range_optimized_dynamic.ab.snap index 6eb8c38c..dc84a6eb 100644 --- a/src/tests/snapshots/amber__tests__compiling__iter_loop_range_optimized_dynamic.ab.snap +++ b/src/tests/snapshots/amber__tests__compiling__iter_loop_range_optimized_dynamic.ab.snap @@ -66,8 +66,8 @@ for (( i_14=${__range_start_14}; i_14 * ${__dir_14} < ${__range_end_14} * ${__di echo "${i_14}" done echo "Expressions (${start_0}+1)..(${end_1}*1)" -__range_start_15="$(( ${start_0} + 1 ))" -__range_end_15="$(( ${end_1} * 1 ))" +__range_start_15="$(( start_0 + 1 ))" +__range_end_15="$(( end_1 * 1 ))" __dir_15=$(( ${__range_start_15} <= ${__range_end_15} ? 1 : -1 )) for (( i_15=${__range_start_15}; i_15 * ${__dir_15} < ${__range_end_15} * ${__dir_15}; i_15+=${__dir_15} )); do echo "${i_15}" diff --git a/src/tests/snapshots/amber__tests__optimizing__unused_variables_loop.ab.snap b/src/tests/snapshots/amber__tests__optimizing__unused_variables_loop.ab.snap index 838539ae..3a057de2 100644 --- a/src/tests/snapshots/amber__tests__optimizing__unused_variables_loop.ab.snap +++ b/src/tests/snapshots/amber__tests__optimizing__unused_variables_loop.ab.snap @@ -5,9 +5,9 @@ expression: output a_0=10 while : do - if [ "$(( ${a_0} <= 0 ))" != 0 ]; then + if [ "$(( a_0 <= 0 ))" != 0 ]; then break fi echo "${a_0}" - a_0="$(( ${a_0} - 1 ))" + a_0="$(( a_0 - 1 ))" done diff --git a/src/tests/snapshots/amber__tests__translating__add_int_shorthand.ab.snap b/src/tests/snapshots/amber__tests__translating__add_int_shorthand.ab.snap index 051f5757..67aeb40b 100644 --- a/src/tests/snapshots/amber__tests__translating__add_int_shorthand.ab.snap +++ b/src/tests/snapshots/amber__tests__translating__add_int_shorthand.ab.snap @@ -53,6 +53,7 @@ Block( default_value: None, is_quoted: true, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, @@ -119,6 +120,7 @@ Block( default_value: None, is_quoted: true, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, diff --git a/src/tests/snapshots/amber__tests__translating__add_num_shorthand.ab.snap b/src/tests/snapshots/amber__tests__translating__add_num_shorthand.ab.snap index ba2f509b..f93d3895 100644 --- a/src/tests/snapshots/amber__tests__translating__add_num_shorthand.ab.snap +++ b/src/tests/snapshots/amber__tests__translating__add_num_shorthand.ab.snap @@ -60,6 +60,7 @@ Block( default_value: None, is_quoted: true, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, @@ -163,6 +164,7 @@ Block( default_value: None, is_quoted: true, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, diff --git a/src/tests/snapshots/amber__tests__translating__div_int_shorthand.ab.snap b/src/tests/snapshots/amber__tests__translating__div_int_shorthand.ab.snap index 173ef3af..6719c1c4 100644 --- a/src/tests/snapshots/amber__tests__translating__div_int_shorthand.ab.snap +++ b/src/tests/snapshots/amber__tests__translating__div_int_shorthand.ab.snap @@ -53,6 +53,7 @@ Block( default_value: None, is_quoted: true, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, @@ -119,6 +120,7 @@ Block( default_value: None, is_quoted: true, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, diff --git a/src/tests/snapshots/amber__tests__translating__div_num_shorthand.ab.snap b/src/tests/snapshots/amber__tests__translating__div_num_shorthand.ab.snap index 2a7aa504..8a9bcf1e 100644 --- a/src/tests/snapshots/amber__tests__translating__div_num_shorthand.ab.snap +++ b/src/tests/snapshots/amber__tests__translating__div_num_shorthand.ab.snap @@ -60,6 +60,7 @@ Block( default_value: None, is_quoted: true, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, @@ -163,6 +164,7 @@ Block( default_value: None, is_quoted: true, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, diff --git a/src/tests/snapshots/amber__tests__translating__loop_iter.ab.snap b/src/tests/snapshots/amber__tests__translating__loop_iter.ab.snap index 2224359e..b8a5b561 100644 --- a/src/tests/snapshots/amber__tests__translating__loop_iter.ab.snap +++ b/src/tests/snapshots/amber__tests__translating__loop_iter.ab.snap @@ -79,6 +79,7 @@ Block( default_value: None, is_quoted: true, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, @@ -122,6 +123,7 @@ Block( default_value: None, is_quoted: false, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, @@ -242,6 +244,7 @@ Block( default_value: None, is_quoted: true, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, @@ -327,6 +330,7 @@ Block( default_value: None, is_quoted: true, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, @@ -371,6 +375,7 @@ Block( default_value: None, is_quoted: false, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, @@ -387,6 +392,7 @@ Block( default_value: None, is_quoted: false, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, @@ -427,6 +433,7 @@ Block( default_value: None, is_quoted: false, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: Some( Index( @@ -442,6 +449,7 @@ Block( default_value: None, is_quoted: true, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, diff --git a/src/tests/snapshots/amber__tests__translating__modulo_int_shorthand.ab.snap b/src/tests/snapshots/amber__tests__translating__modulo_int_shorthand.ab.snap index ada4db7b..8978bf4a 100644 --- a/src/tests/snapshots/amber__tests__translating__modulo_int_shorthand.ab.snap +++ b/src/tests/snapshots/amber__tests__translating__modulo_int_shorthand.ab.snap @@ -53,6 +53,7 @@ Block( default_value: None, is_quoted: true, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, @@ -119,6 +120,7 @@ Block( default_value: None, is_quoted: true, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, diff --git a/src/tests/snapshots/amber__tests__translating__modulo_num_shorthand.ab.snap b/src/tests/snapshots/amber__tests__translating__modulo_num_shorthand.ab.snap index f68d8a4e..df2f105e 100644 --- a/src/tests/snapshots/amber__tests__translating__modulo_num_shorthand.ab.snap +++ b/src/tests/snapshots/amber__tests__translating__modulo_num_shorthand.ab.snap @@ -60,6 +60,7 @@ Block( default_value: None, is_quoted: true, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, @@ -163,6 +164,7 @@ Block( default_value: None, is_quoted: true, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, diff --git a/src/tests/snapshots/amber__tests__translating__mul_int_shorthand.ab.snap b/src/tests/snapshots/amber__tests__translating__mul_int_shorthand.ab.snap index 6ebcbbfc..16d7d1c4 100644 --- a/src/tests/snapshots/amber__tests__translating__mul_int_shorthand.ab.snap +++ b/src/tests/snapshots/amber__tests__translating__mul_int_shorthand.ab.snap @@ -53,6 +53,7 @@ Block( default_value: None, is_quoted: true, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, @@ -119,6 +120,7 @@ Block( default_value: None, is_quoted: true, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, diff --git a/src/tests/snapshots/amber__tests__translating__mul_num_shorthand.ab.snap b/src/tests/snapshots/amber__tests__translating__mul_num_shorthand.ab.snap index e187dfb8..d0eb31a8 100644 --- a/src/tests/snapshots/amber__tests__translating__mul_num_shorthand.ab.snap +++ b/src/tests/snapshots/amber__tests__translating__mul_num_shorthand.ab.snap @@ -60,6 +60,7 @@ Block( default_value: None, is_quoted: true, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, @@ -163,6 +164,7 @@ Block( default_value: None, is_quoted: true, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, diff --git a/src/tests/snapshots/amber__tests__translating__sub_int_shorthand.ab.snap b/src/tests/snapshots/amber__tests__translating__sub_int_shorthand.ab.snap index 9629c8a3..a3690d13 100644 --- a/src/tests/snapshots/amber__tests__translating__sub_int_shorthand.ab.snap +++ b/src/tests/snapshots/amber__tests__translating__sub_int_shorthand.ab.snap @@ -53,6 +53,7 @@ Block( default_value: None, is_quoted: true, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, @@ -119,6 +120,7 @@ Block( default_value: None, is_quoted: true, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, diff --git a/src/tests/snapshots/amber__tests__translating__sub_num_shorthand.ab.snap b/src/tests/snapshots/amber__tests__translating__sub_num_shorthand.ab.snap index 7c5cd65b..e6337297 100644 --- a/src/tests/snapshots/amber__tests__translating__sub_num_shorthand.ab.snap +++ b/src/tests/snapshots/amber__tests__translating__sub_num_shorthand.ab.snap @@ -60,6 +60,7 @@ Block( default_value: None, is_quoted: true, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, @@ -163,6 +164,7 @@ Block( default_value: None, is_quoted: true, is_array_to_string: false, + is_math_var: false, render_type: BashValue, index: None, }, diff --git a/src/translate/compare.rs b/src/translate/compare.rs index 6219c5d4..2abd10ee 100644 --- a/src/translate/compare.rs +++ b/src/translate/compare.rs @@ -211,17 +211,16 @@ pub fn translate_array_equality( right: VarExprFragment, negative: bool ) -> FragmentKind { - let left_len = left.clone().with_length_getter(true).to_frag(); let right_len = right.clone().with_length_getter(true).to_frag(); let left_index = left.with_index_by_value(VarIndexValue::Index(raw_fragment!("i"))).to_frag(); let right_index = right.with_index_by_value(VarIndexValue::Index(raw_fragment!("i"))).to_frag(); - let false_val = if negative { "1" } else { "0" }; - let true_val = if negative { "0" } else { "1" }; + let false_val = raw_fragment!("{}", if negative { "1" } else { "0" }); + let true_val = raw_fragment!("{}", if negative { "0" } else { "1" }); let block = BlockFragment::new(vec![ - fragments!("(( ", left_len.clone(), " != ", right_len, " )) && echo ", raw_fragment!("{false_val}"), " && exit"), - fragments!("for (( i=0; i<", left_len.clone(), "; i++ )); do [[ ", left_index, " != ", right_index, " ]] && echo ", raw_fragment!("{false_val}"), " && exit; done"), - fragments!("echo ", raw_fragment!("{true_val}"), "\n") + fragments!("(( ", left_len.clone(), " != ", right_len, " )) && echo ", false_val.clone(), " && exit"), + fragments!("for (( i=0; i<", left_len.clone(), "; i++ )); do [[ \"", left_index, "\" != \"", right_index, "\" ]] && echo ", false_val, " && exit; done"), + fragments!("echo ", true_val, "\n") ], true); SubprocessFragment::new(fragments!("\n", block.to_frag())).to_frag() } diff --git a/src/translate/fragments/arithmetic.rs b/src/translate/fragments/arithmetic.rs index d64a546a..a9154aa0 100644 --- a/src/translate/fragments/arithmetic.rs +++ b/src/translate/fragments/arithmetic.rs @@ -53,8 +53,22 @@ impl FragmentRenderable for ArithmeticFragment { fn to_string(self, meta: &mut TranslateMetadata) -> String { let dollar = meta.gen_dollar(); let op = self.operator_to_string().to_string(); - let left = self.left.unwrap_or_default().with_quotes(false).to_string(meta); - let right = self.right.unwrap_or_default().with_quotes(false).to_string(meta); + let left = self.left.unwrap_or_default().with_quotes(false); + let right = self.right.unwrap_or_default().with_quotes(false); + + // ShellCheck SC2004: We enable is_math_var to avoid unnecessary ${} wrapping in arithmetic expressions + let left = if let FragmentKind::VarExpr(var) = left { + var.with_math_var(true).to_string(meta) + } else { + left.to_string(meta) + }; + + let right = if let FragmentKind::VarExpr(var) = right { + var.with_math_var(true).to_string(meta) + } else { + right.to_string(meta) + }; + let quote = if self.quoted { meta.gen_quote() } else { "" }; let expr = [left, op, right].iter().filter(|x| !x.is_empty()).join(" "); format!("{quote}{dollar}(( {expr} )){quote}") diff --git a/src/translate/fragments/var_expr.rs b/src/translate/fragments/var_expr.rs index 50eb9609..ca0247a1 100644 --- a/src/translate/fragments/var_expr.rs +++ b/src/translate/fragments/var_expr.rs @@ -38,6 +38,8 @@ pub struct VarExprFragment { pub is_quoted: bool, // Bash's `${array[*]}` expansion pub is_array_to_string: bool, + // Variable is inside an arithmetic expression + pub is_math_var: bool, pub render_type: VarRenderType, // Amber's array subscript like `arr[0]` or `arr[1..5]` pub index: Option>, @@ -54,6 +56,7 @@ impl Default for VarExprFragment { is_ref: false, is_length: false, is_array_to_string: false, + is_math_var: false, is_quoted: true, render_type: VarRenderType::BashValue, index: None, @@ -86,6 +89,11 @@ impl VarExprFragment { self } + pub fn with_math_var(mut self, is_math_var: bool) -> Self { + self.is_math_var = is_math_var; + self + } + pub fn from_stmt(stmt: &VarStmtFragment) -> Self { VarExprFragment { name: stmt.name.clone(), @@ -176,11 +184,15 @@ impl VarExprFragment { let name = self.get_name(); let index = self.index.take(); let default_value = self.default_value.take(); + let index_is_none = index.is_none(); + let default_is_none = default_value.is_none(); let prefix = self.get_variable_prefix(); let suffix = self.get_variable_suffix(meta, index, default_value); if self.is_ref { self.render_deref_variable(meta, prefix, &name, &suffix) + } else if self.is_math_var && !self.is_length && default_is_none && index_is_none { + name.to_string() } else { let quote = if self.is_quoted { meta.gen_quote() } else { "" }; let dollar = meta.gen_dollar(); @@ -242,7 +254,7 @@ impl VarExprFragment { return format!("{quote}{dollar}{{!{name}}}{quote}"); } let id = meta.gen_value_id(); - let eval_value = format!("{prefix}${{{name}}}{suffix}"); + let eval_value = format!("{prefix}${{{name}[0]}}{suffix}"); let var_name = format!("{name}_deref_{id}"); meta.stmt_queue.push_back(RawFragment::from( format!("eval \"local {var_name}={arr_open}\\\"\\${{{eval_value}}}\\\"{arr_close}\"")