diff --git a/Cargo.lock b/Cargo.lock index f3465020..4ff6bd09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -48,9 +48,9 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "heraclitus-compiler" -version = "1.5.6" +version = "1.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07fe84cac1d31ea6cca6ea79984b2e5c2f2eabb973a0e26cdeeaef0e0906a26b" +checksum = "1b30d37150dbf5477a91ad1a9546eab07c3f73225dbe61dd9a8bacd7074deb43" dependencies = [ "capitalize", "colored", diff --git a/Cargo.toml b/Cargo.toml index cecb396b..5d57138f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -heraclitus-compiler = "1.5.6" +heraclitus-compiler = "1.5.8" similar-string = "1.4.2" colored = "2.0.0" itertools = "0.10.5" diff --git a/src/modules/block.rs b/src/modules/block.rs index 6d659269..d17d8743 100644 --- a/src/modules/block.rs +++ b/src/modules/block.rs @@ -1,3 +1,5 @@ +use std::collections::VecDeque; + use heraclitus_compiler::prelude::*; use crate::{utils::{metadata::ParserMetadata, TranslateMetadata}}; use crate::translate::module::TranslateModule; @@ -38,7 +40,7 @@ impl SyntaxModule for Block { continue; } // Handle comments - if token.word.starts_with('#') { + if token.word.starts_with("//") { meta.increment_index(); continue } @@ -62,6 +64,9 @@ impl SyntaxModule for Block { impl TranslateModule for Block { fn translate(&self, meta: &mut TranslateMetadata) -> String { + // Save the current statement queue and create a new one + let mut new_queue = VecDeque::new(); + std::mem::swap(&mut meta.stmt_queue, &mut new_queue); meta.increase_indent(); let result = if self.is_empty() { ":".to_string() @@ -73,6 +78,8 @@ impl TranslateModule for Block { .collect::>().join(";\n") }; meta.decrease_indent(); + // Restore the old statement queue + std::mem::swap(&mut meta.stmt_queue, &mut new_queue); result } } \ No newline at end of file diff --git a/src/modules/condition/ifchain.rs b/src/modules/condition/ifchain.rs index 124315b2..01d03127 100644 --- a/src/modules/condition/ifchain.rs +++ b/src/modules/condition/ifchain.rs @@ -30,7 +30,7 @@ impl SyntaxModule for IfChain { let mut cond = Expr::new(); let mut block = Block::new(); // Handle comments and empty lines - if token_by(meta, |token| token.starts_with('#') || token.starts_with('\n')).is_ok() { + if token_by(meta, |token| token.starts_with("//") || token.starts_with('\n')).is_ok() { continue } // Handle else keyword diff --git a/src/modules/condition/ifcond.rs b/src/modules/condition/ifcond.rs index e70b3dd2..c57615ca 100644 --- a/src/modules/condition/ifcond.rs +++ b/src/modules/condition/ifcond.rs @@ -1,6 +1,7 @@ use heraclitus_compiler::prelude::*; use crate::modules::expression::expr::Expr; use crate::translate::module::TranslateModule; +use crate::utils::cc_flags::{CCFlags, get_ccflag_name}; use crate::utils::metadata::{ParserMetadata, TranslateMetadata}; use crate::modules::block::Block; use crate::modules::statement::stmt::{Statement, StatementType}; @@ -15,10 +16,12 @@ 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(_)); - if is_not_if_chain { + if is_not_if_chain && !meta.context.cc_flags.contains(&CCFlags::AllowNestedIfElse) { + let flag_name = get_ccflag_name(CCFlags::AllowNestedIfElse); // TODO: [A34] Add a comment pointing to the website documentation let message = Message::new_warn_at_token(meta, tok) - .message("You should use if-chain instead of nested if else statements"); + .message("You should use if-chain instead of nested if else statements") + .comment(format!("To surpress this warning, use #[{flag_name}] before the parent function declaration")); meta.add_message(message); } Ok(()) diff --git a/src/modules/expression/expr.rs b/src/modules/expression/expr.rs index 6a96d212..a262de7e 100644 --- a/src/modules/expression/expr.rs +++ b/src/modules/expression/expr.rs @@ -26,7 +26,8 @@ use super::binop::{ neq::Neq }; use super::unop::{ - not::Not + not::Not, + cast::Cast }; use super::parenthesis::Parenthesis; use crate::modules::variable::get::VariableGet; @@ -61,7 +62,8 @@ pub enum ExprType { FunctionInvocation(FunctionInvocation), Array(Array), Range(Range), - Null(Null) + Null(Null), + Cast(Cast) } #[derive(Debug, Clone)] @@ -97,11 +99,13 @@ impl Expr { // Ternary conditional Ternary, // Logical operators - And, Or, Not, + And, Or, // Comparison operators Gt, Ge, Lt, Le, Eq, Neq, // Arithmetic operators Add, Sub, Mul, Div, Modulo, + // Unary operators + Cast, Not, // Literals Range, Parenthesis, CommandExpr, Bool, Number, Text, Array, Null, // Function invocation diff --git a/src/modules/expression/unop/cast.rs b/src/modules/expression/unop/cast.rs new file mode 100644 index 00000000..3fe3a9fe --- /dev/null +++ b/src/modules/expression/unop/cast.rs @@ -0,0 +1,53 @@ +use heraclitus_compiler::prelude::*; +use crate::{utils::{metadata::ParserMetadata, TranslateMetadata, cc_flags::{get_ccflag_name, CCFlags}}, modules::{types::{Type, Typed, parse_type}, expression::binop::parse_left_expr}, translate::{module::TranslateModule}}; +use super::super::expr::Expr; + +#[derive(Debug, Clone)] +pub struct Cast { + expr: Box, + kind: Type +} + +impl Typed for Cast { + fn get_type(&self) -> Type { + self.kind.clone() + } +} + +impl SyntaxModule for Cast { + syntax_name!("Cast"); + + fn new() -> Self { + Cast { + expr: Box::new(Expr::new()), + kind: Type::Generic + } + } + + fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + parse_left_expr(meta, &mut *self.expr, "as")?; + let tok = meta.get_current_token(); + token(meta, "as")?; + self.kind = parse_type(meta)?; + if !meta.context.cc_flags.contains(&CCFlags::AllowAbsurdCast) { + let flag_name = get_ccflag_name(CCFlags::AllowAbsurdCast); + let l_type = self.expr.get_type(); + let r_type = self.kind.clone(); + let message = Message::new_warn_at_token(meta, tok) + .message(format!("Casting a value of type '{l_type}' value to a '{r_type}' is not recommended")) + .comment(format!("To suppress this warning, use #[{flag_name}] before the parent function declaration")); + match self.kind { + Type::Array(_) | Type::Null => meta.add_message(message), + Type::Num => if self.expr.get_type() == Type::Text { meta.add_message(message) }, + _ => {} + } + } + Ok(()) + } +} + +impl TranslateModule for Cast { + fn translate(&self, meta: &mut TranslateMetadata) -> String { + self.expr.translate(meta) + } +} \ No newline at end of file diff --git a/src/modules/expression/unop/mod.rs b/src/modules/expression/unop/mod.rs index f1ba0c66..d59edabb 100644 --- a/src/modules/expression/unop/mod.rs +++ b/src/modules/expression/unop/mod.rs @@ -1 +1,2 @@ -pub mod not; \ No newline at end of file +pub mod not; +pub mod cast; \ No newline at end of file diff --git a/src/modules/expression/unop/not.rs b/src/modules/expression/unop/not.rs index 05853028..fe58bff0 100644 --- a/src/modules/expression/unop/not.rs +++ b/src/modules/expression/unop/not.rs @@ -4,13 +4,12 @@ use super::super::expr::Expr; #[derive(Debug, Clone)] pub struct Not { - expr: Box, - kind: Type + expr: Box } impl Typed for Not { fn get_type(&self) -> Type { - self.kind.clone() + Type::Bool } } @@ -19,8 +18,7 @@ impl SyntaxModule for Not { fn new() -> Self { Not { - expr: Box::new(Expr::new()), - kind: Type::Bool + expr: Box::new(Expr::new()) } } diff --git a/src/modules/function/declaration.rs b/src/modules/function/declaration.rs index c3262990..b2686921 100644 --- a/src/modules/function/declaration.rs +++ b/src/modules/function/declaration.rs @@ -1,7 +1,11 @@ +use std::collections::HashSet; +use std::mem::swap; + use heraclitus_compiler::prelude::*; use itertools::izip; use crate::modules::types::Type; use crate::modules::variable::variable_name_extensions; +use crate::utils::cc_flags::get_ccflag_by_name; use crate::utils::function_cache::FunctionInstance; use crate::utils::function_interface::FunctionInterface; use crate::utils::metadata::{ParserMetadata, TranslateMetadata}; @@ -62,6 +66,12 @@ impl SyntaxModule for FunctionDeclaration { } fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { + let mut flags = HashSet::new(); + // Get all the user-defined compiler flags + while let Ok(flag) = token_by(meta, |val| val.starts_with("#[")) { + // Push to the flags vector as it is more safe in case of parsing errors + flags.insert(get_ccflag_by_name(&flag[2..flag.len() - 1])); + } // Check if this function is public if token(meta, "pub").is_ok() { self.is_public = true; @@ -72,6 +82,8 @@ impl SyntaxModule for FunctionDeclaration { self.name = variable(meta, variable_name_extensions())?; handle_existing_function(meta, tok.clone())?; context!({ + // Set the compiler flags + swap(&mut meta.context.cc_flags, &mut flags); // Get the arguments token(meta, "(")?; loop { @@ -120,6 +132,8 @@ impl SyntaxModule for FunctionDeclaration { returns: self.returns.clone(), is_public: self.is_public }, ctx)?; + // Restore the compiler flags + swap(&mut meta.context.cc_flags, &mut flags); Ok(()) }, |pos| { error_pos!(meta, pos, format!("Failed to parse function declaration '{}'", self.name)) diff --git a/src/modules/function/declaration_utils.rs b/src/modules/function/declaration_utils.rs index 86955609..afa88e00 100644 --- a/src/modules/function/declaration_utils.rs +++ b/src/modules/function/declaration_utils.rs @@ -2,6 +2,7 @@ use heraclitus_compiler::prelude::*; use crate::modules::types::Type; use crate::utils::ParserMetadata; use crate::modules::variable::{handle_identifier_name}; +use crate::utils::cc_flags::{CCFlags, get_ccflag_name}; use crate::utils::context::Context; use crate::utils::function_interface::FunctionInterface; @@ -42,10 +43,11 @@ pub fn handle_add_function(meta: &mut ParserMetadata, tok: Option, fun: F comment: "Please decide whether to use generics or types for all arguments" }) } - if any_typed && fun.returns == Type::Generic { + if any_typed && fun.returns == Type::Generic && !meta.context.cc_flags.contains(&CCFlags::AllowGenericReturn) { + let flag_name = get_ccflag_name(CCFlags::AllowGenericReturn); let message = Message::new_warn_at_token(meta, tok.clone()) .message("Function has typed arguments but a generic return type") - .comment(format!("To surpress this warning, specify a return type for the function '{name}'")); + .comment(format!("To surpress this warning, specify a return type for the function '{name}' or use #[{flag_name}] before the parent function declaration")); meta.add_message(message); } // Try to add the function to the memory diff --git a/src/modules/function/ret.rs b/src/modules/function/ret.rs index 562e08e2..a5a5d551 100644 --- a/src/modules/function/ret.rs +++ b/src/modules/function/ret.rs @@ -5,41 +5,41 @@ use crate::utils::metadata::{ParserMetadata, TranslateMetadata}; use crate::translate::module::TranslateModule; #[derive(Debug, Clone)] -pub struct Ret { +pub struct Return { pub expr: Expr } -impl Typed for Ret { +impl Typed for Return { fn get_type(&self) -> Type { self.expr.get_type() } } -impl SyntaxModule for Ret { - syntax_name!("Ret"); +impl SyntaxModule for Return { + syntax_name!("Return"); fn new() -> Self { - Ret { + Return { expr: Expr::new() } } fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { let tok = meta.get_current_token(); - token(meta, "ret")?; + token(meta, "return")?; if !meta.context.is_fun_ctx { - return error!(meta, tok, - "Return statement outside of function", - "Return statements can only be used inside of functions" - ); + 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)?; match meta.context.fun_ret_type.as_ref() { Some(ret_type) => if ret_type != &self.expr.get_type() { - return error!(meta, tok, - "Return type does not match function return type", - format!("Given type: {}, expected type: {}", self.expr.get_type(), ret_type) - ); + return error!(meta, tok => { + message: "Return type does not match function return type", + comment: format!("Given type: {}, expected type: {}", self.expr.get_type(), ret_type) + }); }, None => { meta.context.fun_ret_type = Some(self.expr.get_type()); @@ -49,7 +49,7 @@ impl SyntaxModule for Ret { } } -impl TranslateModule for Ret { +impl TranslateModule for Return { fn translate(&self, meta: &mut TranslateMetadata) -> String { let (name, id, variant) = meta.fun_name.clone().expect("Function name not set"); let result = self.expr.translate_eval(meta, false); diff --git a/src/modules/statement/stmt.rs b/src/modules/statement/stmt.rs index 04c981ac..eaa3781e 100644 --- a/src/modules/statement/stmt.rs +++ b/src/modules/statement/stmt.rs @@ -28,7 +28,7 @@ use crate::modules::loops::{ }; use crate::modules::function::{ declaration::FunctionDeclaration, - ret::Ret + ret::Return }; use crate::modules::imports::{ import::Import @@ -54,7 +54,7 @@ pub enum StatementType { Break(Break), Continue(Continue), FunctionDeclaration(FunctionDeclaration), - Ret(Ret), + Return(Return), Import(Import), Main(Main), Echo(Echo) @@ -70,7 +70,7 @@ impl Statement { // Imports Import, // Functions - FunctionDeclaration, Main, Ret, + FunctionDeclaration, Main, Return, // Loops InfiniteLoop, IterLoop, Break, Continue, // Conditions @@ -118,7 +118,7 @@ impl SyntaxModule for Statement { for statement in statements { // Handle comments if let Some(token) = meta.get_current_token() { - if token.word.starts_with('#') { + if token.word.starts_with("//") { meta.increment_index(); continue } @@ -143,9 +143,9 @@ impl TranslateModule for Statement { // Translate the statement let translated = self.translate_match(meta, self.value.as_ref().unwrap()); // This is a workaround that handles $(...) which cannot be used as a statement - let translated = if translated.starts_with('$') || translated.starts_with("\"$") { - format!("echo {} > /dev/null 2>&1", translated) - } else { translated }; + let translated = (matches!(self.value, Some(StatementType::Expr(_))) || translated.starts_with('$') || translated.starts_with("\"$")) + .then(|| format!("echo {} > /dev/null 2>&1", translated)) + .unwrap_or_else(|| translated); // Get all the required supplemental statements let indentation = meta.gen_indent(); let statements = meta.stmt_queue.drain(..).map(|st| indentation.clone() + &st + ";\n").join(""); diff --git a/src/rules.rs b/src/rules.rs index 1a9206f9..ba564e73 100644 --- a/src/rules.rs +++ b/src/rules.rs @@ -4,7 +4,7 @@ pub fn get_rules() -> Rules { let symbols = vec![ '+', '-', '*', '/', '%', '\n', ';', ':', '(', ')', '[', ']', '{', '}', ',', '.', - '<', '>', '=' + '<', '>', '=', '!' ]; let compounds = vec![ ('<', '='), @@ -17,7 +17,8 @@ pub fn get_rules() -> Rules { ('*', '='), ('/', '='), ('%', '='), - ('.', '.') + ('.', '.'), + ('/', '/') ]; let region = reg![ reg!(string as "string literal" => { @@ -40,8 +41,12 @@ pub fn get_rules() -> Rules { tokenize: true } ref global) ]), + reg!(cc_flag as "compiler flag" => { + begin: "#[", + end: "]" + }), reg!(comment as "comment" => { - begin: "#", + begin: "//", end: "\n", allow_left_open: true }) diff --git a/src/tests/validity.rs b/src/tests/validity.rs index fe36a367..54ab6d28 100644 --- a/src/tests/validity.rs +++ b/src/tests/validity.rs @@ -127,8 +127,8 @@ fn command_inception() { #[test] fn comment() { let code = " - # this is a comment - let a = 42 # this is a comment as well + // this is a comment + let a = 42 // this is a comment as well "; test_amber!(code, ""); } @@ -370,7 +370,7 @@ fn modulo_shorthand() { fn function() { let code = " fun test() { - ret 'Hello World' + return 'Hello World' } echo test() "; @@ -381,7 +381,7 @@ fn function() { fn function_with_args() { let code = " fun test(a, b) { - ret '{a} {b}' + return '{a} {b}' } echo test('Hello', 'World') "; @@ -392,7 +392,7 @@ fn function_with_args() { fn function_with_args_different_types() { let code = " fun test(a, b) { - ret a + b + return a + b } echo test('Hello', 'World') echo test(11, 42) @@ -404,7 +404,7 @@ fn function_with_args_different_types() { fn function_with_typed_args() { let code = " fun test(a: Num, b: Num) { - ret a + b + return a + b } echo test(11, 42) "; diff --git a/src/utils/cc_flags.rs b/src/utils/cc_flags.rs new file mode 100644 index 00000000..86ae5a3a --- /dev/null +++ b/src/utils/cc_flags.rs @@ -0,0 +1,26 @@ +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub enum CCFlags { + AllowNestedIfElse, + AllowGenericReturn, + AllowAbsurdCast, + UndefinedFlag +} + +pub fn get_ccflag_by_name(flag: &str) -> CCFlags { + match flag { + "allow_nested_if_else" => CCFlags::AllowNestedIfElse, + "allow_generic_return" => CCFlags::AllowGenericReturn, + "allow_absurd_cast" => CCFlags::AllowAbsurdCast, + _ => CCFlags::UndefinedFlag + } +} + +#[allow(dead_code)] +pub fn get_ccflag_name(flag: CCFlags) -> &'static str { + match flag { + CCFlags::AllowNestedIfElse => "allow_nested_if_else", + CCFlags::AllowGenericReturn => "allow_generic_return", + CCFlags::AllowAbsurdCast => "allow_absurd_cast", + CCFlags::UndefinedFlag => "undefined_flag" + } +} \ No newline at end of file diff --git a/src/utils/context.rs b/src/utils/context.rs index f2b970e7..c5d880f8 100644 --- a/src/utils/context.rs +++ b/src/utils/context.rs @@ -1,8 +1,8 @@ use heraclitus_compiler::prelude::*; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use crate::modules::types::Type; -use super::function_interface::FunctionInterface; +use super::{function_interface::FunctionInterface, cc_flags::CCFlags}; #[derive(Clone, Debug)] pub struct FunctionDecl { @@ -108,7 +108,9 @@ pub struct Context { /// This is a list of ids of all the public functions in the file pub pub_funs: Vec, /// The return type of the currently parsed function - pub fun_ret_type: Option + pub fun_ret_type: Option, + /// List of compiler flags + pub cc_flags: HashSet } // FIXME: Move the scope related structures to the separate file @@ -123,7 +125,8 @@ impl Context { is_fun_ctx: false, is_loop_ctx: false, pub_funs: vec![], - fun_ret_type: None + fun_ret_type: None, + cc_flags: HashSet::new() } } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index cb7dcba5..93c4e42d 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -3,4 +3,5 @@ pub mod context; pub mod function_cache; pub mod import_cache; pub mod function_interface; +pub mod cc_flags; pub use metadata::*; \ No newline at end of file diff --git a/test_files/is_even.ab b/test_files/is_even.ab index 50e5e0cc..5a4cee5c 100644 --- a/test_files/is_even.ab +++ b/test_files/is_even.ab @@ -1,5 +1,5 @@ pub import * from 'str/trim.ab'; pub fun is_even(n: Num) { - ret n % 2 == 0 then trim(' even ') else 'odd' + return n % 2 == 0 then trim(' even ') else 'odd' } \ No newline at end of file diff --git a/test_files/str/trim.ab b/test_files/str/trim.ab index 38bae6f8..c0a03865 100644 --- a/test_files/str/trim.ab +++ b/test_files/str/trim.ab @@ -1,5 +1,5 @@ pub fun trim(text) { - ret '$\{text##*( )}' + return '$\{text##*( )}' } main {