diff --git a/debug_test.ab b/debug_test.ab deleted file mode 100644 index 21f25f8ba..000000000 --- a/debug_test.ab +++ /dev/null @@ -1,5 +0,0 @@ -main { - let arr = [1, 2, 3, 4, 5] - let result = arr[0..2] - echo result -} \ No newline at end of file diff --git a/src/compiler.rs b/src/compiler.rs index 7837c4599..3e9cd9820 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -3,6 +3,7 @@ use crate::built_info; use crate::docs::module::DocumentationModule; use crate::modules::block::Block; use crate::modules::prelude::{BlockFragment, FragmentRenderable}; +use crate::modules::typecheck::TypeCheckModule; use crate::optimizer::optimize_fragments; use crate::translate::check_all_blocks; use crate::translate::module::TranslateModule; @@ -300,9 +301,30 @@ impl AmberCompiler { } } + pub fn typecheck(&self, mut block: Block, mut meta: ParserMetadata) -> Result<(Block, ParserMetadata), Message> { + let time = Instant::now(); + + // Perform type checking on the block + if let Err(failure) = block.typecheck(&mut meta) { + return Err(failure.unwrap_loud()); + } + + if Self::env_flag_set(AMBER_DEBUG_TIME) { + let pathname = self.path.clone().unwrap_or(String::from("unknown")); + println!( + "[{}]\tin\t{}ms\t{pathname}", + "Typecheck".green(), + time.elapsed().as_millis() + ); + } + + Ok((block, meta)) + } + pub fn compile(&self) -> Result<(Vec, String), Message> { let tokens = self.tokenize()?; let (block, meta) = self.parse(tokens)?; + let (block, meta) = self.typecheck(block, meta)?; let messages = meta.messages.clone(); let code = self.translate(block, meta)?; Ok((messages, code)) @@ -326,7 +348,8 @@ impl AmberCompiler { pub fn generate_docs(&self, output: Option, usage: bool) -> Result<(), Message> { let tokens = self.tokenize()?; - let (block, mut meta) = self.parse(tokens)?; + let (block, meta) = self.parse(tokens)?; + let (block, mut meta) = self.typecheck(block, meta)?; meta.doc_usage = usage; self.document(block, meta, output); Ok(()) diff --git a/src/modules/block.rs b/src/modules/block.rs index beafda47c..9efdbaa17 100644 --- a/src/modules/block.rs +++ b/src/modules/block.rs @@ -63,31 +63,42 @@ impl SyntaxModule for Block { false }; - meta.with_push_scope(|meta| { - while meta.get_current_token().is_some() { - // Handle the end of line - if token(meta, "\n").is_ok() { - continue; - } - // Handle block end - if !is_single_line && self.parses_syntax && token(meta, "}").is_ok() { - break; - } - let mut statement = Statement::new(); - if let Err(failure) = statement.parse(meta) { - return match failure { - Failure::Quiet(pos) => error_pos!(meta, pos, "Unexpected token"), - Failure::Loud(err) => return Err(Failure::Loud(err)) - } - } - self.statements.push(statement); - // Handle the semicolon - token(meta, ";").ok(); - // Handle single line - if is_single_line { - break; + while meta.get_current_token().is_some() { + // Handle the end of line + if token(meta, "\n").is_ok() { + continue; + } + // Handle block end + if !is_single_line && self.parses_syntax && token(meta, "}").is_ok() { + break; + } + let mut statement = Statement::new(); + if let Err(failure) = statement.parse(meta) { + return match failure { + Failure::Quiet(pos) => error_pos!(meta, pos, "Unexpected token"), + Failure::Loud(err) => return Err(Failure::Loud(err)) } } + self.statements.push(statement); + // Handle the semicolon + token(meta, ";").ok(); + // Handle single line + if is_single_line { + break; + } + } + Ok(()) + } +} + +impl TypeCheckModule for Block { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + let no_scope_exists = meta.context.scopes.is_empty(); + meta.with_push_scope(self.parses_syntax || no_scope_exists, |meta| { + // Type check all statements in the block + for statement in &mut self.statements { + statement.typecheck(meta)?; + } Ok(()) }) } diff --git a/src/modules/builtin/cd.rs b/src/modules/builtin/cd.rs index 18c4c7860..57ee6eab6 100644 --- a/src/modules/builtin/cd.rs +++ b/src/modules/builtin/cd.rs @@ -19,6 +19,16 @@ impl SyntaxModule for Cd { fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { token(meta, "cd")?; syntax(meta, &mut self.value)?; + Ok(()) + } +} + +impl TypeCheckModule for Cd { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + // First, type-check the nested expression + self.value.typecheck(meta)?; + + // Then check if it's the correct type let path_type = self.value.get_type(); if path_type != Type::Text { let position = self.value.get_position(meta); diff --git a/src/modules/builtin/echo.rs b/src/modules/builtin/echo.rs index 83deb9aed..ed96b2ce7 100644 --- a/src/modules/builtin/echo.rs +++ b/src/modules/builtin/echo.rs @@ -24,6 +24,12 @@ impl SyntaxModule for Echo { } } +impl TypeCheckModule for Echo { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.value.typecheck(meta) + } +} + impl TranslateModule for Echo { fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind { fragments!("echo ", self.value.translate(meta)) diff --git a/src/modules/builtin/exit.rs b/src/modules/builtin/exit.rs index aa7211991..efde8b90c 100644 --- a/src/modules/builtin/exit.rs +++ b/src/modules/builtin/exit.rs @@ -24,7 +24,15 @@ impl SyntaxModule for Exit { self.code = Some(code_expr); } - if let Some(ref code_expr) = self.code { + Ok(()) + } +} + +impl TypeCheckModule for Exit { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + if let Some(ref mut code_expr) = self.code { + code_expr.typecheck(meta)?; + let code_type = code_expr.get_type(); if code_type != Type::Int { let position = code_expr.get_position(meta); @@ -34,7 +42,6 @@ impl SyntaxModule for Exit { }); } } - Ok(()) } } diff --git a/src/modules/builtin/len.rs b/src/modules/builtin/len.rs index 3afa72d38..49915c907 100644 --- a/src/modules/builtin/len.rs +++ b/src/modules/builtin/len.rs @@ -2,6 +2,7 @@ use crate::modules::expression::expr::Expr; use crate::modules::expression::unop::UnOp; use crate::modules::prelude::*; use crate::modules::types::{Type, Typed}; +use crate::modules::typecheck::TypeCheckModule; use crate::translate::module::TranslateModule; use crate::utils::{ParserMetadata, TranslateMetadata}; use heraclitus_compiler::prelude::*; @@ -37,7 +38,16 @@ impl SyntaxModule for Len { } } - fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + fn parse(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult { + Ok(()) + } +} + +impl TypeCheckModule for Len { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + // Typecheck the expression first + self.value.typecheck(meta)?; + if !matches!(self.value.get_type(), Type::Text | Type::Array(_)) { let msg = self .value diff --git a/src/modules/builtin/lines.rs b/src/modules/builtin/lines.rs index bf66c1047..5d3704234 100644 --- a/src/modules/builtin/lines.rs +++ b/src/modules/builtin/lines.rs @@ -30,22 +30,33 @@ impl SyntaxModule for LinesInvocation { fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { token(meta, "lines")?; token(meta, "(")?; - let tok = meta.get_current_token(); let mut path = Expr::new(); syntax(meta, &mut path)?; token(meta, ")")?; - if path.get_type() != Type::Text { - let msg = format!( - "Expected value of type 'Text' but got '{}'", - path.get_type() - ); - return error!(meta, tok, msg); - } self.path = Box::new(Some(path)); Ok(()) } } +impl TypeCheckModule for LinesInvocation { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + if let Some(path) = &mut *self.path { + path.typecheck(meta)?; + if path.get_type() != Type::Text { + let msg = format!( + "Expected value of type 'Text' but got '{}'", + path.get_type() + ); + let pos = path.get_position(meta); + return error_pos!(meta, pos, msg); + } + Ok(()) + } else { + unreachable!() + } + } +} + impl TranslateModule for LinesInvocation { fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind { let temp = format!("__AMBER_LINE_{}", meta.gen_value_id()); diff --git a/src/modules/builtin/mv.rs b/src/modules/builtin/mv.rs index 27c515f7d..b83556b85 100644 --- a/src/modules/builtin/mv.rs +++ b/src/modules/builtin/mv.rs @@ -2,7 +2,7 @@ use std::mem::swap; use crate::fragments; use crate::modules::command::modifier::CommandModifier; -use crate::modules::condition::failed::Failed; +use crate::modules::condition::failure_handler::FailureHandler; use crate::modules::expression::expr::Expr; use crate::modules::prelude::*; use crate::modules::types::{Type, Typed}; @@ -13,7 +13,7 @@ pub struct Mv { source: Box, destination: Box, modifier: CommandModifier, - failed: Failed, + failure_handler: FailureHandler, } impl SyntaxModule for Mv { @@ -23,8 +23,8 @@ impl SyntaxModule for Mv { Mv { source: Box::new(Expr::new()), destination: Box::new(Expr::new()), - failed: Failed::new(), - modifier: CommandModifier::new().parse_expr(), + failure_handler: FailureHandler::new(), + modifier: CommandModifier::new_expr(), } } @@ -33,41 +33,53 @@ impl SyntaxModule for Mv { self.modifier.use_modifiers(meta, |_this, meta| { token(meta, "mv")?; syntax(meta, &mut *self.source)?; - let mut path_type = self.source.get_type(); - if path_type != Type::Text { - let position = self.source.get_position(meta); - return error_pos!(meta, position => { - message: "Builtin function `mv` can only be used with values of type Text", - comment: format!("Given type: {}, expected type: {}", path_type, Type::Text) - }); - } syntax(meta, &mut *self.destination)?; - path_type = self.destination.get_type(); - if path_type != Type::Text { - let position = self.destination.get_position(meta); - return error_pos!(meta, position => { - message: "Builtin function `mv` can only be used with values of type Text", - comment: format!("Given type: {}, expected type: {}", path_type, Type::Text) - }); - } - syntax(meta, &mut self.failed)?; + syntax(meta, &mut self.failure_handler)?; Ok(()) }) } } +impl TypeCheckModule for Mv { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.source.typecheck(meta)?; + self.destination.typecheck(meta)?; + self.failure_handler.typecheck(meta)?; + + let source_type = self.source.get_type(); + if source_type != Type::Text { + let position = self.source.get_position(meta); + return error_pos!(meta, position => { + message: "Builtin function `mv` can only be used with values of type Text", + comment: format!("Given type: {}, expected type: {}", source_type, Type::Text) + }); + } + + let dest_type = self.destination.get_type(); + if dest_type != Type::Text { + let position = self.destination.get_position(meta); + return error_pos!(meta, position => { + message: "Builtin function `mv` can only be used with values of type Text", + comment: format!("Given type: {}, expected type: {}", dest_type, Type::Text) + }); + } + + Ok(()) + } +} + impl TranslateModule for Mv { fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind { let source = self.source.translate(meta); let destination = self.destination.translate(meta); - let failed = self.failed.translate(meta); + let handler = self.failure_handler.translate(meta); let mut is_silent = self.modifier.is_silent || meta.silenced; swap(&mut is_silent, &mut meta.silenced); let silent = meta.gen_silent().to_frag(); swap(&mut is_silent, &mut meta.silenced); BlockFragment::new(vec![ fragments!("mv ", source, " ", destination, silent), - failed, + handler, ], false).to_frag() } } diff --git a/src/modules/builtin/nameof.rs b/src/modules/builtin/nameof.rs index 07794f2da..54f6710a1 100644 --- a/src/modules/builtin/nameof.rs +++ b/src/modules/builtin/nameof.rs @@ -8,6 +8,7 @@ use heraclitus_compiler::prelude::*; #[derive(Debug, Clone)] pub struct Nameof { name: String, + token: Option, global_id: Option, } @@ -23,22 +24,29 @@ impl SyntaxModule for Nameof { fn new() -> Self { Nameof { name: String::new(), + token: None, global_id: None, } } fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { token(meta, "nameof")?; - let name = variable(meta, variable_name_extensions())?; - match meta.get_var(&name) { + self.token = meta.get_current_token(); + self.name = variable(meta, variable_name_extensions())?; + Ok(()) + } +} + +impl TypeCheckModule for Nameof { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + match meta.get_var(&self.name) { Some(var_decl) => { self.name.clone_from(&var_decl.name); self.global_id = var_decl.global_id; Ok(()) } None => { - let tok = meta.get_current_token(); - error!(meta, tok, format!("Variable '{name}' not found")) + error!(meta, self.token.clone(), format!("Variable '{}' not found", self.name)) } } } diff --git a/src/modules/command/cmd.rs b/src/modules/command/cmd.rs index 5f0967fd5..748fa8e97 100644 --- a/src/modules/command/cmd.rs +++ b/src/modules/command/cmd.rs @@ -1,9 +1,7 @@ use std::mem::swap; use crate::modules::types::{Type, Typed}; use crate::modules::expression::literal::bool; -use crate::modules::condition::failed::Failed; -use crate::modules::condition::succeeded::Succeeded; -use crate::modules::condition::then::Then; +use crate::modules::condition::failure_handler::FailureHandler; use crate::modules::expression::expr::Expr; use crate::modules::expression::interpolated_region::{InterpolatedRegionType, parse_interpolated_region}; use super::modifier::CommandModifier; @@ -15,22 +13,7 @@ pub struct Command { strings: Vec, interps: Vec, modifier: CommandModifier, - failed: Failed, - succeeded: Succeeded, - then: Then -} - -impl Command { - pub fn handle_multiple_failure_handlers(meta: &mut ParserMetadata, keyword: &str) -> SyntaxResult { - let token = meta.get_current_token(); - if let Ok(word) = token_by(meta, |word| ["failed", "succeeded", "then"].contains(&word.as_str())) { - return error!(meta, token => { - message: format!("Cannot use both '{keyword}' and '{}' blocks for the same command", word), - comment: "Use either '{keyword}' to handle both success and failure, 'failed' or 'succeeded' blocks, but not both" - }); - } - Ok(()) - } + failure_handler: FailureHandler } impl Typed for Command { @@ -46,10 +29,8 @@ impl SyntaxModule for Command { Command { strings: vec![], interps: vec![], - modifier: CommandModifier::new().parse_expr(), - failed: Failed::new(), - succeeded: Succeeded::new(), - then: Then::new() + modifier: CommandModifier::new_expr(), + failure_handler: FailureHandler::new() } } @@ -59,32 +40,18 @@ impl SyntaxModule for Command { let tok = meta.get_current_token(); (self.strings, self.interps) = parse_interpolated_region(meta, &InterpolatedRegionType::Command)?; - // Set position for failed and succeeded handlers + // Set position for failure handler let position = PositionInfo::from_between_tokens(meta, tok.clone(), meta.get_current_token()); - self.failed.set_position(position.clone()); + self.failure_handler.set_position(position.clone()); - // Try to parse then block first - match syntax(meta, &mut self.then) { - Ok(_) => return Command::handle_multiple_failure_handlers(meta, "then"), - err @ Err(Failure::Loud(_)) => return err, - _ => {} - } - - // Try to parse succeeded block - match syntax(meta, &mut self.succeeded) { - Ok(_) => return Command::handle_multiple_failure_handlers(meta, "succeeded"), - err @ Err(Failure::Loud(_)) => return err, - _ => {} - } - - // If no succeeded block, try to parse failed block - match syntax(meta, &mut self.failed) { - Ok(_) => Self::handle_multiple_failure_handlers(meta, "failed"), + // Try to parse failure handler (failed, succeeded, or exited) + match syntax(meta, &mut self.failure_handler) { + Ok(_) => Ok(()), Err(Failure::Quiet(_)) => { - // Neither succeeded, failed, nor then block found + // No failure handler found error!(meta, tok => { message: "Every command statement must handle execution result", - comment: "You can use '?' to propagate failure, 'failed' block to handle failure, 'succeeded' block to handle success, 'then' block to handle both, or 'trust' modifier to ignore results" + comment: "You can use '?' to propagate failure, 'failed' block to handle failure, 'succeeded' block to handle success, 'exited' block to handle both, or 'trust' modifier to ignore results" }) }, Err(err) => Err(err) @@ -93,15 +60,22 @@ impl SyntaxModule for Command { } } +impl TypeCheckModule for Command { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + for interp in self.interps.iter_mut() { + interp.typecheck(meta)?; + } + self.failure_handler.typecheck(meta) + } +} + impl Command { fn translate_command(&self, meta: &mut TranslateMetadata, is_statement: bool) -> FragmentKind { // Translate all interpolations let interps = self.interps.iter() .map(|item| item.translate(meta).with_quotes(false)) .collect::>(); - let failed = self.failed.translate(meta); - let succeeded = self.succeeded.translate(meta); - let then = self.then.translate(meta); + let handler = self.failure_handler.translate(meta); let mut is_silent = self.modifier.is_silent || meta.silenced; let mut is_sudo = self.modifier.is_sudo || meta.sudoed; @@ -122,25 +96,14 @@ impl Command { swap(&mut is_silent, &mut meta.silenced); swap(&mut is_sudo, &mut meta.sudoed); - // Choose between failed, succeeded, then, or no handler - let handler = if self.then.is_parsed { - then - } else if self.failed.is_parsed { - failed - } else if self.succeeded.is_parsed { - succeeded - } else { - FragmentKind::Empty - }; - if is_statement { - if let FragmentKind::Empty = handler { - translation - } else { + if self.failure_handler.is_parsed { meta.stmt_queue.push_back(translation); handler + } else { + translation } - } else if let FragmentKind::Empty = handler { + } else if !self.failure_handler.is_parsed { SubprocessFragment::new(translation).to_frag() } else { let id = meta.gen_value_id(); diff --git a/src/modules/command/modifier.rs b/src/modules/command/modifier.rs index a0205342e..21f3c0a07 100644 --- a/src/modules/command/modifier.rs +++ b/src/modules/command/modifier.rs @@ -5,17 +5,20 @@ use crate::modules::block::Block; #[derive(Debug, Clone)] pub struct CommandModifier { - pub block: Box, - pub is_block: bool, + pub block: Option>, pub is_trust: bool, pub is_silent: bool, pub is_sudo: bool } impl CommandModifier { - pub fn parse_expr(mut self) -> Self { - self.is_block = false; - self + pub fn new_expr() -> Self { + CommandModifier { + block: None, + is_trust: false, + is_silent: false, + is_sudo: false + } } pub fn use_modifiers( @@ -80,8 +83,7 @@ impl SyntaxModule for CommandModifier { fn new() -> Self { CommandModifier { - block: Box::new(Block::new().with_no_indent()), - is_block: true, + block: Some(Box::new(Block::new().with_no_indent())), is_trust: false, is_silent: false, is_sudo: false @@ -90,9 +92,10 @@ impl SyntaxModule for CommandModifier { fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { self.parse_modifier_sequence(meta)?; - if self.is_block { + if let Some(mut block) = self.block.take() { return self.use_modifiers(meta, |this, meta| { - syntax(meta, &mut *this.block)?; + syntax(meta, &mut *block)?; + this.block = Some(block); Ok(()) }) } @@ -100,12 +103,21 @@ impl SyntaxModule for CommandModifier { } } +impl TypeCheckModule for CommandModifier { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + if let Some(block) = &mut self.block { + block.typecheck(meta)?; + } + Ok(()) + } +} + impl TranslateModule for CommandModifier { fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind { - if self.is_block { + if let Some(block) = &self.block { meta.silenced = self.is_silent; meta.sudoed = self.is_sudo; - let result = self.block.translate(meta); + let result = block.translate(meta); meta.silenced = false; meta.sudoed = false; result diff --git a/src/modules/condition/failed.rs b/src/modules/condition/failed.rs deleted file mode 100644 index 0444788d3..000000000 --- a/src/modules/condition/failed.rs +++ /dev/null @@ -1,187 +0,0 @@ -use heraclitus_compiler::prelude::*; -use crate::{fragments, raw_fragment}; -use crate::modules::prelude::*; -use crate::modules::block::Block; -use crate::modules::types::Type; -use crate::modules::variable::variable_name_extensions; - -#[derive(Debug, Clone)] -pub struct Failed { - pub is_parsed: bool, - is_question_mark: bool, - error_position: Option, - function_name: Option, - is_main: bool, - block: Box, - param_name: String, - param_global_id: Option -} - -impl Failed { - pub fn set_position(&mut self, position: PositionInfo) { - self.error_position = Some(position); - } - - pub fn set_function_name(&mut self, name: String) { - self.function_name = Some(name); - } -} - -impl SyntaxModule for Failed { - syntax_name!("Failed Expression"); - - fn new() -> Self { - Failed { - is_parsed: false, - is_question_mark: false, - is_main: false, - function_name: None, - error_position: None, - block: Box::new(Block::new().with_needs_noop().with_condition()), - param_name: String::new(), - param_global_id: None - } - } - - fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { - let tok = meta.get_current_token(); - if token(meta, "?").is_ok() { - if !meta.context.is_fun_ctx && !meta.context.is_main_ctx && !meta.context.is_trust_ctx { - return error!(meta, tok, "The '?' operator can only be used in the main block or inside a function body") - } - self.is_question_mark = true; - } else { - match token(meta, "failed") { - Ok(_) => { - // Check if there's a parameter in parentheses - if token(meta, "(").is_ok() { - context!({ - let param_tok = meta.get_current_token(); - - // Check if we immediately hit a closing paren (empty parameter) - if token(meta, ")").is_ok() { - let pos = PositionInfo::from_between_tokens(meta, param_tok, meta.get_current_token()); - return error_pos!(meta, pos, "Parameter name cannot be empty"); - } - - self.param_name = variable(meta, variable_name_extensions())?; - token(meta, ")")?; - - // Add the parameter variable to the scope and parse the block - meta.with_push_scope(|meta| { - self.param_global_id = meta.add_var(&self.param_name, Type::Int, false); - syntax(meta, &mut *self.block)?; - Ok(()) - })?; - - if self.block.is_empty() { - let message = Message::new_warn_at_token(meta, meta.get_current_token()) - .message("Empty failed block") - .comment("You should use 'trust' modifier to run commands without handling errors"); - meta.add_message(message); - } - Ok(()) - }, |pos| { - error_pos!(meta, pos, "Failed to parse failed block") - })?; - } else { - // No parameter, parse block normally - let tok = meta.get_current_token(); - syntax(meta, &mut *self.block)?; - if self.block.is_empty() { - let message = Message::new_warn_at_token(meta, tok) - .message("Empty failed block") - .comment("You should use 'trust' modifier to run commands without handling errors"); - meta.add_message(message); - } - } - }, - Err(_) => if meta.context.is_trust_ctx { - self.is_main = meta.context.is_main_ctx; - self.is_parsed = true; - return Ok(()); - } else { - match (self.function_name.clone(), self.error_position.clone()) { - (Some(fun_name), Some(pos)) => { - return error_pos!(meta, pos, format!("Failed function call '{fun_name}' must be followed by a 'then', 'succeeded' or 'failed' block, statement or operator '?'")) - } - (None, Some(pos)) => { - return error_pos!(meta, pos, format!("Failed command must be followed by a 'then', 'succeeded' or 'failed' block, statement or operator '?'")) - } - _ => { - return error!(meta, tok, format!("Failed expression must be followed by a 'then', 'succeeded' or 'failed' block, statement or operator '?'")) - } - } - } - } - } - self.is_main = meta.context.is_main_ctx; - self.is_parsed = true; - Ok(()) - } -} - -impl TranslateModule for Failed { - fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind { - if self.is_parsed { - let block = self.block.translate(meta); - // the condition of '$?' clears the status code thus we need to store it in a variable - let status_variable_stmt = VarStmtFragment::new("__status", Type::Int, fragments!("$?")); - let status_variable_expr = VarExprFragment::from_stmt(&status_variable_stmt); - if self.is_question_mark { - // Set default return value if failure happened in a function - let clear_return = if !self.is_main { - let fun_meta = meta.fun_meta.as_ref().expect("Function name and return type not set"); - let stmt = VarStmtFragment::new(&fun_meta.mangled_name(), fun_meta.get_type(), fun_meta.default_return()) - .with_optimization_when_unused(false); - stmt.to_frag() - } else { - FragmentKind::Empty - }; - let ret = if self.is_main { "exit" } else { "return" }; - let ret = fragments!(raw_fragment!("{ret} "), status_variable_expr.clone().to_frag()); - return BlockFragment::new(vec![ - status_variable_stmt.to_frag(), - fragments!("if [ ", status_variable_expr.to_frag(), " != 0 ]; then"), - BlockFragment::new(vec![ - clear_return, - ret, - ], true).to_frag(), - fragments!("fi"), - ], false).to_frag() - } - match &block { - FragmentKind::Empty => { - status_variable_stmt.to_frag() - }, - FragmentKind::Block(block) if block.statements.is_empty() => { - status_variable_stmt.to_frag() - }, - _ => { - // If a parameter name is provided, assign the status to it - if !self.param_name.is_empty() { - let param_assignment = VarStmtFragment::new(&self.param_name, Type::Int, status_variable_expr.clone().to_frag()) - .with_global_id(self.param_global_id); - - BlockFragment::new(vec![ - status_variable_stmt.to_frag(), - fragments!("if [ ", status_variable_expr.to_frag(), " != 0 ]; then"), - param_assignment.to_frag(), - block, - fragments!("fi"), - ], false).to_frag() - } else { - BlockFragment::new(vec![ - status_variable_stmt.to_frag(), - fragments!("if [ ", status_variable_expr.to_frag(), " != 0 ]; then"), - block, - fragments!("fi"), - ], false).to_frag() - } - } - } - } else { - FragmentKind::Empty - } - } -} diff --git a/src/modules/condition/failure_handler.rs b/src/modules/condition/failure_handler.rs new file mode 100644 index 000000000..b1f88610b --- /dev/null +++ b/src/modules/condition/failure_handler.rs @@ -0,0 +1,303 @@ +use heraclitus_compiler::prelude::*; +use crate::{fragments, raw_fragment}; +use crate::modules::prelude::*; +use crate::modules::block::Block; +use crate::modules::types::Type; +use crate::modules::variable::variable_name_extensions; + +#[derive(Debug, Clone, PartialEq)] +pub enum FailureType { + Failed, + Succeeded, + Exited, +} + +impl FailureType { + pub fn to_string(&self) -> &'static str { + match self { + FailureType::Failed => "failed", + FailureType::Succeeded => "succeeded", + FailureType::Exited => "exited", + } + } +} + +#[derive(Debug, Clone)] +pub struct FailureHandler { + pub is_parsed: bool, + pub failure_type: FailureType, + is_question_mark: bool, + error_position: Option, + function_name: Option, + is_main: bool, + block: Box, + param_name: String, + param_global_id: Option +} + +impl FailureHandler { + pub fn set_position(&mut self, position: PositionInfo) { + self.error_position = Some(position); + } + + pub fn set_function_name(&mut self, name: String) { + self.function_name = Some(name); + } +} + +impl SyntaxModule for FailureHandler { + syntax_name!("Failure Handler Expression"); + + fn new() -> Self { + FailureHandler { + is_parsed: false, + failure_type: FailureType::Failed, + is_question_mark: false, + is_main: false, + function_name: None, + error_position: None, + block: Box::new(Block::new().with_needs_noop().with_condition()), + param_name: String::new(), + param_global_id: None + } + } + + fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + let tok = meta.get_current_token(); + + // Check for ? operator first + if token(meta, "?").is_ok() { + if !meta.context.is_fun_ctx && !meta.context.is_main_ctx && !meta.context.is_trust_ctx { + return error!(meta, tok, "The '?' operator can only be used in the main block or inside a function body") + } + self.is_question_mark = true; + self.failure_type = FailureType::Failed; + } else { + let keyword = ["failed", "succeeded", "exited"].iter().fold(None, |acc, keyword| { + acc.or_else(|| token(meta, keyword).ok().map(|_| *keyword)) + }); + + match keyword { + Some("failed") => { + self.failure_type = FailureType::Failed; + + // Check if there's a parameter in parentheses (only for failed) + if token(meta, "(").is_ok() { + context!({ + let param_tok = meta.get_current_token(); + + // Check if we immediately hit a closing paren (empty parameter) + if token(meta, ")").is_ok() { + let pos = PositionInfo::from_between_tokens(meta, param_tok, meta.get_current_token()); + return error_pos!(meta, pos, "Parameter name cannot be empty"); + } + + self.param_name = variable(meta, variable_name_extensions())?; + token(meta, ")")?; + + // Parse the block (scope and variable will be added in typecheck) + syntax(meta, &mut *self.block)?; + Ok(()) + }, |pos| { + error_pos!(meta, pos, "Failed to parse failed block") + })?; + } else { + // No parameter, parse block normally + syntax(meta, &mut *self.block)?; + } + }, + Some("succeeded") => { + self.failure_type = FailureType::Succeeded; + syntax(meta, &mut *self.block)?; + }, + Some("exited") => { + self.failure_type = FailureType::Exited; + + // Check if there's a parameter in parentheses (optional for exited) + if token(meta, "(").is_ok() { + context!({ + let param_tok = meta.get_current_token(); + + // Check if we immediately hit a closing paren (empty parameter) + if token(meta, ")").is_ok() { + let pos = PositionInfo::from_between_tokens(meta, param_tok, meta.get_current_token()); + return error_pos!(meta, pos, "Parameter name cannot be empty"); + } + + self.param_name = variable(meta, variable_name_extensions())?; + token(meta, ")")?; + + // Parse the block (scope and variable will be added in typecheck) + syntax(meta, &mut *self.block)?; + Ok(()) + }, |pos| { + error_pos!(meta, pos, "Failed to parse exited block") + })?; + } else { + // No parameter, parse block normally + syntax(meta, &mut *self.block)?; + } + }, + Some(keyword) => { + unimplemented!("Keyword '{keyword}' is not yet implemented") + }, + None => { + if meta.context.is_trust_ctx { + self.is_main = meta.context.is_main_ctx; + self.is_parsed = true; + return Ok(()); + } else { + match (self.function_name.clone(), self.error_position.clone()) { + (Some(fun_name), Some(pos)) => { + return error_pos!(meta, pos, format!("Failed function call '{fun_name}' must be followed by an 'exited', 'succeeded' or 'failed' block, statement or operator '?'")) + } + (None, Some(pos)) => { + return error_pos!(meta, pos, format!("Failed command must be followed by an 'exited', 'succeeded' or 'failed' block, statement or operator '?'")) + } + _ => { + return error!(meta, tok, format!("Failure handler expression must be followed by an 'exited', 'succeeded' or 'failed' block, statement or operator '?'")) + } + } + } + } + } + + // Check for empty block once after parsing the appropriate variant + if self.block.is_empty() { + let message = Message::new_warn_at_token(meta, meta.get_current_token()) + .message(format!("Empty {} block", self.failure_type.to_string())) + .comment("You should use 'trust' modifier to run commands without handling errors"); + meta.add_message(message); + } + + dbg!(keyword); + if let Some(keyword) = keyword { + let next_tok = meta.get_current_token(); + let next_word = token_by(meta, |word| ["failed", "succeeded", "exited"].contains(&word.as_str())); + if let Ok(word) = next_word { + return error!(meta, next_tok => { + message: format!("Cannot use both '{keyword}' and '{word}' blocks for the same command"), + comment: "Use either '{keyword}' to handle both success and failure, 'failed' or 'succeeded' blocks, but not both" + }); + } + } + } + + + self.is_main = meta.context.is_main_ctx; + self.is_parsed = true; + Ok(()) + } +} + +impl TypeCheckModule for FailureHandler { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + // If we have a parameter (exit code for failed or exited), add it to scope and typecheck the block + if !self.param_name.is_empty() && (self.failure_type == FailureType::Failed || self.failure_type == FailureType::Exited) { + meta.with_push_scope(true, |meta| { + self.param_global_id = meta.add_var(&self.param_name, Type::Int, false); + self.block.typecheck(meta) + }) + } else { + self.block.typecheck(meta) + } + } +} + +impl TranslateModule for FailureHandler { + fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind { + if !self.is_parsed { + return FragmentKind::Empty; + } + + let block = self.block.translate(meta); + // the condition of '$?' clears the status code thus we need to store it in a variable + let status_variable_stmt = VarStmtFragment::new("__status", Type::Int, fragments!("$?")); + let status_variable_expr = VarExprFragment::from_stmt(&status_variable_stmt); + + if self.is_question_mark { + // Set default return value if failure happened in a function + let clear_return = if !self.is_main { + let fun_meta = meta.fun_meta.as_ref().expect("Function name and return type not set"); + let stmt = VarStmtFragment::new(&fun_meta.mangled_name(), fun_meta.get_type(), fun_meta.default_return()) + .with_optimization_when_unused(false); + stmt.to_frag() + } else { + FragmentKind::Empty + }; + let ret = if self.is_main { "exit" } else { "return" }; + let ret = fragments!(raw_fragment!("{ret} "), status_variable_expr.clone().to_frag()); + return BlockFragment::new(vec![ + status_variable_stmt.to_frag(), + fragments!("if [ ", status_variable_expr.to_frag(), " != 0 ]; then"), + BlockFragment::new(vec![ + clear_return, + ret, + ], true).to_frag(), + fragments!("fi"), + ], false).to_frag(); + } + + match &block { + FragmentKind::Empty => { + status_variable_stmt.to_frag() + }, + FragmentKind::Block(block) if block.statements.is_empty() => { + status_variable_stmt.to_frag() + }, + _ => { + match self.failure_type { + FailureType::Failed => { + // If a parameter name is provided, assign the status to it + if !self.param_name.is_empty() { + let param_assignment = VarStmtFragment::new(&self.param_name, Type::Int, status_variable_expr.clone().to_frag()) + .with_global_id(self.param_global_id); + + BlockFragment::new(vec![ + status_variable_stmt.to_frag(), + fragments!("if [ ", status_variable_expr.to_frag(), " != 0 ]; then"), + param_assignment.to_frag(), + block, + fragments!("fi"), + ], false).to_frag() + } else { + BlockFragment::new(vec![ + status_variable_stmt.to_frag(), + fragments!("if [ ", status_variable_expr.to_frag(), " != 0 ]; then"), + block, + fragments!("fi"), + ], false).to_frag() + } + }, + FailureType::Succeeded => { + BlockFragment::new(vec![ + status_variable_stmt.to_frag(), + fragments!("if [ ", status_variable_expr.to_frag(), " = 0 ]; then"), + block, + fragments!("fi"), + ], false).to_frag() + }, + FailureType::Exited => { + // Exited always runs, regardless of exit code + // If a parameter name is provided, assign the status to it + if !self.param_name.is_empty() { + let param_assignment = VarStmtFragment::new(&self.param_name, Type::Int, status_variable_expr.clone().to_frag()) + .with_global_id(self.param_global_id); + + BlockFragment::new(vec![ + status_variable_stmt.to_frag(), + param_assignment.to_frag(), + block, + ], false).to_frag() + } else { + BlockFragment::new(vec![ + status_variable_stmt.to_frag(), + block, + ], false).to_frag() + } + } + } + } + } + } +} diff --git a/src/modules/condition/ifchain.rs b/src/modules/condition/ifchain.rs index e117c0407..071791caf 100644 --- a/src/modules/condition/ifchain.rs +++ b/src/modules/condition/ifchain.rs @@ -53,6 +53,23 @@ impl SyntaxModule for IfChain { } } +impl TypeCheckModule for IfChain { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + // Type-check all condition-block pairs + for (cond, block) in &mut self.cond_blocks { + cond.typecheck(meta)?; + block.typecheck(meta)?; + } + + // Type-check the false block if it exists + if let Some(false_block) = &mut self.false_block { + false_block.typecheck(meta)?; + } + + Ok(()) + } +} + impl TranslateModule for IfChain { fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind { let mut result = vec![]; diff --git a/src/modules/condition/ifcond.rs b/src/modules/condition/ifcond.rs index 35db4bc49..99701ca6b 100644 --- a/src/modules/condition/ifcond.rs +++ b/src/modules/condition/ifcond.rs @@ -3,7 +3,7 @@ use crate::modules::prelude::*; use crate::fragments; use crate::modules::expression::expr::Expr; use crate::utils::cc_flags::{CCFlags, get_ccflag_name}; -use crate::modules::statement::stmt::{Statement, StatementType}; +use crate::modules::statement::stmt::{Statement, StmtType}; use crate::modules::block::Block; #[derive(Debug, Clone)] @@ -15,7 +15,7 @@ pub struct IfCondition { impl IfCondition { fn prevent_not_using_if_chain(&self, meta: &mut ParserMetadata, statement: &Statement, tok: Option) -> Result<(), Failure> { - let is_not_if_chain = matches!(statement.value.as_ref().unwrap(), StatementType::IfCondition(_) | StatementType::IfChain(_)); + let is_not_if_chain = matches!(statement.value.as_ref().unwrap(), StmtType::IfCondition(_) | StmtType::IfChain(_)); if is_not_if_chain && !meta.context.cc_flags.contains(&CCFlags::AllowNestedIfElse) { let flag_name = get_ccflag_name(CCFlags::AllowNestedIfElse); let message = Message::new_warn_at_token(meta, tok) @@ -62,6 +62,18 @@ impl SyntaxModule for IfCondition { } } +impl TypeCheckModule for IfCondition { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.expr.typecheck(meta)?; + self.true_block.typecheck(meta)?; + // Type-check the false block if it exists + if let Some(false_block) = &mut self.false_block { + false_block.typecheck(meta)?; + } + Ok(()) + } +} + impl TranslateModule for IfCondition { fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind { let mut result = vec![]; diff --git a/src/modules/condition/mod.rs b/src/modules/condition/mod.rs index 5955551d7..0fc8f151f 100644 --- a/src/modules/condition/mod.rs +++ b/src/modules/condition/mod.rs @@ -1,5 +1,3 @@ pub mod ifcond; pub mod ifchain; -pub mod failed; -pub mod succeeded; -pub mod then; +pub mod failure_handler; diff --git a/src/modules/condition/succeeded.rs b/src/modules/condition/succeeded.rs deleted file mode 100644 index 0afe1a06b..000000000 --- a/src/modules/condition/succeeded.rs +++ /dev/null @@ -1,78 +0,0 @@ -use heraclitus_compiler::prelude::*; -use crate::fragments; -use crate::modules::prelude::*; -use crate::modules::block::Block; -use crate::modules::types::Type; - -#[derive(Debug, Clone)] -pub struct Succeeded { - pub is_parsed: bool, - block: Box -} - -impl SyntaxModule for Succeeded { - syntax_name!("Succeeded Expression"); - - fn new() -> Self { - Succeeded { - is_parsed: false, - block: Box::new(Block::new().with_needs_noop().with_condition()) - } - } - - fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { - match token(meta, "succeeded") { - Ok(_) => { - let tok = meta.get_current_token(); - syntax(meta, &mut *self.block)?; - if self.block.is_empty() { - let message = Message::new_warn_at_token(meta, tok) - .message("Empty succeeded block") - .comment("You should use 'trust' modifier to run commands without handling errors"); - meta.add_message(message); - } - self.is_parsed = true; - Ok(()) - }, - Err(err) => { - // If we're in a trust context, mark as parsed - if meta.context.is_trust_ctx { - self.is_parsed = true; - Ok(()) - } else { - Err(err) - } - } - } - } -} - -impl TranslateModule for Succeeded { - fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind { - if self.is_parsed { - let block = self.block.translate(meta); - // the condition of '$?' clears the status code thus we need to store it in a variable - let status_variable_stmt = VarStmtFragment::new("__status", Type::Int, fragments!("$?")); - let status_variable_expr = VarExprFragment::from_stmt(&status_variable_stmt); - - match &block { - FragmentKind::Empty => { - status_variable_stmt.to_frag() - }, - FragmentKind::Block(block) if block.statements.is_empty() => { - status_variable_stmt.to_frag() - }, - _ => { - BlockFragment::new(vec![ - status_variable_stmt.to_frag(), - fragments!("if [ ", status_variable_expr.to_frag(), " = 0 ]; then"), - block, - fragments!("fi"), - ], false).to_frag() - } - } - } else { - FragmentKind::Empty - } - } -} diff --git a/src/modules/condition/then.rs b/src/modules/condition/then.rs deleted file mode 100644 index 347d4b371..000000000 --- a/src/modules/condition/then.rs +++ /dev/null @@ -1,107 +0,0 @@ -use heraclitus_compiler::prelude::*; -use crate::fragments; -use crate::modules::prelude::*; -use crate::modules::block::Block; -use crate::modules::types::Type; -use crate::modules::variable::variable_name_extensions; - -#[derive(Debug, Clone)] -pub struct Then { - pub is_parsed: bool, - block: Box, - param_name: String, - param_global_id: Option -} - -impl SyntaxModule for Then { - syntax_name!("Then Expression"); - - fn new() -> Self { - Then { - is_parsed: false, - block: Box::new(Block::new().with_needs_noop().with_condition()), - param_name: String::new(), - param_global_id: None - } - } - - fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { - match token(meta, "then") { - Ok(_) => { - context!({ - // Parse the parameter in parentheses - token(meta, "(")?; - let param_tok = meta.get_current_token(); - - // Check if we immediately hit a closing paren (empty parameter) - if token(meta, ")").is_ok() { - let pos = PositionInfo::from_between_tokens(meta, param_tok, meta.get_current_token()); - return error_pos!(meta, pos, "Parameter name cannot be empty"); - } - - self.param_name = variable(meta, variable_name_extensions())?; - token(meta, ")")?; - - // Add the parameter variable to the scope and parse the block - meta.with_push_scope(|meta| { - self.param_global_id = meta.add_var(&self.param_name, Type::Int, false); - syntax(meta, &mut *self.block)?; - Ok(()) - })?; - - if self.block.is_empty() { - let message = Message::new_warn_at_token(meta, meta.get_current_token()) - .message("Empty then block") - .comment("You should use 'trust' modifier to run commands without handling errors"); - meta.add_message(message); - } - self.is_parsed = true; - Ok(()) - }, |pos| { - error_pos!(meta, pos, "Failed to parse then block") - }) - }, - Err(err) => { - // If we're in a trust context, mark as parsed - if meta.context.is_trust_ctx { - self.is_parsed = true; - Ok(()) - } else { - Err(err) - } - } - } - } -} - -impl TranslateModule for Then { - fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind { - if self.is_parsed { - let block = self.block.translate(meta); - // the condition of '$?' clears the status code thus we need to store it in a variable - let status_variable_stmt = VarStmtFragment::new("__status", Type::Int, fragments!("$?")); - let status_variable_expr = VarExprFragment::from_stmt(&status_variable_stmt); - - match &block { - FragmentKind::Empty => { - status_variable_stmt.to_frag() - }, - FragmentKind::Block(block) if block.statements.is_empty() => { - status_variable_stmt.to_frag() - }, - _ => { - let param_assignment = VarStmtFragment::new(&self.param_name, Type::Int, status_variable_expr.to_frag()) - .with_global_id(self.param_global_id); - - BlockFragment::new(vec![ - status_variable_stmt.to_frag(), - param_assignment.to_frag(), - block, - ], false).to_frag() - } - } - } else { - FragmentKind::Empty - } - } -} diff --git a/src/modules/expression/binop/add.rs b/src/modules/expression/binop/add.rs index 4dda5dd8e..0e6860d17 100644 --- a/src/modules/expression/binop/add.rs +++ b/src/modules/expression/binop/add.rs @@ -46,7 +46,15 @@ impl SyntaxModule for Add { } } - fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + fn parse(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult { + Ok(()) + } +} + +impl TypeCheckModule for Add { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.left.typecheck(meta)?; + self.right.typecheck(meta)?; self.kind = Self::typecheck_allowed_types(meta, "addition", &self.left, &self.right, &[ Type::Num, Type::Int, diff --git a/src/modules/expression/binop/and.rs b/src/modules/expression/binop/and.rs index 7e6ecd5ec..edd430bc3 100644 --- a/src/modules/expression/binop/and.rs +++ b/src/modules/expression/binop/and.rs @@ -43,7 +43,15 @@ impl SyntaxModule for And { } } - fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + fn parse(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult { + Ok(()) + } +} + +impl TypeCheckModule for And { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.left.typecheck(meta)?; + self.right.typecheck(meta)?; Self::typecheck_equality(meta, &self.left, &self.right)?; Ok(()) } diff --git a/src/modules/expression/binop/div.rs b/src/modules/expression/binop/div.rs index 9c3b2a143..2a9e89a82 100644 --- a/src/modules/expression/binop/div.rs +++ b/src/modules/expression/binop/div.rs @@ -46,7 +46,15 @@ impl SyntaxModule for Div { } } - fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + fn parse(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult { + Ok(()) + } +} + +impl TypeCheckModule for Div { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.left.typecheck(meta)?; + self.right.typecheck(meta)?; self.kind = Self::typecheck_allowed_types(meta, "division", &self.left, &self.right, &[ Type::Num, Type::Int, diff --git a/src/modules/expression/binop/eq.rs b/src/modules/expression/binop/eq.rs index af3a1aa7f..bf00e2463 100644 --- a/src/modules/expression/binop/eq.rs +++ b/src/modules/expression/binop/eq.rs @@ -44,7 +44,15 @@ impl SyntaxModule for Eq { } } - fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + fn parse(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult { + Ok(()) + } +} + +impl TypeCheckModule for Eq { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.left.typecheck(meta)?; + self.right.typecheck(meta)?; Self::typecheck_equality(meta, &self.left, &self.right)?; Ok(()) } diff --git a/src/modules/expression/binop/ge.rs b/src/modules/expression/binop/ge.rs index cb9a98818..9c4407cf7 100644 --- a/src/modules/expression/binop/ge.rs +++ b/src/modules/expression/binop/ge.rs @@ -43,7 +43,15 @@ impl SyntaxModule for Ge { } } - fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + fn parse(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult { + Ok(()) + } +} + +impl TypeCheckModule for Ge { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.left.typecheck(meta)?; + self.right.typecheck(meta)?; Self::typecheck_allowed_types(meta, "comparison", &self.left, &self.right, &[ Type::Num, Type::Int, diff --git a/src/modules/expression/binop/gt.rs b/src/modules/expression/binop/gt.rs index 80486395f..f34e281ef 100644 --- a/src/modules/expression/binop/gt.rs +++ b/src/modules/expression/binop/gt.rs @@ -43,7 +43,15 @@ impl SyntaxModule for Gt { } } - fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + fn parse(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult { + Ok(()) + } +} + +impl TypeCheckModule for Gt { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.left.typecheck(meta)?; + self.right.typecheck(meta)?; Self::typecheck_allowed_types(meta, "comparison", &self.left, &self.right, &[ Type::Num, Type::Int, diff --git a/src/modules/expression/binop/le.rs b/src/modules/expression/binop/le.rs index e9b4d7f95..739e87f79 100644 --- a/src/modules/expression/binop/le.rs +++ b/src/modules/expression/binop/le.rs @@ -43,7 +43,15 @@ impl SyntaxModule for Le { } } - fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + fn parse(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult { + Ok(()) + } +} + +impl TypeCheckModule for Le { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.left.typecheck(meta)?; + self.right.typecheck(meta)?; Self::typecheck_allowed_types(meta, "comparison", &self.left, &self.right, &[ Type::Num, Type::Int, diff --git a/src/modules/expression/binop/lt.rs b/src/modules/expression/binop/lt.rs index 63ff08285..bd93482be 100644 --- a/src/modules/expression/binop/lt.rs +++ b/src/modules/expression/binop/lt.rs @@ -43,7 +43,15 @@ impl SyntaxModule for Lt { } } - fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + fn parse(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult { + Ok(()) + } +} + +impl TypeCheckModule for Lt { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.left.typecheck(meta)?; + self.right.typecheck(meta)?; Self::typecheck_allowed_types(meta, "comparison", &self.left, &self.right, &[ Type::Num, Type::Int, diff --git a/src/modules/expression/binop/mod.rs b/src/modules/expression/binop/mod.rs index a2930dfd0..3c7795526 100644 --- a/src/modules/expression/binop/mod.rs +++ b/src/modules/expression/binop/mod.rs @@ -3,6 +3,7 @@ use crate::modules::types::{Type, Typed}; use crate::utils::metadata::ParserMetadata; use crate::utils::pluralize; use super::super::expression::expr::Expr; +use crate::modules::typecheck::TypeCheckModule; pub mod add; pub mod sub; @@ -19,7 +20,7 @@ pub mod eq; pub mod neq; pub mod range; -pub trait BinOp: SyntaxModule { +pub trait BinOp: SyntaxModule + TypeCheckModule { fn set_left(&mut self, left: Expr); fn set_right(&mut self, right: Expr); fn parse_operator(&mut self, meta: &mut ParserMetadata) -> SyntaxResult; diff --git a/src/modules/expression/binop/modulo.rs b/src/modules/expression/binop/modulo.rs index 2c5852cd9..0212186c3 100644 --- a/src/modules/expression/binop/modulo.rs +++ b/src/modules/expression/binop/modulo.rs @@ -44,7 +44,15 @@ impl SyntaxModule for Modulo { } } - fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + fn parse(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult { + Ok(()) + } +} + +impl TypeCheckModule for Modulo { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.left.typecheck(meta)?; + self.right.typecheck(meta)?; self.kind = Self::typecheck_allowed_types(meta, "modulo", &self.left, &self.right, &[ Type::Num, Type::Int, diff --git a/src/modules/expression/binop/mul.rs b/src/modules/expression/binop/mul.rs index 84f5cca24..37c8cdaf9 100644 --- a/src/modules/expression/binop/mul.rs +++ b/src/modules/expression/binop/mul.rs @@ -45,7 +45,15 @@ impl SyntaxModule for Mul { } } - fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + fn parse(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult { + Ok(()) + } +} + +impl TypeCheckModule for Mul { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.left.typecheck(meta)?; + self.right.typecheck(meta)?; self.kind = Self::typecheck_allowed_types(meta, "multiplication", &self.left, &self.right, &[ Type::Num, Type::Int, diff --git a/src/modules/expression/binop/neq.rs b/src/modules/expression/binop/neq.rs index 6d599e8b1..63432d3e4 100644 --- a/src/modules/expression/binop/neq.rs +++ b/src/modules/expression/binop/neq.rs @@ -44,7 +44,15 @@ impl SyntaxModule for Neq { } } - fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + fn parse(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult { + Ok(()) + } +} + +impl TypeCheckModule for Neq { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.left.typecheck(meta)?; + self.right.typecheck(meta)?; Self::typecheck_equality(meta, &self.left, &self.right)?; Ok(()) } diff --git a/src/modules/expression/binop/or.rs b/src/modules/expression/binop/or.rs index 0bc5c96af..b958cb486 100644 --- a/src/modules/expression/binop/or.rs +++ b/src/modules/expression/binop/or.rs @@ -43,7 +43,15 @@ impl SyntaxModule for Or { } } - fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + fn parse(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult { + Ok(()) + } +} + +impl TypeCheckModule for Or { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.left.typecheck(meta)?; + self.right.typecheck(meta)?; Self::typecheck_equality(meta, &self.left, &self.right)?; Ok(()) } diff --git a/src/modules/expression/binop/range.rs b/src/modules/expression/binop/range.rs index b6e2edf39..d5056dda4 100644 --- a/src/modules/expression/binop/range.rs +++ b/src/modules/expression/binop/range.rs @@ -47,7 +47,15 @@ impl SyntaxModule for Range { } } - fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + fn parse(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult { + Ok(()) + } +} + +impl TypeCheckModule for Range { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.from.typecheck(meta)?; + self.to.typecheck(meta)?; Self::typecheck_allowed_types(meta, "range operator", &self.from, &self.to, &[Type::Int])?; Ok(()) } diff --git a/src/modules/expression/binop/sub.rs b/src/modules/expression/binop/sub.rs index c79f31848..3630451bc 100644 --- a/src/modules/expression/binop/sub.rs +++ b/src/modules/expression/binop/sub.rs @@ -45,7 +45,15 @@ impl SyntaxModule for Sub { } } - fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + fn parse(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult { + Ok(()) + } +} + +impl TypeCheckModule for Sub { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.left.typecheck(meta)?; + self.right.typecheck(meta)?; self.kind = Self::typecheck_allowed_types(meta, "subtraction", &self.left, &self.right, &[ Type::Num, Type::Int, diff --git a/src/modules/expression/expr.rs b/src/modules/expression/expr.rs index 4020301a1..b700e1bf5 100644 --- a/src/modules/expression/expr.rs +++ b/src/modules/expression/expr.rs @@ -5,6 +5,7 @@ use crate::modules::command::cmd::Command; use crate::modules::expression::binop::BinOp; use crate::modules::prelude::FragmentKind; use crate::modules::types::{Typed, Type}; +use crate::modules::typecheck::TypeCheckModule; use crate::translate::module::TranslateModule; use crate::utils::{ParserMetadata, TranslateMetadata}; use crate::modules::expression::typeop::TypeOp; @@ -50,7 +51,13 @@ use super::ternop::ternary::Ternary; use crate::modules::function::invocation::FunctionInvocation; use crate::modules::builtin::lines::LinesInvocation; use crate::modules::builtin::nameof::Nameof; -use crate::{document_expression, parse_expr, parse_expr_group, translate_expression}; +use crate::{ + document_expression, + parse_expression, + parse_expression_group, + typecheck_expression, + translate_expression +}; #[derive(Debug, Clone)] pub enum ExprType { @@ -137,7 +144,7 @@ impl SyntaxModule for Expr { } fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { - let result = parse_expr!(meta, [ + let result = parse_expression!(meta, [ ternary @ TernOp => [ Ternary ], range @ BinOp => [ Range ], or @ BinOp => [ Or ], @@ -165,30 +172,25 @@ impl SyntaxModule for Expr { } } +impl TypeCheckModule for Expr { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + typecheck_expression!(self, meta, self.value.as_mut().unwrap(), [ + Add, And, Array, Bool, Cast, Command, Div, Eq, FunctionInvocation, + Ge, Gt, Integer, Is, Le, Len, LinesInvocation, Lt, Modulo, + Mul, Nameof, Neg, Neq, Not, Null, Number, Or, Parentheses, + Range, Status, Sub, Ternary, Text, VariableGet + ]); + Ok(()) + } +} + impl TranslateModule for Expr { fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind { translate_expression!(meta, self.value.as_ref().unwrap(), [ - // Ternary conditional - Ternary, - // Logical operators - And, Or, - // Comparison operators - Gt, Ge, Lt, Le, Eq, Neq, - // Arithmetic operators - Add, Sub, Mul, Div, Modulo, - // Binary operators - Range, Cast, Is, - // Unary operators - Not, Neg, Nameof, Len, - // Literals - Parentheses, Bool, Number, Integer, Text, - Array, Null, Status, - // Builtin invocation - LinesInvocation, - // Function invocation - FunctionInvocation, Command, - // Variable access - VariableGet + Add, And, Array, Bool, Cast, Command, Div, Eq, FunctionInvocation, + Ge, Gt, Integer, Is, Le, Len, LinesInvocation, Lt, Modulo, + Mul, Nameof, Neg, Neq, Not, Null, Number, Or, Parentheses, + Range, Status, Sub, Ternary, Text, VariableGet ]) } } @@ -196,27 +198,10 @@ impl TranslateModule for Expr { impl DocumentationModule for Expr { fn document(&self, meta: &ParserMetadata) -> String { document_expression!(meta, self.value.as_ref().unwrap(), [ - // Ternary conditional - Ternary, - // Logical operators - And, Or, - // Comparison operators - Gt, Ge, Lt, Le, Eq, Neq, - // Arithmetic operators - Add, Sub, Mul, Div, Modulo, - // Binary operators - Range, Cast, Is, - // Unary operators - Not, Neg, Nameof, Len, - // Literals - Parentheses, Bool, Number, Integer, Text, - Array, Null, Status, - // Builtin invocation - LinesInvocation, - // Function invocation - FunctionInvocation, Command, - // Variable access - VariableGet + Add, And, Array, Bool, Cast, Command, Div, Eq, FunctionInvocation, + Ge, Gt, Integer, Is, Le, Len, LinesInvocation, Lt, Modulo, + Mul, Nameof, Neg, Neq, Not, Null, Number, Or, Parentheses, + Range, Status, Sub, Ternary, Text, VariableGet ]) } } diff --git a/src/modules/expression/literal/array.rs b/src/modules/expression/literal/array.rs index 25be64a7a..e048f913f 100644 --- a/src/modules/expression/literal/array.rs +++ b/src/modules/expression/literal/array.rs @@ -21,7 +21,7 @@ impl SyntaxModule for Array { fn new() -> Self { Array { exprs: vec![], - kind: Type::Null + kind: Type::Generic } } @@ -52,21 +52,6 @@ impl SyntaxModule for Array { // Parse array value let mut value = Expr::new(); syntax(meta, &mut value)?; - // Handle nested arrays - if value.kind.is_array() { - let pos = value.get_position(meta); - return error_pos!(meta, pos, "Arrays cannot be nested due to the Bash limitations") - } - match self.kind { - Type::Null => self.kind = Type::Array(Box::new(value.get_type())), - Type::Array(ref mut kind) => { - if value.get_type() != **kind { - let pos = value.get_position(meta); - return error_pos!(meta, pos, format!("Expected array value of type '{kind}'")) - } - }, - _ => () - } let tok = meta.get_current_token(); if token(meta, "]").is_ok() { self.exprs.push(value); @@ -84,6 +69,57 @@ impl SyntaxModule for Array { } } +impl TypeCheckModule for Array { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + // First type-check all the expressions + for expr in &mut self.exprs { + expr.typecheck(meta)?; + // Handle nested arrays + if expr.get_type().is_array() { + let pos = expr.get_position(meta); + return error_pos!(meta, pos, "Arrays cannot be nested due to the Bash limitations") + } + } + + // Then determine the array type + if self.exprs.is_empty() { + // Empty array keeps its existing type (from explicit type annotation or default) + return Ok(()); + } + + match self.kind { + Type::Generic => { + // Infer type from first element + self.kind = Type::Array(Box::new(self.exprs[0].get_type())); + }, + Type::Array(ref expected_type) => { + // Type already specified, validate all elements match + for expr in &self.exprs { + let expr_type = expr.get_type(); + if expr_type != **expected_type { + let pos = expr.get_position(meta); + return error_pos!(meta, pos, format!("Expected array value of type '{expected_type}'")) + } + } + }, + _ => unimplemented!("Unexpected array type state {0}.", self.kind) + } + + // Validate all elements have the same type + if let Type::Array(ref element_type) = self.kind { + for expr in &self.exprs[1..] { + let expr_type = expr.get_type(); + if expr_type != **element_type { + let pos = expr.get_position(meta); + return error_pos!(meta, pos, format!("Array elements must have the same type. Expected '{}', found '{}'", element_type, expr_type)); + } + } + } + + Ok(()) + } +} + impl TranslateModule for Array { fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind { let id = meta.gen_value_id(); diff --git a/src/modules/expression/literal/bool.rs b/src/modules/expression/literal/bool.rs index 5a77b1bb6..0652f3cbd 100644 --- a/src/modules/expression/literal/bool.rs +++ b/src/modules/expression/literal/bool.rs @@ -30,6 +30,12 @@ impl SyntaxModule for Bool { } } +impl TypeCheckModule for Bool { + fn typecheck(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult { + Ok(()) + } +} + impl TranslateModule for Bool { fn translate(&self, _meta: &mut TranslateMetadata) -> FragmentKind { raw_fragment!("{}", if self.value { 1 } else { 0 }) diff --git a/src/modules/expression/literal/integer.rs b/src/modules/expression/literal/integer.rs index d4d0d7351..315f42867 100644 --- a/src/modules/expression/literal/integer.rs +++ b/src/modules/expression/literal/integer.rs @@ -34,6 +34,12 @@ impl SyntaxModule for Integer { } } +impl TypeCheckModule for Integer { + fn typecheck(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult { + Ok(()) + } +} + impl TranslateModule for Integer { fn translate(&self, _meta: &mut TranslateMetadata) -> FragmentKind { RawFragment::from(self.value.to_string()).to_frag() diff --git a/src/modules/expression/literal/null.rs b/src/modules/expression/literal/null.rs index f8c7c98f8..af5da2e4e 100644 --- a/src/modules/expression/literal/null.rs +++ b/src/modules/expression/literal/null.rs @@ -26,6 +26,12 @@ impl SyntaxModule for Null { } } +impl TypeCheckModule for Null { + fn typecheck(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult { + Ok(()) + } +} + impl TranslateModule for Null { fn translate(&self, _meta: &mut TranslateMetadata) -> FragmentKind { fragments!("''") diff --git a/src/modules/expression/literal/number.rs b/src/modules/expression/literal/number.rs index 0e565c375..e2d24379d 100644 --- a/src/modules/expression/literal/number.rs +++ b/src/modules/expression/literal/number.rs @@ -38,6 +38,12 @@ impl SyntaxModule for Number { } } +impl TypeCheckModule for Number { + fn typecheck(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult { + Ok(()) + } +} + impl TranslateModule for Number { fn translate(&self, _meta: &mut TranslateMetadata) -> FragmentKind { RawFragment::from(self.value.to_string()).to_frag() diff --git a/src/modules/expression/literal/status.rs b/src/modules/expression/literal/status.rs index b214fe0d6..bcc19274d 100644 --- a/src/modules/expression/literal/status.rs +++ b/src/modules/expression/literal/status.rs @@ -28,6 +28,12 @@ impl SyntaxModule for Status { } } +impl TypeCheckModule for Status { + fn typecheck(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult { + Ok(()) + } +} + impl TranslateModule for Status { fn translate(&self, _meta: &mut TranslateMetadata) -> FragmentKind { VarExprFragment::new("__status", Type::Int).to_frag() diff --git a/src/modules/expression/literal/text.rs b/src/modules/expression/literal/text.rs index df289efed..3540a2ad0 100644 --- a/src/modules/expression/literal/text.rs +++ b/src/modules/expression/literal/text.rs @@ -34,6 +34,16 @@ impl SyntaxModule for Text { } } +impl TypeCheckModule for Text { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + // Type check all interpolated expressions + for expr in &mut self.interps { + expr.typecheck(meta)?; + } + Ok(()) + } +} + impl TranslateModule for Text { fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind { // Translate all interpolations diff --git a/src/modules/expression/macros.rs b/src/modules/expression/macros.rs index e174c0ff5..58b5be367 100644 --- a/src/modules/expression/macros.rs +++ b/src/modules/expression/macros.rs @@ -1,5 +1,5 @@ #[macro_export] -macro_rules! parse_expr_group { +macro_rules! parse_expression_group { // Group type that handles Binary Operators (@internal ({$cur:ident, $prev:ident}, $meta:expr, BinOp => [$($cur_modules:ident),+])) => {{ let start_index = $meta.get_index(); @@ -131,7 +131,7 @@ macro_rules! parse_expr_group { } #[macro_export] -macro_rules! parse_expr { +macro_rules! parse_expression { // Base Case: Current and previous precedence groups remaining (@internal ( $cur_name:ident @ $cur_type:ident => [$($cur_modules:ident),*], @@ -142,14 +142,14 @@ macro_rules! parse_expr { } fn $next_name(meta: &mut ParserMetadata) -> Result { - parse_expr_group!(@internal ( + parse_expression_group!(@internal ( {$next_name, _terminal}, meta, $next_type => [$($next_modules),*] )) } fn $cur_name(meta: &mut ParserMetadata) -> Result { - parse_expr_group!(@internal ( + parse_expression_group!(@internal ( {$cur_name, $next_name}, meta, $cur_type => [$($cur_modules),*] )) @@ -162,13 +162,13 @@ macro_rules! parse_expr { $next_name:ident @ $next_type:ident => [$($next_modules:ident),*], $($rest_name:ident @ $rest_type:ident => [$($rest_modules:ident),*]),+ )) => { - parse_expr!(@internal ( + parse_expression!(@internal ( $next_name @ $next_type => [$($next_modules),*], $($rest_name @ $rest_type => [$($rest_modules),*]),*) ); fn $cur_name (meta: &mut ParserMetadata) -> Result { - parse_expr_group!(@internal ( + parse_expression_group!(@internal ( {$cur_name, $next_name}, meta, $cur_type => [$($cur_modules),*] )) @@ -176,7 +176,7 @@ macro_rules! parse_expr { }; // Public interface: - // parse_expr!(meta, [ + // parse_expression!(meta, [ // name @ TernOp => [Ternary], // name @ BinOp => [Add, Sub], // name @ BinOp => [Mul, Div], @@ -188,7 +188,7 @@ macro_rules! parse_expr { $name:ident @ $type:ident => [$($modules:ident),*], $($rest_name:ident @ $rest_type:ident => [$($rest_modules:ident),*]),+ ]) => {{ - parse_expr!(@internal ( + parse_expression!(@internal ( $name @ $type => [$($modules),*], $($rest_name @ $rest_type => [$($rest_modules),*]),* )); @@ -197,7 +197,7 @@ macro_rules! parse_expr { }}; // Edge case: Single group provided - // parse_expr!(meta, [ + // parse_expression!(meta, [ // name @ Literal => [Num, Text], // ]); ($meta:expr, [ @@ -208,7 +208,7 @@ macro_rules! parse_expr { } fn $name(meta: &mut ParserMetadata) -> Result { - parse_expr_group!(@internal ( + parse_expression_group!(@internal ( {$name, _terminal}, meta, $type => [$($modules),*] )) @@ -218,6 +218,20 @@ macro_rules! parse_expr { }}; } +#[macro_export] +macro_rules! typecheck_expression { + ($self:ident, $meta:expr, $expr_type:expr, [$($expression:ident),*]) => { + match $expr_type { + $( + ExprType::$expression(expr) => { + expr.typecheck($meta)?; + $self.kind = expr.get_type(); + }, + )* + } + }; +} + #[macro_export] macro_rules! translate_expression { ($meta:expr, $value:expr, [$($item:ident),*]) => { diff --git a/src/modules/expression/parentheses.rs b/src/modules/expression/parentheses.rs index 254f30583..92eb0d9e3 100644 --- a/src/modules/expression/parentheses.rs +++ b/src/modules/expression/parentheses.rs @@ -1,5 +1,9 @@ use heraclitus_compiler::prelude::*; -use crate::{docs::module::DocumentationModule, modules::{prelude::FragmentKind, types::{Type, Typed}}, utils::metadata::ParserMetadata}; +use crate::docs::module::DocumentationModule; +use crate::modules::prelude::FragmentKind; +use crate::modules::types::{Type, Typed}; +use crate::modules::typecheck::TypeCheckModule; +use crate::utils::metadata::ParserMetadata; use crate::translate::module::TranslateModule; use super::expr::Expr; @@ -28,12 +32,19 @@ impl SyntaxModule for Parentheses { fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { token(meta, "(")?; syntax(meta, &mut *self.value)?; - self.kind = self.value.get_type(); token(meta, ")")?; Ok(()) } } +impl TypeCheckModule for Parentheses { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.value.typecheck(meta)?; + self.kind = self.value.get_type(); + Ok(()) + } +} + impl TranslateModule for Parentheses { fn translate(&self, meta: &mut crate::utils::TranslateMetadata) -> FragmentKind { self.value.translate(meta) diff --git a/src/modules/expression/ternop/mod.rs b/src/modules/expression/ternop/mod.rs index 7902d30a5..f96575e84 100644 --- a/src/modules/expression/ternop/mod.rs +++ b/src/modules/expression/ternop/mod.rs @@ -1,10 +1,11 @@ use heraclitus_compiler::prelude::*; use crate::utils::ParserMetadata; use super::expr::Expr; +use crate::modules::typecheck::TypeCheckModule; pub mod ternary; -pub trait TernOp: SyntaxModule { +pub trait TernOp: SyntaxModule + TypeCheckModule { fn set_left(&mut self, left: Expr); fn set_middle(&mut self, middle: Expr); fn set_right(&mut self, right: Expr); diff --git a/src/modules/expression/ternop/ternary.rs b/src/modules/expression/ternop/ternary.rs index ecdd43065..ce2fe4366 100644 --- a/src/modules/expression/ternop/ternary.rs +++ b/src/modules/expression/ternop/ternary.rs @@ -55,12 +55,22 @@ impl SyntaxModule for Ternary { } } - fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + fn parse(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult { + Ok(()) + } +} + +impl TypeCheckModule for Ternary { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.cond.typecheck(meta)?; if self.cond.get_type() != Type::Bool { let msg = self.cond.get_error_message(meta) .message("Expected expression that evaluates to 'Bool' in ternary condition"); return Err(Failure::Loud(msg)); } + + self.true_expr.typecheck(meta)?; + self.false_expr.typecheck(meta)?; if self.true_expr.get_type() != self.false_expr.get_type() { let pos = get_binop_position_info(meta, &self.true_expr, &self.false_expr); let msg = Message::new_err_at_position(meta, pos) diff --git a/src/modules/expression/typeop/cast.rs b/src/modules/expression/typeop/cast.rs index 9dc007f15..3ba1fda18 100644 --- a/src/modules/expression/typeop/cast.rs +++ b/src/modules/expression/typeop/cast.rs @@ -43,7 +43,15 @@ impl SyntaxModule for Cast { } } - fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + fn parse(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult { + Ok(()) + } +} + +impl TypeCheckModule for Cast { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.expr.typecheck(meta)?; + let begin = meta.get_token_at(self.expr.pos.0); let end = meta.get_current_token(); let pos = PositionInfo::from_between_tokens(meta, begin, end); diff --git a/src/modules/expression/typeop/is.rs b/src/modules/expression/typeop/is.rs index 287c4c17b..496bc2079 100644 --- a/src/modules/expression/typeop/is.rs +++ b/src/modules/expression/typeop/is.rs @@ -48,6 +48,12 @@ impl SyntaxModule for Is { } } +impl TypeCheckModule for Is { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.expr.typecheck(meta) + } +} + impl TranslateModule for Is { fn translate(&self, _meta: &mut TranslateMetadata) -> FragmentKind { if self.expr.get_type() == self.kind { diff --git a/src/modules/expression/typeop/mod.rs b/src/modules/expression/typeop/mod.rs index 2888c0d58..3911b4876 100644 --- a/src/modules/expression/typeop/mod.rs +++ b/src/modules/expression/typeop/mod.rs @@ -1,12 +1,13 @@ use heraclitus_compiler::prelude::*; use crate::{modules::types::Type, utils::ParserMetadata}; use super::expr::Expr; +use crate::modules::typecheck::TypeCheckModule; pub mod is; pub mod cast; -pub trait TypeOp: SyntaxModule { +pub trait TypeOp: SyntaxModule + TypeCheckModule { fn set_left(&mut self, left: Expr); fn set_right(&mut self, right: Type); fn parse_operator(&mut self, meta: &mut ParserMetadata) -> SyntaxResult; diff --git a/src/modules/expression/unop/mod.rs b/src/modules/expression/unop/mod.rs index e157ebdf1..01a65042a 100644 --- a/src/modules/expression/unop/mod.rs +++ b/src/modules/expression/unop/mod.rs @@ -1,11 +1,12 @@ use heraclitus_compiler::prelude::*; use crate::{modules::types::{Type, Typed}, utils::{pluralize, ParserMetadata}}; use super::expr::Expr; +use crate::modules::typecheck::TypeCheckModule; pub mod not; pub mod neg; -pub trait UnOp: SyntaxModule { +pub trait UnOp: SyntaxModule + TypeCheckModule { fn set_expr(&mut self, expr: Expr); fn parse_operator(&mut self, meta: &mut ParserMetadata) -> SyntaxResult; diff --git a/src/modules/expression/unop/neg.rs b/src/modules/expression/unop/neg.rs index 19077c136..b23d020d8 100644 --- a/src/modules/expression/unop/neg.rs +++ b/src/modules/expression/unop/neg.rs @@ -3,6 +3,7 @@ use crate::modules::expression::expr::Expr; use crate::modules::expression::unop::UnOp; use crate::modules::prelude::{ArithmeticFragment, FragmentKind, FragmentRenderable, RawFragment}; use crate::modules::types::{Type, Typed}; +use crate::modules::typecheck::TypeCheckModule; use crate::translate::compute::{translate_float_computation, ArithOp}; use crate::translate::module::TranslateModule; use crate::utils::metadata::ParserMetadata; @@ -47,7 +48,14 @@ impl SyntaxModule for Neg { } } - fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + fn parse(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult { + Ok(()) + } +} + +impl TypeCheckModule for Neg { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.expr.typecheck(meta)?; Self::typecheck_allowed_types(meta, "arithmetic negation", &self.expr, &[Type::Num, Type::Int])?; Ok(()) } diff --git a/src/modules/expression/unop/not.rs b/src/modules/expression/unop/not.rs index 70f9d30bf..3d62fffe4 100644 --- a/src/modules/expression/unop/not.rs +++ b/src/modules/expression/unop/not.rs @@ -37,7 +37,14 @@ impl SyntaxModule for Not { } } - fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + fn parse(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult { + Ok(()) + } +} + +impl TypeCheckModule for Not { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.expr.typecheck(meta)?; Self::typecheck_allowed_types(meta, "logical negation", &self.expr, &[Type::Bool])?; Ok(()) } diff --git a/src/modules/function/declaration.rs b/src/modules/function/declaration.rs index 4e780fa0f..ac84d471b 100644 --- a/src/modules/function/declaration.rs +++ b/src/modules/function/declaration.rs @@ -12,6 +12,7 @@ use crate::modules::statement::comment_doc::CommentDoc; use crate::modules::expression::expr::Expr; use crate::modules::types::{Type, Typed}; use crate::modules::variable::variable_name_extensions; +use crate::modules::typecheck::TypeCheckModule; use crate::utils::cc_flags::get_ccflag_by_name; use crate::utils::context::Context; use crate::utils::function_cache::FunctionInstance; @@ -20,27 +21,42 @@ use crate::modules::types::parse_type; use crate::utils::function_metadata::FunctionMetadata; use super::declaration_utils::*; +#[derive(Debug, Clone)] +pub struct FunctionDeclarationArgument { + pub name: String, + pub kind: Type, + pub optional: Option, + pub is_ref: bool, + pub tok: Option, +} + #[derive(Debug, Clone)] pub struct FunctionDeclaration { pub name: String, - pub arg_refs: Vec, - pub arg_names: Vec, - pub arg_types: Vec, - pub arg_optionals: Vec, + pub args: Vec, pub returns: Type, pub id: usize, pub is_public: bool, pub comment: Option, /// Function signature prepared for docs generation - pub doc_signature: Option + pub doc_signature: Option, + /// Function body context for typecheck phase + pub function_body: Option>, + /// Whether function is failable + pub is_failable: bool, + /// Whether function was declared as failable + pub declared_failable: bool, + /// Token for function name (for error positioning) + pub name_token: Option } impl FunctionDeclaration { - fn set_args_as_variables(&self, _meta: &mut TranslateMetadata, function: &FunctionInstance, arg_refs: &[bool]) -> Option { - if !self.arg_names.is_empty() { + fn set_args_as_variables(&self, _meta: &mut TranslateMetadata, function: &FunctionInstance) -> Option { + if !self.args.is_empty() { let mut result = vec![]; - for (index, (name, kind, is_ref)) in izip!(self.arg_names.clone(), &function.args, arg_refs).enumerate() { - match (is_ref, kind) { + for (index, (arg, kind)) in izip!(self.args.iter(), &function.args).enumerate() { + let name = &arg.name; + match (arg.is_ref, kind) { (false, Type::Array(_)) => result.push(raw_fragment!("local {name}=(\"${{!{}}}\")", index + 1)), _ => result.push(raw_fragment!("local {name}=${}", index + 1)), } @@ -97,15 +113,16 @@ impl SyntaxModule for FunctionDeclaration { fn new() -> Self { FunctionDeclaration { name: String::new(), - arg_names: vec![], - arg_types: vec![], - arg_refs: vec![], - arg_optionals: vec![], + args: vec![], returns: Type::Generic, id: 0, is_public: false, comment: None, doc_signature: None, + function_body: None, + is_failable: false, + declared_failable: false, + name_token: None, } } @@ -134,21 +151,15 @@ impl SyntaxModule for FunctionDeclaration { } return Err(err) } - // Check if we are in the global scope - if !meta.is_global_scope() { - return error!(meta, tok, "Functions can only be declared in the global scope") - } // Get the function name - let tok = meta.get_current_token(); + self.name_token = meta.get_current_token(); self.name = variable(meta, variable_name_extensions())?; - handle_existing_function(meta, tok.clone())?; let mut optional = false; context!({ // Set the compiler flags meta.with_context_fn(Context::set_cc_flags, flags, |meta| { // Get the arguments token(meta, "(")?; - let mut seen_argument_names = HashSet::new(); loop { if token(meta, ")").is_ok() { break @@ -157,59 +168,45 @@ impl SyntaxModule for FunctionDeclaration { let name_token = meta.get_current_token(); let name = variable(meta, variable_name_extensions())?; - // Check for duplicate argument name - if !seen_argument_names.insert(name.clone()) { - return error!(meta, name_token, format!("Argument '{name}' is already defined")); - } - // Optionally parse the argument type - let mut arg_type = Type::Generic; - match token(meta, ":") { - Ok(_) => { - self.arg_refs.push(is_ref); - self.arg_names.push(name.clone()); - arg_type = parse_type(meta)?; - self.arg_types.push(arg_type.clone()); - }, - Err(_) => { - self.arg_refs.push(is_ref); - self.arg_names.push(name.clone()); - self.arg_types.push(Type::Generic); - } - } - match token(meta, "=") { + let arg_type = match token(meta, ":") { + Ok(_) => parse_type(meta)?, + Err(_) => Type::Generic + }; + + // Optionally parse default value + let optional_expr = match token(meta, "=") { Ok(_) => { - if is_ref { - return error!(meta, name_token, "A ref cannot be optional"); - } optional = true; let mut expr = Expr::new(); syntax(meta, &mut expr)?; - if !expr.get_type().is_allowed_in(&arg_type) { - return error!(meta, name_token, "Optional argument does not match annotated type"); - } - self.arg_optionals.push(expr); + Some(expr) }, - Err(_) => { - if optional { - return error!(meta, name_token, "All arguments following an optional argument must also be optional"); - } - }, - } + Err(_) => None, + }; + + self.args.push(FunctionDeclarationArgument { + name, + kind: arg_type, + optional: optional_expr, + is_ref, + tok: name_token, + }); match token(meta, ")") { Ok(_) => break, Err(_) => token(meta, ",")? }; } let mut returns_tok = None; - let mut declared_failable = false; + let mut question_tok = None; // Optionally parse the return type match token(meta, ":") { Ok(_) => { returns_tok = meta.get_current_token(); self.returns = parse_type(meta)?; + question_tok = meta.get_current_token(); if token(meta, "?").is_ok() { - declared_failable = true; + self.declared_failable = true; } }, Err(_) => self.returns = Type::Generic @@ -217,33 +214,23 @@ impl SyntaxModule for FunctionDeclaration { // Parse the body token(meta, "{")?; let (index_begin, index_end, is_failable) = skip_function_body(meta); + self.is_failable = is_failable; if self.returns == Type::Generic { - declared_failable = is_failable; + self.declared_failable = is_failable; } - if is_failable && !declared_failable { + + // Validate failable function declarations + if is_failable && !self.declared_failable { return error!(meta, returns_tok, "Failable functions must have a '?' after the type name"); } - if !is_failable && declared_failable { - return error!(meta, returns_tok, "Infallible functions must not have a '?' after the type name"); + if !is_failable && self.declared_failable { + return error!(meta, question_tok.or(returns_tok), "Infallible functions must not have a '?' after the type name"); } - // Create a new context with the function body - let expr = meta.context.expr[index_begin..index_end].to_vec(); - let ctx = meta.context.clone().function_invocation(expr); + // Store function body for typecheck phase + self.function_body = Some(meta.context.expr[index_begin..index_end].to_vec()); token(meta, "}")?; self.doc_signature = Some(self.render_function_signature(meta, doc_index)?); - // Add the function to the memory - self.id = handle_add_function(meta, tok.clone(), FunctionInterface { - id: None, - name: self.name.clone(), - arg_names: self.arg_names.clone(), - arg_types: self.arg_types.clone(), - arg_refs: self.arg_refs.clone(), - returns: self.returns.clone(), - arg_optionals: self.arg_optionals.clone(), - is_public: self.is_public, - is_failable - }, ctx)?; Ok(()) })?; Ok(()) @@ -253,6 +240,65 @@ impl SyntaxModule for FunctionDeclaration { } } +impl TypeCheckModule for FunctionDeclaration { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + // Check if we are in the global scope + if !meta.is_global_scope() { + return error!(meta, self.name_token.clone(), "Functions can only be declared in the global scope") + } + + // Check if function already exists + handle_existing_function(meta, self.name_token.clone())?; + + // Check for duplicate argument names + let mut seen_argument_names = HashSet::new(); + for arg in &self.args { + if !seen_argument_names.insert(arg.name.clone()) { + return error!(meta, arg.tok.clone(), format!("Argument '{}' is already defined", arg.name)); + } + } + + // Validate optional arguments + // Typecheck and validate optional arguments + let mut optional_started = false; + for arg in &mut self.args { + if let Some(ref mut expr) = arg.optional { + // Check if ref arguments are optional + if arg.is_ref { + return error!(meta, arg.tok.clone(), "A ref cannot be optional"); + } + + // Typecheck the optional argument expression first + expr.typecheck(meta)?; + + // Validate optional argument type + if !expr.get_type().is_allowed_in(&arg.kind) { + return error!(meta, arg.tok.clone(), "Optional argument does not match annotated type"); + } + + optional_started = true; + } else if optional_started { + return error!(meta, arg.tok.clone(), "All arguments following an optional argument must also be optional"); + } + } + + // Create function context and add to memory + let expr = self.function_body.clone().unwrap_or_default(); + let ctx = meta.context.clone().function_invocation(expr); + + self.id = handle_add_function(meta, self.name_token.clone(), FunctionInterface { + id: None, + name: self.name.clone(), + args: self.args.clone(), + returns: self.returns.clone(), + is_public: self.is_public, + is_failable: self.is_failable + }, ctx)?; + + Ok(()) + } +} + impl TranslateModule for FunctionDeclaration { fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind { let mut result = vec![]; @@ -266,7 +312,7 @@ impl TranslateModule for FunctionDeclaration { // Parse the function body let name = raw_fragment!("{}{}__{}_v{}", prefix, self.name, self.id, index); result.push(fragments!(name, "() {")); - if let Some(args) = self.set_args_as_variables(meta, function, &self.arg_refs) { + if let Some(args) = self.set_args_as_variables(meta, function) { result.push(args); } result.push(function.block.translate(meta)); diff --git a/src/modules/function/declaration_utils.rs b/src/modules/function/declaration_utils.rs index c7ca3f4aa..9472fa42d 100644 --- a/src/modules/function/declaration_utils.rs +++ b/src/modules/function/declaration_utils.rs @@ -76,8 +76,8 @@ pub fn handle_existing_function(meta: &mut ParserMetadata, tok: Option) - pub fn handle_add_function(meta: &mut ParserMetadata, tok: Option, fun: FunctionInterface, ctx: Context) -> Result { let name = fun.name.clone(); handle_identifier_name(meta, &name, tok.clone())?; - let any_generic = fun.arg_types.iter().any(|kind| kind == &Type::Generic); - let any_typed = fun.arg_types.iter().any(|kind| kind != &Type::Generic); + let any_generic = fun.args.iter().any(|arg| arg.kind == Type::Generic); + let any_typed = fun.args.iter().any(|arg| arg.kind != Type::Generic); // Either all arguments are generic or typed if any_typed && any_generic { return error!(meta, tok => { diff --git a/src/modules/function/fail.rs b/src/modules/function/fail.rs index b77f81b0c..73faa0641 100644 --- a/src/modules/function/fail.rs +++ b/src/modules/function/fail.rs @@ -53,18 +53,8 @@ impl SyntaxModule for Fail { self.code = value; }, Err(_) => { - match syntax(meta, &mut self.expr) { - Ok(_) => { - if self.expr.get_type() != Type::Num { - return error!(meta, tok => { - message: "Invalid exit code", - comment: "Fail status must be a non-zero integer" - }); - } - }, - Err(_) => { - self.code = "1".to_string(); - } + if syntax(meta, &mut self.expr).is_err() { + self.code = "1".to_string(); } } } @@ -72,6 +62,20 @@ impl SyntaxModule for Fail { } } +impl TypeCheckModule for Fail { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + // Only check if we have an expression (not a code value) + if self.code.is_empty() && self.expr.get_type() != Type::Num { + let tok = meta.get_current_token(); + return error!(meta, tok => { + message: "Invalid exit code", + comment: "Fail status must be a non-zero integer" + }); + } + Ok(()) + } +} + impl TranslateModule for Fail { fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind { let translate = if self.code.is_empty() { diff --git a/src/modules/function/invocation.rs b/src/modules/function/invocation.rs index d80d9b785..b03bd1dd8 100644 --- a/src/modules/function/invocation.rs +++ b/src/modules/function/invocation.rs @@ -1,14 +1,11 @@ use std::mem::swap; use heraclitus_compiler::prelude::*; -use crate::modules::command::cmd::Command; use crate::{fragments, raw_fragment}; use crate::modules::prelude::*; use itertools::izip; use crate::modules::command::modifier::CommandModifier; -use crate::modules::condition::failed::Failed; -use crate::modules::condition::succeeded::Succeeded; -use crate::modules::condition::then::Then; +use crate::modules::condition::failure_handler::FailureHandler; use crate::modules::types::{Type, Typed}; use crate::modules::variable::variable_name_extensions; use crate::modules::expression::expr::{Expr, ExprType}; @@ -17,6 +14,7 @@ use super::invocation_utils::*; #[derive(Debug, Clone)] pub struct FunctionInvocation { name: String, + name_tok: Option, args: Vec, refs: Vec, kind: Type, @@ -24,9 +22,7 @@ pub struct FunctionInvocation { id: usize, line: usize, col: usize, - failed: Failed, - succeeded: Succeeded, - then: Then, + failure_handler: FailureHandler, modifier: CommandModifier, is_failable: bool } @@ -50,6 +46,7 @@ impl SyntaxModule for FunctionInvocation { fn new() -> Self { FunctionInvocation { name: String::new(), + name_tok: None, args: vec![], refs: vec![], kind: Type::Null, @@ -57,10 +54,8 @@ impl SyntaxModule for FunctionInvocation { id: 0, line: 0, col: 0, - failed: Failed::new(), - succeeded: Succeeded::new(), - then: Then::new(), - modifier: CommandModifier::new().parse_expr(), + failure_handler: FailureHandler::new(), + modifier: CommandModifier::new_expr(), is_failable: false } } @@ -68,16 +63,17 @@ impl SyntaxModule for FunctionInvocation { fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { syntax(meta, &mut self.modifier)?; self.modifier.use_modifiers(meta, |_this, meta| { - // Get the function name + // Get the function name and store token for error reporting let tok = meta.get_current_token(); if let Some(ref tok) = tok { (self.line, self.col) = tok.pos; } self.name = variable(meta, variable_name_extensions())?; - self.failed.set_function_name(self.name.clone()); - // Get the arguments + self.name_tok = tok.clone(); + self.failure_handler.set_function_name(self.name.clone()); + + // Parse arguments syntax token(meta, "(")?; - self.id = handle_function_reference(meta, tok.clone(), &self.name)?; loop { if token(meta, ")").is_ok() { break @@ -90,74 +86,73 @@ impl SyntaxModule for FunctionInvocation { Err(_) => token(meta, ",")?, }; } - let function_unit = meta.get_fun_declaration(&self.name).unwrap().clone(); - let expected_arg_count = function_unit.arg_refs.len(); - let actual_arg_count = self.args.len(); - let optional_count = function_unit.arg_optionals.len(); - - // Case when function call is missing arguments - if actual_arg_count < expected_arg_count { - // Check if we can compensate with optional arguments stored in fun_unit - if actual_arg_count >= expected_arg_count - optional_count { - let missing = expected_arg_count - actual_arg_count; - let provided_optional = optional_count - missing; - for exp in function_unit.arg_optionals.iter().skip(provided_optional){ - self.args.push(exp.clone()); - } - } - } - let types = self.args.iter().map(Expr::get_type).collect::>(); - let var_refs = self.args.iter().map(is_ref).collect::>(); - self.refs.clone_from(&function_unit.arg_refs); - (self.kind, self.variant_id) = handle_function_parameters(meta, self.id, function_unit.clone(), &types, &var_refs, tok.clone())?; - - // Set position for failed and succeeded handlers - let position = PositionInfo::from_between_tokens(meta, tok.clone(), meta.get_current_token()); - self.failed.set_position(position.clone()); - - self.is_failable = function_unit.is_failable; - if self.is_failable { - match syntax(meta, &mut self.then) { - Ok(_) => return Command::handle_multiple_failure_handlers(meta, "then"), - err @ Err(Failure::Loud(_)) => return err, - _ => {} - } + // Store position for later error reporting + self.failure_handler.set_position(PositionInfo::from_between_tokens(meta, tok.clone(), meta.get_current_token())); - match syntax(meta, &mut self.succeeded) { - Ok(_) => return Command::handle_multiple_failure_handlers(meta, "succeeded"), - err @ Err(Failure::Loud(_)) => return err, - _ => {} - } + // Try to parse the failed block if present (optional in parse phase) + syntax(meta, &mut self.failure_handler).ok(); - return match syntax(meta, &mut self.failed) { - Ok(_) => Command::handle_multiple_failure_handlers(meta, "failed"), - Err(Failure::Quiet(_)) => error!(meta, tok => { - message: "This function can fail. Please handle the failure or success", - comment: "You can use '?' to propagate failure, 'failed' block to handle failure, 'succeeded' block to handle success, or 'then' block to handle both" - }), - Err(err) => Err(err) - } - } else { - let tok = meta.get_current_token(); - let index = meta.get_index(); - if let Ok(symbol) = token_by(meta, |word| ["?", "failed", "succeeded", "then"].contains(&word.as_str())) { - // This could be a ternary operator - if symbol == "then" && token(meta, "(").is_err() { - meta.set_index(index); - } else { - return error!(meta, tok => { - message: "This function cannot fail", - comment: format!("You can remove the '{symbol}' in the end") - }); - } - } - } Ok(()) }) } } +impl TypeCheckModule for FunctionInvocation { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + // Type-check all arguments first + for arg in &mut self.args { + arg.typecheck(meta)?; + } + + // Look up the function declaration (this requires typecheck phase context) + self.id = handle_function_reference(meta, self.name_tok.clone(), &self.name)?; + + let function_unit = meta.get_fun_declaration(&self.name).unwrap().clone(); + let expected_arg_count = function_unit.args.len(); + let actual_arg_count = self.args.len(); + let optional_count = function_unit.args.iter().filter(|arg| arg.optional.is_some()).count(); + + // Handle missing arguments by filling with optional defaults + if actual_arg_count < expected_arg_count { + // Check if we can compensate with optional arguments stored in fun_unit + if actual_arg_count >= expected_arg_count - optional_count { + let missing = expected_arg_count - actual_arg_count; + let provided_optional = optional_count - missing; + let optionals: Vec<_> = function_unit.args.iter().filter_map(|arg| arg.optional.as_ref()).collect(); + for exp in optionals.iter().skip(provided_optional){ + self.args.push((*exp).clone()); + } + } + } + + // Validate arguments and get function variant + let types = self.args.iter().map(Expr::get_type).collect::>(); + let var_refs = self.args.iter().map(is_ref).collect::>(); + self.refs = function_unit.args.iter().map(|arg| arg.is_ref).collect(); + (self.kind, self.variant_id) = handle_function_parameters(meta, self.id, function_unit.clone(), &types, &var_refs, self.name_tok.clone())?; + + // Handle failable function logic + self.is_failable = function_unit.is_failable; + if self.is_failable { + if !self.failure_handler.is_parsed { + return error!(meta, self.name_tok.clone() => { + message: format!("Function '{}' can potentially fail but is left unhandled.", self.name), + comment: "You can use '?' to propagate failure, 'failed' block to handle failure, 'succeeded' block to handle success, or 'exited' block to handle both" + }); + } + self.failure_handler.typecheck(meta)?; + } else if self.failure_handler.is_parsed { + let message = Message::new_warn_at_token(meta, self.name_tok.clone()) + .message("This function cannot fail") + .comment("You can remove the failure handler block or '?' at the end"); + meta.add_message(message); + } + + Ok(()) + } +} + impl TranslateModule for FunctionInvocation { fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind { // Get the variable prefix based on function name casing @@ -176,18 +171,9 @@ impl TranslateModule for FunctionInvocation { let args = ListFragment::new(args).with_spaces().to_frag(); meta.stmt_queue.push_back(fragments!(name, " ", args, silent)); swap(&mut is_silent, &mut meta.silenced); - if self.is_failable { - // Choose between failed, succeeded, or then handler - if self.then.is_parsed { - let then = self.then.translate(meta); - meta.stmt_queue.push_back(then); - } else if self.failed.is_parsed { - let failed = self.failed.translate(meta); - meta.stmt_queue.push_back(failed); - } else if self.succeeded.is_parsed { - let succeeded = self.succeeded.translate(meta); - meta.stmt_queue.push_back(succeeded); - } + if self.is_failable && self.failure_handler.is_parsed { + let handler = self.failure_handler.translate(meta); + meta.stmt_queue.push_back(handler); } if self.kind != Type::Null { // Get the variable prefix for return values diff --git a/src/modules/function/invocation_utils.rs b/src/modules/function/invocation_utils.rs index efc162e8f..61bd8c02f 100644 --- a/src/modules/function/invocation_utils.rs +++ b/src/modules/function/invocation_utils.rs @@ -3,6 +3,7 @@ use heraclitus_compiler::prelude::*; use similar_string::find_best_similarity; use crate::modules::block::Block; use crate::modules::types::Type; +use crate::modules::typecheck::TypeCheckModule; use crate::utils::{pluralize, ParserMetadata}; use crate::utils::context::FunctionDecl; @@ -26,9 +27,9 @@ fn ordinal_number(index: usize) -> String { fn run_function_with_args(meta: &mut ParserMetadata, mut fun: FunctionDecl, args: &[Type], tok: Option) -> Result<(Type, usize), Failure> { // Check if there are the correct amount of arguments - if fun.arg_names.len() != args.len() { - let max_args = fun.arg_names.len(); - let min_args = fun.arg_names.len() - fun.arg_optionals.len(); + if fun.args.len() != args.len() { + let max_args = fun.args.len(); + let min_args = fun.args.len() - fun.args.iter().filter(|arg| arg.optional.is_some()).count(); let opt_argument = if max_args > min_args {&format!(" ({max_args} optional)")} else {""}; // Determine the correct grammar let txt_arguments = pluralize(min_args, "argument", "arguments"); @@ -38,7 +39,9 @@ fn run_function_with_args(meta: &mut ParserMetadata, mut fun: FunctionDecl, args } // Check if the function argument types match if fun.is_args_typed { - for (index, (arg_name, arg_type, given_type)) in izip!(fun.arg_names.iter(), fun.arg_types.iter(), args.iter()).enumerate() { + for (index, (arg, given_type)) in izip!(fun.args.iter(), args.iter()).enumerate() { + let arg_name = &arg.name; + let arg_type = &arg.kind; if !given_type.is_allowed_in(arg_type) { let fun_name = &fun.name; let ordinal = ordinal_number(index); @@ -51,9 +54,9 @@ fn run_function_with_args(meta: &mut ParserMetadata, mut fun: FunctionDecl, args // Swap the contexts to use the function context meta.with_context_ref(&mut context, |meta| { // Create a sub context for new variables - meta.with_push_scope(|meta| { - for (kind, name, is_ref) in izip!(args, &fun.arg_names, &fun.arg_refs) { - meta.add_param(name, kind.clone(), *is_ref); + meta.with_push_scope(true, |meta| { + for (kind, arg) in izip!(args, &fun.args) { + meta.add_param(&arg.name, kind.clone(), arg.is_ref); } // Set the expected return type if specified if fun.returns != Type::Generic { @@ -61,6 +64,8 @@ fn run_function_with_args(meta: &mut ParserMetadata, mut fun: FunctionDecl, args } // Parse the function body syntax(meta, &mut block)?; + // Typecheck the function body + block.typecheck(meta)?; Ok(()) })?; Ok(()) @@ -70,7 +75,9 @@ fn run_function_with_args(meta: &mut ParserMetadata, mut fun: FunctionDecl, args fun.returns = context.fun_ret_type.clone().unwrap_or(Type::Null); }; // Set the new argument types - fun.arg_types = args.to_vec(); + for (arg, new_type) in fun.args.iter_mut().zip(args.iter()) { + arg.kind = new_type.clone(); + } // Persist the new function instance Ok((fun.returns.clone(), meta.add_fun_instance(fun.into_interface(), block))) } @@ -92,8 +99,10 @@ pub fn handle_function_reference(meta: &ParserMetadata, tok: Option, name pub fn handle_function_parameters(meta: &mut ParserMetadata, id: usize, fun: FunctionDecl, args: &[Type], vars: &[bool], tok: Option) -> Result<(Type, usize), Failure> { // Check if the function arguments that are references are passed as variables and not as values - for (index, (is_ref, arg_name, var)) in izip!(fun.arg_refs.iter(), fun.arg_names.iter(), vars.iter()).enumerate() { - if *is_ref && !var { + for (index, (arg, var)) in izip!(fun.args.iter(), vars.iter()).enumerate() { + let is_ref = arg.is_ref; + let arg_name = &arg.name; + if is_ref && !var { let fun_name = &fun.name; let ordinal = ordinal_number(index); return error!(meta, tok, format!("Cannot pass {ordinal} argument '{arg_name}' as a reference to the function '{fun_name}' because it is not a variable")) diff --git a/src/modules/function/ret.rs b/src/modules/function/ret.rs index 42c18c5a0..bf5afe3b9 100644 --- a/src/modules/function/ret.rs +++ b/src/modules/function/ret.rs @@ -26,20 +26,29 @@ impl SyntaxModule for Return { } fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { - let tok = meta.get_current_token(); token(meta, "return")?; if !meta.context.is_fun_ctx { + let tok = meta.get_current_token(); return error!(meta, tok => { message: "Return statement outside of function", comment: "Return statements can only be used inside of functions" }); } syntax(meta, &mut self.expr)?; + Ok(()) + } +} + +impl TypeCheckModule for Return { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.expr.typecheck(meta)?; + let ret_type = meta.context.fun_ret_type.as_ref(); let expr_type = &self.expr.get_type(); match ret_type { Some(ret_type) => { if !expr_type.is_allowed_in(ret_type) { + let tok = meta.get_current_token(); return error!(meta, tok => { message: "Return type does not match function return type", comment: format!("Given type: {}, expected type: {}", expr_type, ret_type) diff --git a/src/modules/imports/import.rs b/src/modules/imports/import.rs index 84ae05f60..b538d6542 100644 --- a/src/modules/imports/import.rs +++ b/src/modules/imports/import.rs @@ -105,7 +105,8 @@ impl Import { .file_import(&meta.context.trace, position); meta.with_context_ref(&mut context, |meta| { // Parse imported code - syntax(meta, &mut block) + syntax(meta, &mut block)?; + block.typecheck(meta) })?; // Persist compiled file to cache meta.import_cache.add_import_metadata(Some(self.path.value.clone()), block, context.pub_funs.clone()); @@ -136,9 +137,6 @@ impl SyntaxModule for Import { self.is_pub = token(meta, "pub").is_ok(); self.token_import = meta.get_current_token(); token(meta, "import")?; - if !meta.is_global_scope() { - return error!(meta, self.token_import.clone(), "Imports must be in the global scope") - } match token(meta, "*") { Ok(_) => self.is_all = true, Err(_) => { @@ -185,7 +183,15 @@ impl SyntaxModule for Import { token(meta, "from")?; self.token_path = meta.get_current_token(); syntax(meta, &mut self.path)?; - // Import code from file or standard library + Ok(()) + } +} + +impl TypeCheckModule for Import { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + if !meta.is_global_scope() { + return error!(meta, self.token_import.clone(), "Imports must be in the global scope") + } self.add_import(meta, &self.path.value.clone())?; let code = self.resolve_import(meta)?; self.handle_import(meta, code)?; diff --git a/src/modules/loops/break_stmt.rs b/src/modules/loops/break_stmt.rs index d9f2b7de7..f3887277c 100644 --- a/src/modules/loops/break_stmt.rs +++ b/src/modules/loops/break_stmt.rs @@ -6,26 +6,36 @@ use crate::utils::metadata::{ParserMetadata, TranslateMetadata}; use crate::modules::prelude::*; #[derive(Debug, Clone)] -pub struct Break; +pub struct Break { + tok: Option +} impl SyntaxModule for Break { syntax_name!("Break"); fn new() -> Self { - Break + Break { + tok: None + } } fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { - let tok = meta.get_current_token(); + self.tok = meta.get_current_token(); token(meta, "break")?; - // Detect if the break statement is inside a loop - if !meta.context.is_loop_ctx { - return error!(meta, tok, "Break statement can only be used inside a loop") - } Ok(()) } } +impl TypeCheckModule for Break { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + // Detect if the break statement is inside a loop + if !meta.context.is_loop_ctx { + return error!(meta, self.tok.clone(), "Break statement can only be used inside a loop") + } + Ok(()) + } +} + impl TranslateModule for Break { fn translate(&self, _meta: &mut TranslateMetadata) -> FragmentKind { fragments!("break") diff --git a/src/modules/loops/continue_stmt.rs b/src/modules/loops/continue_stmt.rs index 15b852104..3098a1cc7 100644 --- a/src/modules/loops/continue_stmt.rs +++ b/src/modules/loops/continue_stmt.rs @@ -3,21 +3,31 @@ use crate::modules::prelude::*; use heraclitus_compiler::prelude::*; #[derive(Debug, Clone)] -pub struct Continue; +pub struct Continue { + tok: Option, +} impl SyntaxModule for Continue { syntax_name!("Continue"); fn new() -> Self { - Continue + Continue { + tok: None + } } fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { - let tok = meta.get_current_token(); + self.tok = meta.get_current_token(); token(meta, "continue")?; + Ok(()) + } +} + +impl TypeCheckModule for Continue { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { // Detect if the continue statement is inside a loop if !meta.context.is_loop_ctx { - return error!(meta, tok, "Continue statement can only be used inside a loop") + return error!(meta, self.tok.clone(), "Continue statement can only be used inside a loop") } Ok(()) } diff --git a/src/modules/loops/infinite_loop.rs b/src/modules/loops/infinite_loop.rs index 50a2de31f..3d8e0a994 100644 --- a/src/modules/loops/infinite_loop.rs +++ b/src/modules/loops/infinite_loop.rs @@ -20,10 +20,16 @@ impl SyntaxModule for InfiniteLoop { fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { token(meta, "loop")?; + syntax(meta, &mut self.block)?; + Ok(()) + } +} + +impl TypeCheckModule for InfiniteLoop { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { // Save loop context state and set it to true meta.with_context_fn(Context::set_is_loop_ctx, true, |meta| { - syntax(meta, &mut self.block)?; - Ok(()) + self.block.typecheck(meta) })?; Ok(()) } diff --git a/src/modules/loops/iter_loop.rs b/src/modules/loops/iter_loop.rs index f8292c03a..a0e8c1449 100644 --- a/src/modules/loops/iter_loop.rs +++ b/src/modules/loops/iter_loop.rs @@ -46,32 +46,11 @@ impl SyntaxModule for IterLoop { self.iter_name = variable(meta, variable_name_extensions())?; } token(meta, "in")?; - context!({ - // Parse iterable - let tok = meta.get_current_token(); - syntax(meta, &mut self.iter_expr)?; - self.iter_type = match self.iter_expr.get_type() { - Type::Array(kind) => *kind, - _ => return error!(meta, tok, "Expected iterable"), - }; - // Create iterator variable - meta.with_push_scope(|meta| { - self.iter_global_id = meta.add_var(&self.iter_name, self.iter_type.clone(), false); - if let Some(index) = self.iter_index.as_ref() { - self.iter_index_global_id = meta.add_var(index, Type::Int, false); - } - // Save loop context state and set it to true - meta.with_context_fn(Context::set_is_loop_ctx, true, |meta| { - // Parse loop - syntax(meta, &mut self.block)?; - Ok(()) - })?; - Ok(()) - })?; - Ok(()) - }, |pos| { - error_pos!(meta, pos, "Syntax error in loop") - }) + // Parse iterable expression + syntax(meta, &mut self.iter_expr)?; + // Parse loop body + syntax(meta, &mut self.block)?; + Ok(()) } } @@ -112,6 +91,38 @@ impl TranslateModule for IterLoop { } } +impl TypeCheckModule for IterLoop { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.iter_expr.typecheck(meta)?; + + // Determine iterator type after typechecking + self.iter_type = match self.iter_expr.get_type() { + Type::Array(kind) => *kind, + _ => { + let pos = self.iter_expr.get_position(meta); + return error_pos!(meta, pos, "Expected iterable"); + } + }; + + // Create iterator variable + meta.with_push_scope(true, |meta| { + self.iter_global_id = meta.add_var(&self.iter_name, self.iter_type.clone(), false); + if let Some(index) = self.iter_index.as_ref() { + self.iter_index_global_id = meta.add_var(index, Type::Int, false); + } + // Save loop context state and set it to true + meta.with_context_fn(Context::set_is_loop_ctx, true, |meta| { + // Type-check the loop body + self.block.typecheck(meta)?; + Ok(()) + })?; + Ok(()) + })?; + + Ok(()) + } +} + impl IterLoop { fn translate_path(&self, meta: &mut TranslateMetadata) -> Option { if let Some(ExprType::LinesInvocation(value)) = &self.iter_expr.value { diff --git a/src/modules/loops/while_loop.rs b/src/modules/loops/while_loop.rs index 223e18eca..10a8bb63e 100644 --- a/src/modules/loops/while_loop.rs +++ b/src/modules/loops/while_loop.rs @@ -36,11 +36,17 @@ impl SyntaxModule for WhileLoop { )); } + syntax(meta, &mut self.block)?; + Ok(()) + } +} + +impl TypeCheckModule for WhileLoop { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.condition.typecheck(meta)?; // Save loop context state and set it to true meta.with_context_fn(Context::set_is_loop_ctx, true, |meta| { - // Parse loop - syntax(meta, &mut self.block)?; - Ok(()) + self.block.typecheck(meta) })?; Ok(()) } diff --git a/src/modules/main.rs b/src/modules/main.rs index c7ac774cc..f8297c82f 100644 --- a/src/modules/main.rs +++ b/src/modules/main.rs @@ -11,7 +11,8 @@ pub struct Main { pub args: Option, pub args_global_id: Option, pub block: Block, - pub is_skipped: bool + pub token: Option, + pub is_skipped: bool, } impl SyntaxModule for Main { @@ -22,17 +23,14 @@ impl SyntaxModule for Main { args: None, args_global_id: None, block: Block::new().with_no_indent(), + token: None, is_skipped: false } } fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { - let tok = meta.get_current_token(); + self.token = meta.get_current_token(); token(meta, "main")?; - // Main cannot be parsed inside of a block - if !meta.is_global_scope() { - return error!(meta, tok, "Main must be in the global scope") - } // If this main is included in other file, skip it if !meta.context.trace.is_empty() { self.is_skipped = true; @@ -44,7 +42,7 @@ impl SyntaxModule for Main { token(meta, ")")?; } // Create a new scope for variables - meta.with_push_scope(|meta| { + meta.with_push_scope(true, |meta| { // Create variables for arg in self.args.iter() { self.args_global_id = meta.add_var(arg, Type::Array(Box::new(Type::Text)), true); @@ -61,6 +59,26 @@ impl SyntaxModule for Main { } } +impl TypeCheckModule for Main { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + // Main cannot be parsed inside of a block + if !meta.is_global_scope() { + return error!(meta, self.token.clone(), "Main must be in the global scope") + } + + // Typecheck the main block content + meta.with_push_scope(true, |meta| { + // Create variables for main arguments + for arg in self.args.iter() { + meta.add_var(arg, Type::Array(Box::new(Type::Text)), true); + } + // Typecheck the block + self.block.typecheck(meta)?; + Ok(()) + }) + } +} + impl TranslateModule for Main { fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind { if self.is_skipped { diff --git a/src/modules/mod.rs b/src/modules/mod.rs index ed45da19a..558d5487d 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -12,6 +12,7 @@ pub mod imports; pub mod main; pub mod builtin; pub mod prelude; +pub mod typecheck; #[macro_export] macro_rules! handle_types { diff --git a/src/modules/prelude.rs b/src/modules/prelude.rs index c35411fd6..78746d987 100644 --- a/src/modules/prelude.rs +++ b/src/modules/prelude.rs @@ -13,3 +13,4 @@ pub use crate::translate::fragments::var_stmt::VarStmtFragment; pub use crate::translate::module::TranslateModule; pub use crate::translate::compute::ArithOp; pub use crate::utils::{ParserMetadata, TranslateMetadata}; +pub use crate::modules::typecheck::TypeCheckModule; diff --git a/src/modules/shorthand/add.rs b/src/modules/shorthand/add.rs index caf3ba271..a3222e941 100644 --- a/src/modules/shorthand/add.rs +++ b/src/modules/shorthand/add.rs @@ -14,7 +14,8 @@ pub struct ShorthandAdd { expr: Box, kind: Type, global_id: Option, - is_ref: bool + is_ref: bool, + tok: Option, } impl SyntaxModule for ShorthandAdd { @@ -26,20 +27,30 @@ impl SyntaxModule for ShorthandAdd { expr: Box::new(Expr::new()), kind: Type::Null, global_id: None, - is_ref: false + is_ref: false, + tok: None, } } fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { - let var_tok = meta.get_current_token(); + self.tok = meta.get_current_token(); self.var = variable(meta, variable_name_extensions())?; token(meta, "+=")?; - let variable = handle_variable_reference(meta, &var_tok, &self.var)?; - prevent_constant_mutation(meta, &var_tok, &self.var, variable.is_const)?; + syntax(meta, &mut *self.expr)?; + Ok(()) + } +} + +impl TypeCheckModule for ShorthandAdd { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.expr.typecheck(meta)?; + + let variable = handle_variable_reference(meta, &self.tok, &self.var)?; + prevent_constant_mutation(meta, &self.tok, &self.var, variable.is_const)?; self.kind = variable.kind; self.global_id = variable.global_id; self.is_ref = variable.is_ref; - syntax(meta, &mut *self.expr)?; + shorthand_typecheck_allowed_types(meta, "add", &self.kind, &self.expr, &[ Type::Num, Type::Int, diff --git a/src/modules/shorthand/div.rs b/src/modules/shorthand/div.rs index d0d54ef17..dabd6895a 100644 --- a/src/modules/shorthand/div.rs +++ b/src/modules/shorthand/div.rs @@ -14,7 +14,8 @@ pub struct ShorthandDiv { expr: Box, kind: Type, global_id: Option, - is_ref: bool + is_ref: bool, + tok: Option, } impl SyntaxModule for ShorthandDiv { @@ -26,20 +27,30 @@ impl SyntaxModule for ShorthandDiv { expr: Box::new(Expr::new()), kind: Type::Null, global_id: None, - is_ref: false + is_ref: false, + tok: None, } } fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { - let var_tok = meta.get_current_token(); + self.tok = meta.get_current_token(); self.var = variable(meta, variable_name_extensions())?; token(meta, "/=")?; - let variable = handle_variable_reference(meta, &var_tok, &self.var)?; - prevent_constant_mutation(meta, &var_tok, &self.var, variable.is_const)?; + syntax(meta, &mut *self.expr)?; + Ok(()) + } +} + +impl TypeCheckModule for ShorthandDiv { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.expr.typecheck(meta)?; + + let variable = handle_variable_reference(meta, &self.tok, &self.var)?; + prevent_constant_mutation(meta, &self.tok, &self.var, variable.is_const)?; self.kind = variable.kind; self.global_id = variable.global_id; self.is_ref = variable.is_ref; - syntax(meta, &mut *self.expr)?; + shorthand_typecheck_allowed_types(meta, "divide", &self.kind, &self.expr, &[ Type::Num, Type::Int, diff --git a/src/modules/shorthand/modulo.rs b/src/modules/shorthand/modulo.rs index 9a32ffb95..cc4fc1107 100644 --- a/src/modules/shorthand/modulo.rs +++ b/src/modules/shorthand/modulo.rs @@ -14,7 +14,8 @@ pub struct ShorthandModulo { expr: Box, kind: Type, global_id: Option, - is_ref: bool + is_ref: bool, + tok: Option, } impl SyntaxModule for ShorthandModulo { @@ -26,20 +27,30 @@ impl SyntaxModule for ShorthandModulo { expr: Box::new(Expr::new()), kind: Type::Null, global_id: None, - is_ref: false + is_ref: false, + tok: None, } } fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { - let var_tok = meta.get_current_token(); + self.tok = meta.get_current_token(); self.var = variable(meta, variable_name_extensions())?; token(meta, "%=")?; - let variable = handle_variable_reference(meta, &var_tok, &self.var)?; - prevent_constant_mutation(meta, &var_tok, &self.var, variable.is_const)?; + syntax(meta, &mut *self.expr)?; + Ok(()) + } +} + +impl TypeCheckModule for ShorthandModulo { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.expr.typecheck(meta)?; + + let variable = handle_variable_reference(meta, &self.tok, &self.var)?; + prevent_constant_mutation(meta, &self.tok, &self.var, variable.is_const)?; self.kind = variable.kind; self.global_id = variable.global_id; self.is_ref = variable.is_ref; - syntax(meta, &mut *self.expr)?; + shorthand_typecheck_allowed_types(meta, "modulo", &self.kind, &self.expr, &[ Type::Num, Type::Int diff --git a/src/modules/shorthand/mul.rs b/src/modules/shorthand/mul.rs index 9d6c318be..13e1af300 100644 --- a/src/modules/shorthand/mul.rs +++ b/src/modules/shorthand/mul.rs @@ -14,7 +14,8 @@ pub struct ShorthandMul { expr: Box, kind: Type, global_id: Option, - is_ref: bool + is_ref: bool, + tok: Option, } impl SyntaxModule for ShorthandMul { @@ -26,20 +27,30 @@ impl SyntaxModule for ShorthandMul { expr: Box::new(Expr::new()), kind: Type::Null, global_id: None, - is_ref: false + is_ref: false, + tok: None, } } fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { - let var_tok = meta.get_current_token(); + self.tok = meta.get_current_token(); self.var = variable(meta, variable_name_extensions())?; token(meta, "*=")?; - let variable = handle_variable_reference(meta, &var_tok, &self.var)?; - prevent_constant_mutation(meta, &var_tok, &self.var, variable.is_const)?; + syntax(meta, &mut *self.expr)?; + Ok(()) + } +} + +impl TypeCheckModule for ShorthandMul { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.expr.typecheck(meta)?; + + let variable = handle_variable_reference(meta, &self.tok, &self.var)?; + prevent_constant_mutation(meta, &self.tok, &self.var, variable.is_const)?; self.kind = variable.kind; self.global_id = variable.global_id; self.is_ref = variable.is_ref; - syntax(meta, &mut *self.expr)?; + shorthand_typecheck_allowed_types(meta, "multiply", &self.kind, &self.expr, &[ Type::Num, Type::Int, diff --git a/src/modules/shorthand/sub.rs b/src/modules/shorthand/sub.rs index 4d31ccf92..d6e74cfbc 100644 --- a/src/modules/shorthand/sub.rs +++ b/src/modules/shorthand/sub.rs @@ -14,7 +14,8 @@ pub struct ShorthandSub { expr: Box, kind: Type, global_id: Option, - is_ref: bool + is_ref: bool, + tok: Option, } impl SyntaxModule for ShorthandSub { @@ -26,20 +27,30 @@ impl SyntaxModule for ShorthandSub { expr: Box::new(Expr::new()), kind: Type::Null, global_id: None, - is_ref: false + is_ref: false, + tok: None, } } fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { - let var_tok = meta.get_current_token(); + self.tok = meta.get_current_token(); self.var = variable(meta, variable_name_extensions())?; token(meta, "-=")?; - let variable = handle_variable_reference(meta, &var_tok, &self.var)?; - prevent_constant_mutation(meta, &var_tok, &self.var, variable.is_const)?; + syntax(meta, &mut *self.expr)?; + Ok(()) + } +} + +impl TypeCheckModule for ShorthandSub { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.expr.typecheck(meta)?; + + let variable = handle_variable_reference(meta, &self.tok, &self.var)?; + prevent_constant_mutation(meta, &self.tok, &self.var, variable.is_const)?; self.kind = variable.kind; self.global_id = variable.global_id; self.is_ref = variable.is_ref; - syntax(meta, &mut *self.expr)?; + shorthand_typecheck_allowed_types(meta, "subtract", &self.kind, &self.expr, &[ Type::Num, Type::Int, diff --git a/src/modules/statement/comment.rs b/src/modules/statement/comment.rs index d6ce79d12..88250e96d 100644 --- a/src/modules/statement/comment.rs +++ b/src/modules/statement/comment.rs @@ -22,6 +22,12 @@ impl SyntaxModule for Comment { } } +impl TypeCheckModule for Comment { + fn typecheck(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult { + Ok(()) + } +} + impl TranslateModule for Comment { fn translate(&self, meta: &mut crate::utils::TranslateMetadata) -> FragmentKind { if meta.minify { diff --git a/src/modules/statement/comment_doc.rs b/src/modules/statement/comment_doc.rs index 1cf6d6832..5bf3172a0 100644 --- a/src/modules/statement/comment_doc.rs +++ b/src/modules/statement/comment_doc.rs @@ -60,6 +60,12 @@ impl SyntaxModule for CommentDoc { } } +impl TypeCheckModule for CommentDoc { + fn typecheck(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult { + Ok(()) + } +} + impl TranslateModule for CommentDoc { fn translate(&self, _meta: &mut TranslateMetadata) -> FragmentKind { let comments = self.value.trim().lines() diff --git a/src/modules/statement/mod.rs b/src/modules/statement/mod.rs index 6f8c9d547..d4b09ae5c 100644 --- a/src/modules/statement/mod.rs +++ b/src/modules/statement/mod.rs @@ -1,3 +1,25 @@ pub mod stmt; pub mod comment; pub mod comment_doc; + +#[macro_export] +macro_rules! typecheck_statement { + ($meta:expr, $stmt_type:expr, [$($stmt:ident),*]) => { + match $stmt_type { + $( + StmtType::$stmt(stmt) => stmt.typecheck($meta)?, + )* + } + }; +} + +#[macro_export] +macro_rules! translate_statement { + ($meta:expr, $stmt_type:expr, [$($stmt:ident),*]) => { + match $stmt_type { + $( + StmtType::$stmt(stmt) => stmt.translate($meta)?, + )* + } + }; +} diff --git a/src/modules/statement/stmt.rs b/src/modules/statement/stmt.rs index 5fa59dc8d..a24a8a9fe 100644 --- a/src/modules/statement/stmt.rs +++ b/src/modules/statement/stmt.rs @@ -9,7 +9,7 @@ use crate::modules::variable::{ set::VariableSet, }; use crate::modules::command::modifier::CommandModifier; -use crate::handle_types; +use crate::{handle_types, typecheck_statement}; use crate::modules::condition::{ ifchain::IfChain, ifcond::IfCondition, @@ -45,7 +45,7 @@ use super::comment_doc::CommentDoc; use super::comment::Comment; #[derive(Debug, Clone)] -pub enum StatementType { +pub enum StmtType { Expr(Expr), VariableInit(VariableInit), VariableSet(VariableSet), @@ -77,11 +77,11 @@ pub enum StatementType { #[derive(Debug, Clone)] pub struct Statement { - pub value: Option + pub value: Option } impl Statement { - handle_types!(StatementType, [ + handle_types!(StmtType, [ // Imports Import, // Functions @@ -105,7 +105,7 @@ impl Statement { ]); // Get result out of the provided module and save it in the internal state - fn get(&mut self, meta: &mut M, mut module: S, cb: impl Fn(S) -> StatementType) -> SyntaxResult + fn get(&mut self, meta: &mut M, mut module: S, cb: impl Fn(S) -> StmtType) -> SyntaxResult where M: Metadata, S: SyntaxModule @@ -121,7 +121,7 @@ impl Statement { pub fn get_docs_item_name(&self) -> Option { match &self.value { - Some(StatementType::FunctionDeclaration(inner)) => Some(inner.name.clone()), + Some(StmtType::FunctionDeclaration(inner)) => Some(inner.name.clone()), _ => None, } } @@ -155,13 +155,26 @@ impl SyntaxModule for Statement { } } +impl TypeCheckModule for Statement { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + typecheck_statement!(meta, self.value.as_mut().unwrap(), [ + Break, Cd, CommandModifier, Comment, CommentDoc, Continue, Echo, + Exit, Expr, Fail, FunctionDeclaration, IfChain, IfCondition, + Import, InfiniteLoop, IterLoop, Main, Mv, Return, ShorthandAdd, + ShorthandDiv, ShorthandModulo, ShorthandMul, ShorthandSub, + VariableInit, VariableSet, WhileLoop + ]); + Ok(()) + } +} + impl TranslateModule for Statement { fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind { // Translate the staxtement let statement = self.value.as_ref().unwrap(); // This is a workaround that handles $(...) which cannot be used as a statement match statement { - StatementType::Expr(expr) => { + StmtType::Expr(expr) => { match &expr.value { Some(ExprType::Command(cmd)) => { cmd.translate_command_statement(meta) diff --git a/src/modules/typecheck.rs b/src/modules/typecheck.rs new file mode 100644 index 000000000..e2e085d4f --- /dev/null +++ b/src/modules/typecheck.rs @@ -0,0 +1,11 @@ +use heraclitus_compiler::prelude::*; +use crate::utils::ParserMetadata; + +/// Trait for modules that need to perform type checking operations. +/// This separates type checking logic from the parsing logic in SyntaxModule. +pub trait TypeCheckModule { + /// Perform type checking for this module. + /// This method should be called after parsing is complete but before translation. + /// It can mutate the module to store type information. + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult; +} \ No newline at end of file diff --git a/src/modules/variable/get.rs b/src/modules/variable/get.rs index fc87c8171..25fbb591b 100644 --- a/src/modules/variable/get.rs +++ b/src/modules/variable/get.rs @@ -1,7 +1,8 @@ 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::modules::variable::{handle_index_accessor, handle_variable_reference, variable_name_extensions, validate_index_accessor}; use crate::modules::prelude::*; +use crate::modules::typecheck::TypeCheckModule; use heraclitus_compiler::prelude::*; #[derive(Debug, Clone)] @@ -10,7 +11,8 @@ pub struct VariableGet { kind: Type, global_id: Option, index: Box>, - is_ref: bool + is_ref: bool, + tok: Option } impl Typed for VariableGet { @@ -42,22 +44,40 @@ impl SyntaxModule for VariableGet { kind: Type::Null, global_id: None, index: Box::new(None), - is_ref: false + is_ref: false, + tok: None } } fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { - let tok = meta.get_current_token(); + self.tok = meta.get_current_token(); self.name = variable(meta, variable_name_extensions())?; - let variable = handle_variable_reference(meta, &tok, &self.name)?; + self.index = Box::new(handle_index_accessor(meta, true)?); + Ok(()) + } +} + +impl TypeCheckModule for VariableGet { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + let variable = handle_variable_reference(meta, &self.tok, &self.name)?; 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, 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)); + + // Typecheck and validate index expression if present + if let Some(ref mut index_expr) = self.index.as_mut() { + // Check if the variable can be indexed + if !matches!(variable.kind, Type::Array(_)) { + return error!(meta, self.tok.clone(), format!("Cannot index a non-array variable of type '{}'", self.kind)); + } + + // Typecheck the index expression + index_expr.typecheck(meta)?; + + // Validate the index type + validate_index_accessor(meta, index_expr, true, self.tok.clone())?; } + Ok(()) } } diff --git a/src/modules/variable/init.rs b/src/modules/variable/init.rs index 21c9b9781..c60fc6316 100644 --- a/src/modules/variable/init.rs +++ b/src/modules/variable/init.rs @@ -11,15 +11,15 @@ pub struct VariableInit { global_id: Option, is_fun_ctx: bool, is_const: bool, + tok: Option, } impl VariableInit { fn handle_add_variable( &mut self, meta: &mut ParserMetadata, - tok: Option ) -> SyntaxResult { - handle_identifier_name(meta, &self.name, tok)?; + handle_identifier_name(meta, &self.name, self.tok.clone())?; self.global_id = meta.add_var(&self.name, self.expr.get_type(), self.is_const); Ok(()) } @@ -34,21 +34,19 @@ impl SyntaxModule for VariableInit { expr: Box::new(Expr::new()), global_id: None, is_fun_ctx: false, - is_const: false + is_const: false, + tok: None, } } fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { let keyword = token_by(meta, |word| ["let", "const"].contains(&word.as_str()))?; self.is_const = keyword == "const"; - // Get the variable name - let tok = meta.get_current_token(); + self.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_variable(meta, tok)?; self.is_fun_ctx = meta.context.is_fun_ctx; Ok(()) }, |position| { @@ -57,6 +55,14 @@ impl SyntaxModule for VariableInit { } } +impl TypeCheckModule for VariableInit { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.expr.typecheck(meta)?; + self.handle_add_variable(meta)?; + Ok(()) + } +} + impl TranslateModule for VariableInit { fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind { let expr = self.expr.translate(meta); diff --git a/src/modules/variable/mod.rs b/src/modules/variable/mod.rs index 52d250911..1e8b08001 100644 --- a/src/modules/variable/mod.rs +++ b/src/modules/variable/mod.rs @@ -112,24 +112,27 @@ fn is_camel_case(name: &str) -> bool { false } -pub fn handle_index_accessor(meta: &mut ParserMetadata, range: bool) -> Result, Failure> { +pub fn handle_index_accessor(meta: &mut ParserMetadata, _range: bool) -> Result, Failure> { if token(meta, "[").is_ok() { - let tok = meta.get_current_token(); let mut index = Expr::new(); syntax(meta, &mut index)?; - if !allow_index_accessor(&index, range) { - let expected = if range { "integer or range" } else { "integer (and not a range)" }; - let side = if range { "right" } else { "left" }; - let message = format!("Index accessor must be an {expected} for {side} side of operation"); - let comment = format!("The index accessor must be an {} and not {}", expected, index.get_type()); - return error!(meta, tok => { message: message, comment: comment }); - } token(meta, "]")?; return Ok(Some(index)); } Ok(None) } +pub fn validate_index_accessor(meta: &ParserMetadata, index: &Expr, range: bool, tok: Option) -> SyntaxResult { + if !allow_index_accessor(index, range) { + let expected = if range { "integer or range" } else { "integer (and not a range)" }; + let side = if range { "right" } else { "left" }; + let message = format!("Index accessor must be an {expected} for {side} side of operation"); + let comment = format!("The index accessor must be an {} and not {}", expected, index.get_type()); + return error!(meta, tok => { message: message, comment: comment }); + } + Ok(()) +} + fn allow_index_accessor(index: &Expr, range: bool) -> bool { match (&index.kind, &index.value) { (Type::Int, _) => true, diff --git a/src/modules/variable/set.rs b/src/modules/variable/set.rs index a981ceabb..5b3a29648 100644 --- a/src/modules/variable/set.rs +++ b/src/modules/variable/set.rs @@ -3,7 +3,7 @@ use crate::modules::prelude::*; use crate::docs::module::DocumentationModule; use crate::{modules::expression::expr::Expr, translate::module::TranslateModule}; use crate::utils::{ParserMetadata, TranslateMetadata}; -use super::{handle_index_accessor, handle_variable_reference, prevent_constant_mutation, variable_name_extensions}; +use super::{handle_index_accessor, handle_variable_reference, prevent_constant_mutation, variable_name_extensions, validate_index_accessor}; use crate::modules::types::{Typed, Type}; #[derive(Debug, Clone)] @@ -12,7 +12,9 @@ pub struct VariableSet { expr: Box, global_id: Option, index: Option, - is_ref: bool + is_ref: bool, + var_type: Type, + tok: Option, } impl SyntaxModule for VariableSet { @@ -24,41 +26,60 @@ impl SyntaxModule for VariableSet { expr: Box::new(Expr::new()), global_id: None, index: None, - is_ref: false + is_ref: false, + var_type: Type::Null, + tok: None, } } fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { - let tok = meta.get_current_token(); + self.tok = meta.get_current_token(); self.name = variable(meta, variable_name_extensions())?; self.index = handle_index_accessor(meta, false)?; token(meta, "=")?; syntax(meta, &mut *self.expr)?; - let variable = handle_variable_reference(meta, &tok, &self.name)?; + Ok(()) + } +} + +impl TypeCheckModule for VariableSet { + fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + self.expr.typecheck(meta)?; + if let Some(index) = &mut self.index { + index.typecheck(meta)?; + } + + let variable = handle_variable_reference(meta, &self.tok, &self.name)?; self.global_id = variable.global_id; self.is_ref = variable.is_ref; - prevent_constant_mutation(meta, &tok, &self.name, variable.is_const)?; - // Typecheck the variable - let left_type = variable.kind.clone(); - let right_type = self.expr.get_type(); - // Check if the variable can be indexed - if self.index.is_some() && !matches!(variable.kind, Type::Array(_)) { - return error!(meta, tok, format!("Cannot assign a value to an index of a non-array variable of type '{left_type}'")); + self.var_type = variable.kind.clone(); + prevent_constant_mutation(meta, &self.tok, &self.name, variable.is_const)?; + + if let Some(ref index_expr) = self.index { + if !matches!(variable.kind, Type::Array(_)) { + let left_type = variable.kind.clone(); + return error!(meta, self.tok.clone(), format!("Cannot assign a value to an index of a non-array variable of type '{left_type}'")); + } + + // Validate the index type (must be integer, not range, for assignment) + validate_index_accessor(meta, index_expr, false, self.tok.clone())?; } - // Handle index assignment + + let right_type = self.expr.get_type(); + if self.index.is_some() { - // Check if the assigned value is compatible with the array - if let Type::Array(kind) = variable.kind.clone() { - if !self.expr.get_type().is_allowed_in(&kind) { - let right_type = self.expr.get_type(); - return error!(meta, tok, format!("Cannot assign value of type '{right_type}' to an array of '{kind}'")); + if let Type::Array(kind) = &self.var_type { + if !right_type.is_allowed_in(kind) { + let tok = self.expr.get_position(meta); + return error_pos!(meta, tok, format!("Cannot assign value of type '{right_type}' to an array of '{kind}'")); } } } - // Check if the variable is compatible with the assigned value - else if !self.expr.get_type().is_allowed_in(&variable.kind) { - return error!(meta, tok, format!("Cannot assign value of type '{right_type}' to a variable of type '{left_type}'")); + else if !right_type.is_allowed_in(&self.var_type) { + let tok = self.expr.get_position(meta); + return error_pos!(meta, tok, format!("Cannot assign value of type '{right_type}' to a variable of type '{}'", self.var_type)); } + Ok(()) } } diff --git a/src/tests/erroring/exit_invalid_type.ab b/src/tests/erroring/exit_invalid_type.ab new file mode 100644 index 000000000..8dedd908d --- /dev/null +++ b/src/tests/erroring/exit_invalid_type.ab @@ -0,0 +1,4 @@ +// Output +// Builtin function `exit` can only be used with values of type Int + +exit "hello" \ No newline at end of file diff --git a/src/tests/erroring/failed_command.ab b/src/tests/erroring/failed_command.ab index 7edfe7981..4d466b046 100644 --- a/src/tests/erroring/failed_command.ab +++ b/src/tests/erroring/failed_command.ab @@ -1,5 +1,5 @@ // Output -// Failed command must be followed by a 'then', 'succeeded' or 'failed' block, statement or operator '?' +// Failed command must be followed by an 'exited', 'succeeded' or 'failed' block, statement or operator '?' echo $ echo "Hello, this will not fail" $ echo $ echo "This is my first amber command" $ diff --git a/src/tests/erroring/failed_function.ab b/src/tests/erroring/failed_function.ab index 289825ff3..fddee3ba8 100644 --- a/src/tests/erroring/failed_function.ab +++ b/src/tests/erroring/failed_function.ab @@ -1,5 +1,5 @@ // Output -// Failed function call 'foo' must be followed by a 'then', 'succeeded' or 'failed' block, statement or operator '?' +// Function 'foo' can potentially fail but is left unhandled. fun foo(a: Text, b: Num): Null? { echo a + " World" diff --git a/src/tests/erroring/then_empty_param.ab b/src/tests/erroring/then_empty_param.ab index 758e07af0..5fbac0e88 100644 --- a/src/tests/erroring/then_empty_param.ab +++ b/src/tests/erroring/then_empty_param.ab @@ -2,7 +2,7 @@ // Parameter name cannot be empty main { - $ echo "test" $ then() { + $ echo "test" $ exited() { echo "Success!" } -} \ No newline at end of file +} diff --git a/src/tests/erroring/then_failed_same_line.ab b/src/tests/erroring/then_failed_same_line.ab index 4773439ee..bcf226a1d 100644 --- a/src/tests/erroring/then_failed_same_line.ab +++ b/src/tests/erroring/then_failed_same_line.ab @@ -1,10 +1,10 @@ // Output -// Cannot use both 'then' and 'failed' blocks for the same command +// Cannot use both 'exited' and 'failed' blocks for the same command main { - $ echo "test" $ then(code) { + $ echo "test" $ exited(code) { echo "Success!" } failed { echo "Failed!" } -} \ No newline at end of file +} diff --git a/src/tests/erroring/then_succeeded_same_line.ab b/src/tests/erroring/then_succeeded_same_line.ab index 27d4ae7c2..e72badf6b 100644 --- a/src/tests/erroring/then_succeeded_same_line.ab +++ b/src/tests/erroring/then_succeeded_same_line.ab @@ -1,10 +1,10 @@ // Output -// Cannot use both 'then' and 'succeeded' blocks for the same command +// Cannot use both 'exited' and 'succeeded' blocks for the same command main { - $ echo "test" $ then(code) { + $ echo "test" $ exited(code) { echo "Success!" } succeeded { echo "Success!" } -} \ No newline at end of file +} diff --git a/src/tests/optimizing.rs b/src/tests/optimizing.rs index 3b201fb4d..768c9e504 100644 --- a/src/tests/optimizing.rs +++ b/src/tests/optimizing.rs @@ -13,6 +13,7 @@ pub fn translate_and_optimize_amber_code>(code: T) -> Option>(code: T) -> Option { let compiler = AmberCompiler::new(code.into(), None, options); let tokens = compiler.tokenize().ok()?; let (ast, meta) = compiler.parse(tokens).ok()?; + let (ast, meta) = compiler.typecheck(ast, meta).ok()?; let mut translate_meta = TranslateMetadata::new(meta, &compiler.options); Some(ast.translate(&mut translate_meta)) } diff --git a/src/tests/validity/nameof.ab b/src/tests/validity/nameof.ab new file mode 100644 index 000000000..1fb9e37b2 --- /dev/null +++ b/src/tests/validity/nameof.ab @@ -0,0 +1,5 @@ +// Output +// 12 + +let variable = 12 +trust $ echo \${nameof variable} $ diff --git a/src/tests/validity/then_block.ab b/src/tests/validity/then_block.ab index db2e46c9c..ff7dd4273 100644 --- a/src/tests/validity/then_block.ab +++ b/src/tests/validity/then_block.ab @@ -3,7 +3,7 @@ // Test passed with code: 0 main { - $ echo "test" $ then(code) { + $ echo "test" $ exited(code) { echo "Test passed with code: {code}" } -} \ No newline at end of file +} diff --git a/src/tests/validity/then_block_failure.ab b/src/tests/validity/then_block_failure.ab index 6e708ccb7..fa82d6747 100644 --- a/src/tests/validity/then_block_failure.ab +++ b/src/tests/validity/then_block_failure.ab @@ -3,8 +3,8 @@ main { silent { - $ nonexistent_command $ then(code) { + $ nonexistent_command $ exited(code) { echo "Command failed with code: {code}" } } -} \ No newline at end of file +} diff --git a/src/utils/context.rs b/src/utils/context.rs index 2591d2790..fe86d098e 100644 --- a/src/utils/context.rs +++ b/src/utils/context.rs @@ -1,17 +1,23 @@ use super::{cc_flags::CCFlags, function_interface::FunctionInterface}; use crate::modules::expression::expr::Expr; +use crate::modules::function::declaration::FunctionDeclarationArgument; use crate::modules::types::Type; use amber_meta::ContextHelper; use heraclitus_compiler::prelude::*; use std::collections::{HashMap, HashSet}; +#[derive(Clone, Debug)] +pub struct FunctionDeclArg { + pub name: String, + pub kind: Type, + pub optional: Option, + pub is_ref: bool, +} + #[derive(Clone, Debug)] pub struct FunctionDecl { pub name: String, - pub arg_names: Vec, - pub arg_types: Vec, - pub arg_refs: Vec, - pub arg_optionals: Vec, + pub args: Vec, pub returns: Type, pub is_args_typed: bool, pub is_public: bool, @@ -21,13 +27,18 @@ pub struct FunctionDecl { impl FunctionDecl { pub fn into_interface(self) -> FunctionInterface { + let args = self.args.into_iter().map(|arg| FunctionDeclarationArgument { + name: arg.name, + kind: arg.kind, + optional: arg.optional, + is_ref: arg.is_ref, + tok: None, + }).collect(); + FunctionInterface { id: Some(self.id), name: self.name, - arg_names: self.arg_names, - arg_types: self.arg_types, - arg_refs: self.arg_refs, - arg_optionals: self.arg_optionals, + args, returns: self.returns, is_public: self.is_public, is_failable: self.is_failable, diff --git a/src/utils/function_interface.rs b/src/utils/function_interface.rs index 8a6923ef9..465c37c2d 100644 --- a/src/utils/function_interface.rs +++ b/src/utils/function_interface.rs @@ -1,17 +1,12 @@ use crate::modules::{types::Type, block::Block}; -use crate::modules::expression::expr::Expr; -use super::{context::FunctionDecl, function_cache::FunctionInstance}; - - +use crate::modules::function::declaration::FunctionDeclarationArgument; +use super::{context::{FunctionDecl, FunctionDeclArg}, function_cache::FunctionInstance}; #[derive(Clone, Debug)] pub struct FunctionInterface { pub id: Option, pub name: String, - pub arg_names: Vec, - pub arg_types: Vec, - pub arg_refs: Vec, - pub arg_optionals : Vec, + pub args: Vec, pub returns: Type, pub is_public: bool, pub is_failable: bool, @@ -19,13 +14,17 @@ pub struct FunctionInterface { impl FunctionInterface { pub fn into_fun_declaration(self, id: usize) -> FunctionDecl { - let is_args_typed = self.arg_types.iter().all(|t| t != &Type::Generic); + let is_args_typed = self.args.iter().all(|arg| arg.kind != Type::Generic); + let args = self.args.into_iter().map(|arg| FunctionDeclArg { + name: arg.name, + kind: arg.kind, + optional: arg.optional, + is_ref: arg.is_ref, + }).collect(); + FunctionDecl { name: self.name, - arg_names: self.arg_names, - arg_types: self.arg_types, - arg_refs: self.arg_refs, - arg_optionals: self.arg_optionals, + args, returns: self.returns, is_args_typed, is_public: self.is_public, @@ -37,9 +36,9 @@ impl FunctionInterface { pub fn into_fun_instance(self, block: Block) -> FunctionInstance { FunctionInstance { variant_id: 0, - args: self.arg_types, + args: self.args.iter().map(|arg| arg.kind.clone()).collect(), returns: self.returns, block } } -} \ No newline at end of file +} diff --git a/src/utils/metadata/parser.rs b/src/utils/metadata/parser.rs index 65ff6ffc9..377a82d03 100644 --- a/src/utils/metadata/parser.rs +++ b/src/utils/metadata/parser.rs @@ -48,13 +48,17 @@ impl ParserMetadata { } /// Pushes a new scope to the stack - pub fn with_push_scope(&mut self, mut body: B) -> SyntaxResult + pub fn with_push_scope(&mut self, predicate: bool, mut body: B) -> SyntaxResult where B: FnMut(&mut Self) -> SyntaxResult { - self.context.scopes.push(ScopeUnit::new()); + if predicate { + self.context.scopes.push(ScopeUnit::new()); + } let result = body(self); - self.context.scopes.pop(); + if predicate { + self.context.scopes.pop(); + } result } diff --git a/src/utils/metadata/translate.rs b/src/utils/metadata/translate.rs index 5fc7124b0..def29febd 100644 --- a/src/utils/metadata/translate.rs +++ b/src/utils/metadata/translate.rs @@ -96,7 +96,7 @@ impl TranslateMetadata { if self.sudoed { let var_name = "__sudo"; let condition = r#"[ "$(id -u)" -ne 0 ] && command -v sudo >/dev/null 2>&1 && printf sudo"#; - let condition_frag = RawFragment::new(&format!("$({})", condition)).to_frag(); + let condition_frag = RawFragment::new(&format!("$({condition})")).to_frag(); let var_stmt = VarStmtFragment::new(var_name, Type::Text, condition_frag); let var_expr = VarExprFragment::from_stmt(&var_stmt).with_quotes(false); self.stmt_queue.push_back(var_stmt.to_frag());