-
Notifications
You must be signed in to change notification settings - Fork 94
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 #626
Changes from 13 commits
b752d0f
eaf9317
525cf23
061e40f
f3e9ac5
947d197
a47eadd
89cce1a
6cb2d12
b31d4ff
98433af
a09f0b0
e14373b
e909825
4c35d32
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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 { | ||
|
@@ -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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use compile-time arithmetic to convert Amber There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's ok to assume we're given an integer not a decimal here, but we can always come back to this when we have a separate integer type. |
||
(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() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use compile-time arithmetic to convert Amber |
||
// 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}")) | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -101,6 +101,14 @@ impl Typed for Expr { | |
} | ||
|
||
impl Expr { | ||
pub fn get_integer_value(&self) -> Option<isize> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function is intended to return an integer for compile-time arithmetic, but only when given integer literals like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was tempted to put this functionality into the |
||
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); | ||
|
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should avoid nested |
||
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 { | ||
|
@@ -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() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This Clippy-suggested change has already been made on the master branch by @Ph0enixKM; but my change is identical, and should not cause a merge conflict. |
||
}; | ||
// Set the new argument types | ||
fun.arg_types = args.to_vec(); | ||
|
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 { | ||
|
@@ -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() | ||
} | ||
} | ||
} | ||
|
@@ -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)); | ||
|
@@ -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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did not want to collapse the |
||
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}}}") | ||
} | ||
} | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creates more readable output for
assert_eq!
macro. See https://crates.io/crates/pretty_assertions for more information.