diff --git a/src/modules/loops/iter_loop.rs b/src/modules/loops/iter_loop.rs index 47359852..4fa0a7b2 100644 --- a/src/modules/loops/iter_loop.rs +++ b/src/modules/loops/iter_loop.rs @@ -49,9 +49,9 @@ impl SyntaxModule for IterLoop { token(meta, "{")?; // Create iterator variable meta.with_push_scope(|meta| { - meta.add_var(&self.iter_name, self.iter_type.clone()); + meta.add_var(&self.iter_name, self.iter_type.clone(), false); if let Some(index) = self.iter_index.as_ref() { - meta.add_var(index, Type::Num); + meta.add_var(index, Type::Num, false); } // Save loop context state and set it to true meta.with_context_fn(Context::set_is_loop_ctx, true, |meta| { diff --git a/src/modules/main.rs b/src/modules/main.rs index 2f95d7cc..aab1e110 100644 --- a/src/modules/main.rs +++ b/src/modules/main.rs @@ -47,7 +47,7 @@ impl SyntaxModule for Main { meta.with_push_scope(|meta| { // Create variables for arg in self.args.iter() { - meta.add_var(arg, Type::Array(Box::new(Type::Text))); + meta.add_var(arg, Type::Array(Box::new(Type::Text)), true); } // Parse the block syntax(meta, &mut self.block)?; @@ -71,7 +71,7 @@ impl TranslateModule for Main { let dollar = meta.gen_dollar(); let args = self.args.clone().map_or_else( String::new, - |name| format!("{name}=({quote}{dollar}0{quote} {quote}{dollar}@{quote})") + |name| format!("declare -r {name}=({quote}{dollar}0{quote} {quote}{dollar}@{quote})") ); format!("{args}\n{}", self.block.translate(meta)) } diff --git a/src/modules/statement/stmt.rs b/src/modules/statement/stmt.rs index 8459561d..0962c008 100644 --- a/src/modules/statement/stmt.rs +++ b/src/modules/statement/stmt.rs @@ -1,6 +1,7 @@ use heraclitus_compiler::prelude::*; use itertools::Itertools; use crate::docs::module::DocumentationModule; +use crate::modules::variable::constinit::ConstInit; use crate::utils::metadata::{ParserMetadata, TranslateMetadata}; use crate::modules::expression::expr::{Expr, ExprType}; use crate::translate::module::TranslateModule; @@ -71,6 +72,7 @@ pub enum StatementType { CommandModifier(CommandModifier), Comment(Comment), CommentDoc(CommentDoc), + ConstInit(ConstInit), } #[derive(Debug, Clone)] @@ -89,7 +91,7 @@ impl Statement { // Conditions IfChain, IfCondition, // Variables - VariableInit, VariableSet, + VariableInit, VariableSet, ConstInit, // Short hand ShorthandAdd, ShorthandSub, ShorthandMul, ShorthandDiv, diff --git a/src/modules/variable/constinit.rs b/src/modules/variable/constinit.rs new file mode 100644 index 00000000..ebdfc525 --- /dev/null +++ b/src/modules/variable/constinit.rs @@ -0,0 +1,73 @@ +use heraclitus_compiler::prelude::*; +use crate::docs::module::DocumentationModule; +use crate::modules::types::{Typed, Type}; +use crate::modules::expression::expr::Expr; +use crate::translate::module::TranslateModule; +use crate::utils::metadata::{ParserMetadata, TranslateMetadata}; +use super::{variable_name_extensions, handle_identifier_name}; + +#[derive(Debug, Clone)] +pub struct ConstInit { + name: String, + expr: Box, + global_id: Option, + is_fun_ctx: bool +} + +impl ConstInit { + fn handle_add_const(&mut self, meta: &mut ParserMetadata, name: &str, kind: Type, tok: Option) -> SyntaxResult { + handle_identifier_name(meta, name, tok)?; + self.global_id = meta.add_var(name, kind, true); + Ok(()) + } +} + +impl SyntaxModule for ConstInit { + syntax_name!("Constant Initialize"); + + fn new() -> Self { + ConstInit { + name: String::new(), + expr: Box::new(Expr::new()), + global_id: None, + is_fun_ctx: false + } + } + + fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + token(meta, "const")?; + // Get the variable name + let tok = meta.get_current_token(); + self.name = variable(meta, variable_name_extensions())?; + context!({ + token(meta, "=")?; + syntax(meta, &mut *self.expr)?; + // Add a variable to the memory + self.handle_add_const(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)) + }) + } +} + +impl TranslateModule for ConstInit { + fn translate(&self, meta: &mut TranslateMetadata) -> String { + let name = self.name.clone(); + let mut expr = self.expr.translate(meta); + if let Type::Array(_) = self.expr.get_type() { + expr = format!("({expr})"); + } + match self.global_id { + Some(id) => format!("declare -r __{id}_{name}={expr}"), + None => format!("declare -r {name}={expr}") + } + } +} + +impl DocumentationModule for ConstInit { + fn document(&self, _meta: &ParserMetadata) -> String { + "".to_string() + } +} diff --git a/src/modules/variable/init.rs b/src/modules/variable/init.rs index ad4171c8..94ffc296 100644 --- a/src/modules/variable/init.rs +++ b/src/modules/variable/init.rs @@ -17,7 +17,7 @@ pub struct VariableInit { impl VariableInit { fn handle_add_variable(&mut self, meta: &mut ParserMetadata, name: &str, kind: Type, tok: Option) -> SyntaxResult { handle_identifier_name(meta, name, tok)?; - self.global_id = meta.add_var(name, kind); + self.global_id = meta.add_var(name, kind, false); Ok(()) } } diff --git a/src/modules/variable/mod.rs b/src/modules/variable/mod.rs index f0acace9..a8a20358 100644 --- a/src/modules/variable/mod.rs +++ b/src/modules/variable/mod.rs @@ -8,6 +8,7 @@ use super::expression::expr::Expr; pub mod init; pub mod set; pub mod get; +pub mod constinit; pub fn variable_name_extensions() -> Vec { vec!['_'] @@ -18,7 +19,7 @@ pub fn variable_name_keywords() -> Vec<&'static str> { // Literals "true", "false", "null", // Variable keywords - "let", "as", "is", + "let", "as", "is", "const", // Control flow keywords "if", "then", "else", // Loop keywords diff --git a/src/modules/variable/set.rs b/src/modules/variable/set.rs index 94fe4031..223da82d 100644 --- a/src/modules/variable/set.rs +++ b/src/modules/variable/set.rs @@ -46,6 +46,10 @@ impl SyntaxModule for VariableSet { let variable = handle_variable_reference(meta, tok.clone(), &self.name)?; self.global_id = variable.global_id; self.is_ref = variable.is_ref; + // Check for constant reassignment + if variable.is_const { + return error!(meta, tok, format!("Cannot reassign constant")) + } // Typecheck the variable let left_type = variable.kind.clone(); let right_type = self.expr.get_type(); diff --git a/src/tests/validity/constant.ab b/src/tests/validity/constant.ab new file mode 100644 index 00000000..e43dfb7e --- /dev/null +++ b/src/tests/validity/constant.ab @@ -0,0 +1,15 @@ +// Output +// 1 +// 42 +// 1 +// 42 + +main { + const x = 42 + unsafe $ x=123 $ + echo status // will output 1 if reassignment didnt succeed + echo x + unsafe $ unset x $ + echo status // will output 1 if unsetting did not succeed + echo x +} \ No newline at end of file diff --git a/src/utils/context.rs b/src/utils/context.rs index 0988ab14..071472d1 100644 --- a/src/utils/context.rs +++ b/src/utils/context.rs @@ -41,6 +41,7 @@ pub struct VariableDecl { pub kind: Type, pub global_id: Option, pub is_ref: bool, + pub is_const: bool, } #[derive(Clone, Debug)] diff --git a/src/utils/metadata/parser.rs b/src/utils/metadata/parser.rs index 9a41f4a0..9ef07487 100644 --- a/src/utils/metadata/parser.rs +++ b/src/utils/metadata/parser.rs @@ -68,19 +68,20 @@ impl ParserMetadata { } /// Adds a variable to the current scope - pub fn add_var(&mut self, name: &str, kind: Type) -> Option { + pub fn add_var(&mut self, name: &str, kind: Type, is_const: bool) -> Option { let global_id = self.is_global_scope().then(|| self.gen_var_id()); let scope = self.context.scopes.last_mut().unwrap(); scope.add_var(VariableDecl { name: name.to_string(), kind, global_id, - is_ref: false + is_ref: false, + is_const, }); global_id } - /// Adds a parameter as variable to the current scope + /// Adds a function parameter as variable to the current scope pub fn add_param(&mut self, name: &str, kind: Type, is_ref: bool) -> Option { let global_id = self.is_global_scope().then(|| self.gen_var_id()); let scope = self.context.scopes.last_mut().unwrap(); @@ -88,7 +89,8 @@ impl ParserMetadata { name: name.to_string(), kind, global_id, - is_ref + is_ref, + is_const: false, }); global_id }