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

Implement basic std functions to manipulate arrays #625

Closed
wants to merge 13 commits into from
Closed
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
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 {
(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,
}
}

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
27 changes: 24 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,21 @@ impl TranslateModule for Neg {
}
}

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

pub fn get_array_index(&self, meta: &mut TranslateMetadata) -> String {
if let Some(expr) = self.get_integer_value() {
expr.to_string()
} else {
let expr = self.expr.translate(meta);
translate_computation(meta, ArithOp::Neg, None, Some(expr))
}
}
}

impl DocumentationModule for Neg {
fn document(&self, _meta: &ParserMetadata) -> String {
"".to_string()
Expand Down
2 changes: 1 addition & 1 deletion src/modules/function/invocation_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ fn run_function_with_args(meta: &mut ParserMetadata, mut fun: FunctionDecl, args
})?;
// Set the new return type or null if nothing was returned
if let Type::Generic = fun.returns {
fun.returns = context.fun_ret_type.clone().unwrap_or_else(|| Type::Null);
fun.returns = context.fun_ret_type.clone().unwrap_or(Type::Null);
};
// Set the new argument types
fun.arg_types = args.to_vec();
Expand Down
104 changes: 78 additions & 26 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 @@ -23,10 +26,22 @@ impl VariableGet {

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()
if let Type::Array(item_kind) = &self.kind {
if let Some(index) = self.index.as_ref() {
if let Some(ExprType::Range(_)) = &index.value {
// Array type (indexing array by range)
self.kind.clone()
} else {
// Item type (indexing array by number)
*item_kind.clone()
}
} else {
// Array type (returning array)
self.kind.clone()
}
} else {
// Variable type (returning text or number)
self.kind.clone()
}
}
}
Expand All @@ -51,7 +66,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 @@ -61,29 +76,66 @@ impl SyntaxModule<ParserMetadata> for VariableGet {
}

impl TranslateModule for VariableGet {
#[allow(clippy::collapsible_else_if)]
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) => {
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
match &self.kind {
Type::Array(_) => {
if self.is_ref {
if let Some(index) = self.index.as_ref() {
let value = 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}]}}\\\"")
}
};
let id = meta.gen_value_id();
let stmt = format!("eval \"local __AMBER_ARRAY_GET_{id}_{name}={value}\"");
meta.stmt_queue.push_back(stmt);
format!("$__AMBER_ARRAY_GET_{id}_{name}") // echo $__ARRAY_GET
} else {
format!("{quote}${{!__AMBER_ARRAY_{name}}}{quote}")
}
} else {
if let Some(index) = self.index.as_ref() {
match &index.value {
Some(ExprType::Range(range)) => {
let (offset, length) = range.get_array_index(meta);
format!("{quote}${{{name}[@]:{offset}:{length}}}{quote}")
}
Some(ExprType::Neg(neg)) => {
let index = neg.get_array_index(meta);
format!("{quote}${{{name}[{index}]}}{quote}")
}
_ => {
let index = index.translate(meta);
format!("{quote}${{{name}[{index}]}}{quote}")
}
}
} else {
format!("{quote}${{{name}[@]}}{quote}")
}
}
}
Type::Text => {
let ref_prefix = if self.is_ref { "!" } else { "" };
format!("{quote}${{{ref_prefix}{name}}}{quote}")
}
_ => {
let ref_prefix = if self.is_ref { "!" } else { "" };
format!("${{{ref_prefix}{name}}}")
}
}
}
}
Expand Down
Loading
Loading