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

Support array indexing by range #628

Merged
merged 15 commits into from
Dec 12, 2024
Merged
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
23 changes: 23 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ wildmatch = "2.4.0"
[dev-dependencies]
assert_cmd = "2.0.14"
predicates = "3.1.0"
pretty_assertions = "1.4.1"
tempfile = "3.10.1"
tiny_http = "0.12.0"

Expand Down
3 changes: 2 additions & 1 deletion src/modules/expression/binop/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,11 @@ impl TranslateModule for Add {
match self.kind {
Type::Array(_) => {
let quote = meta.gen_quote();
let dollar = meta.gen_dollar();
let id = meta.gen_value_id();
let name = format!("__AMBER_ARRAY_ADD_{id}");
meta.stmt_queue.push_back(format!("{name}=({left} {right})"));
format!("{quote}${{{name}[@]}}{quote}")
format!("{quote}{dollar}{{{name}[@]}}{quote}")
},
Type::Text => format!("{}{}", left, right),
_ => translate_computation(meta, ArithOp::Add, Some(left), Some(right))
Expand Down
71 changes: 57 additions & 14 deletions src/modules/expression/binop/range.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use std::ops::Sub;
use heraclitus_compiler::prelude::*;
use crate::docs::module::DocumentationModule;
use crate::modules::expression::expr::{Expr, ExprType};
use crate::modules::expression::binop::BinOp;
use crate::modules::expression::expr::Expr;
use crate::modules::types::{Type, Typed};
use crate::utils::metadata::ParserMetadata;
use crate::translate::compute::{translate_computation, ArithOp};
use crate::translate::module::TranslateModule;
use crate::utils::metadata::ParserMetadata;
use crate::utils::TranslateMetadata;
use crate::{handle_binop, error_type_match};
use super::BinOp;
use crate::{error_type_match, handle_binop};
use heraclitus_compiler::prelude::*;
use std::cmp::max;

#[derive(Debug, Clone)]
pub struct Range {
Expand Down Expand Up @@ -59,17 +59,60 @@ impl SyntaxModule<ParserMetadata> for Range {
impl TranslateModule for Range {
fn translate(&self, meta: &mut TranslateMetadata) -> String {
let from = self.from.translate(meta);
let to = self.to.translate(meta);
if self.neq {
let to_neq = if let Some(ExprType::Number(_)) = &self.to.value {
to.parse::<isize>().unwrap_or_default().sub(1).to_string()
let to = if let Some(to) = self.to.get_integer_value() {
if self.neq {
Ph0enixKM marked this conversation as resolved.
Show resolved Hide resolved
(to - 1).to_string()
} else {
translate_computation(meta, ArithOp::Sub, Some(to), Some("1".to_string()))
};
meta.gen_subprocess(&format!("seq {} {}", from, to_neq))
to.to_string()
}
} else {
meta.gen_subprocess(&format!("seq {} {}", from, to))
let to = self.to.translate(meta);
if self.neq {
translate_computation(meta, ArithOp::Sub, Some(to), Some("1".to_string()))
} else {
to
}
};
let stmt = format!("seq {} {}", from, to);
meta.gen_subprocess(&stmt)
}
}

impl Range {
pub fn get_array_index(&self, meta: &mut TranslateMetadata) -> (String, String) {
if let Some(from) = self.from.get_integer_value() {
if let Some(mut to) = self.to.get_integer_value() {
// Make the upper bound exclusive.
if !self.neq {
to += 1;
}
// Cap the lower bound at zero.
let offset = max(from, 0);
// Cap the slice length at zero.
let length = max(to - offset, 0);
return (offset.to_string(), length.to_string());
}
}
let local = if meta.fun_meta.is_some() { "local " } else { "" };
// Make the upper bound exclusive.
let upper_name = format!("__SLICE_UPPER_{}", meta.gen_value_id());
let mut upper_val = self.to.translate(meta);
if !self.neq {
upper_val = translate_computation(meta, ArithOp::Add, Some(upper_val), Some("1".to_string()));
}
meta.stmt_queue.push_back(format!("{local}{upper_name}={upper_val}"));
// Cap the lower bound at zero.
let offset_name = format!("__SLICE_OFFSET_{}", meta.gen_value_id());
let offset_val = self.from.translate(meta);
meta.stmt_queue.push_back(format!("{local}{offset_name}={offset_val}"));
meta.stmt_queue.push_back(format!("{offset_name}=$(({offset_name} > 0 ? {offset_name} : 0))"));
let offset_val = format!("${offset_name}");
// Cap the slice length at zero.
let length_name = format!("__SLICE_LENGTH_{}", meta.gen_value_id());
let length_val = translate_computation(meta, ArithOp::Sub, Some(upper_val), Some(offset_val));
meta.stmt_queue.push_back(format!("{local}{length_name}={length_val}"));
meta.stmt_queue.push_back(format!("{length_name}=$(({length_name} > 0 ? {length_name} : 0))"));
(format!("${offset_name}"), format!("${length_name}"))
}
}

Expand Down
8 changes: 8 additions & 0 deletions src/modules/expression/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,14 @@ impl Typed for Expr {
}

impl Expr {
pub fn get_integer_value(&self) -> Option<isize> {
match &self.value {
Some(ExprType::Number(value)) => value.get_integer_value(),
Some(ExprType::Neg(value)) => value.get_integer_value(),
_ => None,
}
}
Ph0enixKM marked this conversation as resolved.
Show resolved Hide resolved

pub fn get_position(&self, meta: &mut ParserMetadata) -> PositionInfo {
let begin = meta.get_token_at(self.pos.0);
let end = meta.get_token_at(self.pos.1);
Expand Down
7 changes: 7 additions & 0 deletions src/modules/expression/literal/number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ impl TranslateModule for Number {
}
}

impl Number {
pub fn get_integer_value(&self) -> Option<isize> {
let value = self.value.parse().unwrap_or_default();
Some(value)
}
}

impl DocumentationModule for Number {
fn document(&self, _meta: &ParserMetadata) -> String {
"".to_string()
Expand Down
26 changes: 23 additions & 3 deletions src/modules/expression/unop/neg.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
use heraclitus_compiler::prelude::*;
use crate::{utils::{metadata::ParserMetadata, TranslateMetadata}, modules::types::{Type, Typed}, translate::{module::TranslateModule, compute::{translate_computation, ArithOp}}};
use super::{super::expr::Expr, UnOp};
use crate::docs::module::DocumentationModule;
use crate::error_type_match;
use crate::modules::expression::expr::Expr;
use crate::modules::expression::unop::UnOp;
use crate::modules::types::{Type, Typed};
use crate::translate::compute::{translate_computation, ArithOp};
use crate::translate::module::TranslateModule;
use crate::utils::metadata::ParserMetadata;
use crate::utils::TranslateMetadata;
use heraclitus_compiler::prelude::*;
use std::ops::Neg as _;

#[derive(Debug, Clone)]
pub struct Neg {
Expand Down Expand Up @@ -51,6 +57,20 @@ impl TranslateModule for Neg {
}
}

impl Neg {
pub fn get_integer_value(&self) -> Option<isize> {
self.expr.get_integer_value().map(isize::neg)
}

mks-h marked this conversation as resolved.
Show resolved Hide resolved
pub fn get_array_index(&self, meta: &mut TranslateMetadata) -> String {
if let Some(expr) = self.get_integer_value() {
expr.to_string()
} else {
self.translate(meta)
}
}
}

impl DocumentationModule for Neg {
fn document(&self, _meta: &ParserMetadata) -> String {
"".to_string()
Expand Down
128 changes: 94 additions & 34 deletions src/modules/variable/get.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use heraclitus_compiler::prelude::*;
use crate::{docs::module::DocumentationModule, modules::{expression::expr::Expr, types::{Type, Typed}}, utils::{ParserMetadata, TranslateMetadata}};
use crate::docs::module::DocumentationModule;
use crate::modules::expression::expr::{Expr, ExprType};
use crate::modules::types::{Type, Typed};
use crate::modules::variable::{handle_index_accessor, handle_variable_reference, variable_name_extensions};
use crate::translate::module::TranslateModule;
use super::{variable_name_extensions, handle_variable_reference, handle_index_accessor};
use crate::utils::{ParserMetadata, TranslateMetadata};
use heraclitus_compiler::prelude::*;

#[derive(Debug, Clone)]
pub struct VariableGet {
Expand All @@ -12,21 +15,25 @@ pub struct VariableGet {
is_ref: bool
}

impl VariableGet {
pub fn get_translated_name(&self) -> String {
match self.global_id {
Some(id) => format!("__{id}_{}", self.name),
None => self.name.to_string()
}
}
}

impl Typed for VariableGet {
fn get_type(&self) -> Type {
match (&*self.index, self.kind.clone()) {
// Return the type of the array element if indexed
(Some(_), Type::Array(kind)) => *kind,
_ => self.kind.clone()
match (&self.kind, self.index.as_ref()) {
(Type::Array(kind), Some(index)) if matches!(index.value, Some(ExprType::Range(_))) => {
// Array type (indexing array by range)
Type::Array(kind.clone())
}
(Type::Array(kind), Some(_)) => {
// Item type (indexing array by number)
*kind.clone()
}
(Type::Array(kind), None) => {
// Array type (returning array)
Type::Array(kind.clone())
}
(kind, _) => {
// Variable type (returning text or number)
kind.clone()
}
}
}
}
Expand All @@ -51,7 +58,7 @@ impl SyntaxModule<ParserMetadata> for VariableGet {
self.global_id = variable.global_id;
self.is_ref = variable.is_ref;
self.kind = variable.kind.clone();
self.index = Box::new(handle_index_accessor(meta)?);
self.index = Box::new(handle_index_accessor(meta, true)?);
// Check if the variable can be indexed
if self.index.is_some() && !matches!(variable.kind, Type::Array(_)) {
return error!(meta, tok, format!("Cannot index a non-array variable of type '{}'", self.kind));
Expand All @@ -63,27 +70,80 @@ impl SyntaxModule<ParserMetadata> for VariableGet {
impl TranslateModule for VariableGet {
fn translate(&self, meta: &mut TranslateMetadata) -> String {
let name = self.get_translated_name();
let ref_prefix = if self.is_ref { "!" } else { "" };
let res = format!("${{{ref_prefix}{name}}}");
// Text variables need to be encapsulated in string literals
// Otherwise, they will be "spread" into tokens
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)),
None => format!("{quote}${{{name}[@]}}{quote}")
},
(true, Type::Array(_)) => match *self.index {
Some(ref expr) => {
match &self.kind {
Type::Array(_) if self.is_ref => {
if let Some(index) = self.index.as_ref() {
let id = meta.gen_value_id();
let expr = expr.translate_eval(meta, true);
meta.stmt_queue.push_back(format!("eval \"local __AMBER_ARRAY_GET_{id}_{name}=\\\"\\${{${name}[{expr}]}}\\\"\""));
format!("$__AMBER_ARRAY_GET_{id}_{name}") // echo $__ARRAY_GET
},
None => format!("{quote}${{!__AMBER_ARRAY_{name}}}{quote}")
},
(_, Type::Text) => format!("{quote}{res}{quote}"),
_ => res
let value = Self::slice_ref_array(meta, &name, index);
let stmt = format!("eval \"local __AMBER_ARRAY_GET_{id}_{name}=\\\"\\${{{value}}}\\\"\"");
meta.stmt_queue.push_back(stmt);
format!("$__AMBER_ARRAY_GET_{id}_{name}")
} else {
format!("{quote}${{!__AMBER_ARRAY_{name}}}{quote}")
}
}
Type::Array(_) if !self.is_ref => {
if let Some(index) = self.index.as_ref() {
let value = Self::slice_copy_array(meta, &name, index);
format!("{quote}{value}{quote}")
} else {
format!("{quote}${{{name}[@]}}{quote}")
}
}
Type::Text => {
let prefix = if self.is_ref { "!" } else { "" };
format!("{quote}${{{prefix}{name}}}{quote}")
}
_ => {
let prefix = if self.is_ref { "!" } else { "" };
format!("${{{prefix}{name}}}")
}
}
}
}

impl VariableGet {
pub fn get_translated_name(&self) -> String {
match self.global_id {
Some(id) => format!("__{id}_{}", self.name),
None => self.name.to_string()
}
}

fn slice_ref_array(meta: &mut TranslateMetadata, name: &str, index: &Expr) -> String {
match &index.value {
Some(ExprType::Range(range)) => {
let (offset, length) = range.get_array_index(meta);
format!("${name}[@]:{offset}:{length}")
}
Some(ExprType::Neg(neg)) => {
let index = neg.get_array_index(meta);
format!("${name}[{index}]")
}
_ => {
let index = index.translate_eval(meta, true);
format!("${name}[{index}]")
}
}
}

fn slice_copy_array(meta: &mut TranslateMetadata, name: &str, index: &Expr) -> String {
match &index.value {
Some(ExprType::Range(range)) => {
let (offset, length) = range.get_array_index(meta);
format!("${{{name}[@]:{offset}:{length}}}")
}
Some(ExprType::Neg(neg)) => {
let index = neg.get_array_index(meta);
format!("${{{name}[{index}]}}")
}
_ => {
let index = index.translate(meta);
format!("${{{name}[{index}]}}")
}
}
}
}
Expand Down
Loading
Loading