diff --git a/Cargo.lock b/Cargo.lock index 8dae1b2a..2eec961f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,6 +15,7 @@ dependencies = [ name = "amber" version = "0.3.5-alpha" dependencies = [ + "amber-meta", "assert_cmd", "build-helper", "chrono", @@ -30,6 +31,15 @@ dependencies = [ "tiny_http", ] +[[package]] +name = "amber-meta" +version = "0.1.0" +dependencies = [ + "proc-macro2 1.0.86", + "quote 1.0.36", + "syn 2.0.68", +] + [[package]] name = "android-tzdata" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index 5f74ad74..d797237c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,21 +8,22 @@ rust-version = "1.79" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -heraclitus-compiler = "1.8.0" -similar-string = "1.4.2" +amber-meta = { path = "meta" } +chrono = "0.4.38" +clap = { version = "4.4.18", features = ["derive"] } colored = "2.0.0" +heraclitus-compiler = "1.8.0" +include_dir = "0.7.4" itertools = "0.13.0" -clap = { version = "4.4.18", features = ["derive"] } -chrono = "0.4.38" +similar-string = "1.4.2" test-generator = "0.3.1" -include_dir = "0.7.4" # test dependencies [dev-dependencies] -tiny_http = "0.12.0" assert_cmd = "2.0.14" predicates = "3.1.0" tempfile = "3.10.1" +tiny_http = "0.12.0" [profile.release] strip = true @@ -37,6 +38,9 @@ lto = "thin" [profile.test] opt-level = 3 +[workspace] +members = ["meta"] + # Config for 'cargo dist' [workspace.metadata.dist] # The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax) diff --git a/meta/Cargo.toml b/meta/Cargo.toml new file mode 100644 index 00000000..77de81ae --- /dev/null +++ b/meta/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "amber-meta" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0.86" +quote = "1.0.36" +syn = { version = "2.0.68", features = ["visit"] } diff --git a/meta/src/helper.rs b/meta/src/helper.rs new file mode 100644 index 00000000..7e2342c0 --- /dev/null +++ b/meta/src/helper.rs @@ -0,0 +1,48 @@ +use crate::utils; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::visit::Visit; +use syn::{Field, Ident, PathSegment}; + +pub struct HelperVisitor { + name: Ident, + functions: Vec, +} + +impl HelperVisitor { + pub fn new(name: &Ident) -> Self { + Self { + name: name.clone(), + functions: Vec::new(), + } + } + + fn make_function(name: &Ident, segment: &PathSegment) -> TokenStream2 { + let concat = format!("set_{}", name); + let concat = Ident::new(&concat, name.span()); + quote! { + /// Sets the field value and returns the previous value. + pub fn #concat(&mut self, mut #name: #segment) -> #segment { + use std::mem::swap; + swap(&mut self.#name, &mut #name); + #name + } + } + } + + pub fn make_block(&self) -> TokenStream2 { + utils::make_block(&self.name, &self.functions) + } +} + +impl<'a> Visit<'a> for HelperVisitor { + fn visit_field(&mut self, field: &'a Field) { + if field.attrs.iter().any(utils::is_context) { + if let Some(name) = &field.ident { + if let Some(segment) = utils::get_type(field) { + self.functions.push(Self::make_function(name, segment)); + } + } + } + } +} diff --git a/meta/src/lib.rs b/meta/src/lib.rs new file mode 100644 index 00000000..9aff9711 --- /dev/null +++ b/meta/src/lib.rs @@ -0,0 +1,104 @@ +mod helper; +mod manager; +mod utils; + +use crate::helper::HelperVisitor; +use crate::manager::ManagerVisitor; +use proc_macro::TokenStream; +use syn::visit::Visit; +use syn::*; + +/// Derive macro `ContextManager` allows changes to be made to annotated +/// fields on a struct, with automatic reset on early error return. +/// +/// In this example, we change some settings on an object, and rely on +/// the context manager to reset those settings when it fails. The macro +/// creates three functions for each annotated field in the `Amplifier` +/// struct, and we call the following ones here: +/// +/// * Function `Amplifier::with_panel_ref()` swaps the existing `panel` +/// field on the `Amplifier` object, passes the `Amplifier` object to +/// the lambda by mutable reference, swaps the old `panel` field on +/// exit, and returns the result. +/// +/// * Function `Amplifier::with_power()` sets the `power` field on the +/// `Amplifier` object, and resets the old value on exit. Requires +/// the field being modified to implement the `Copy` and `Clone` traits. +/// +/// * Function `Amplifier::with_panel_fn()` sets the `volume` field on +/// the encapsulated `Panel` object, by calling its setter function +/// `Panel::set_volume()`, and resets the old value on exit. Note, +/// the setter function is created by derive macro `ContextHelper`. +/// +/// ```rust +/// use amber_meta::{ContextHelper, ContextManager}; +/// +/// #[derive(ContextManager)] +/// struct Amplifier { +/// #[context] +/// power: bool, +/// input: f64, +/// output: f64, +/// #[context] +/// panel: Panel, +/// } +/// +/// #[derive(ContextHelper)] +/// struct Panel { +/// #[context] +/// volume: u8, +/// display: Option, +/// } +/// +/// impl Panel { +/// fn new() -> Panel { +/// Panel { volume: 0, display: None } +/// } +/// } +/// +/// fn demo_amplifier(amp: &mut Amplifier) -> Result<(), String> { +/// // Install a new control panel. +/// let mut panel = Panel::new(); +/// amp.with_panel_ref(&mut panel, |amp| { +/// // Turn the power on. +/// amp.with_power(true, |amp| { +/// // Set the volume to 11. +/// amp.with_panel_fn(Panel::set_volume, 11, |amp| { +/// // Strum a guitar chord. +/// play_guitar(amp)?; +/// Ok(()) +/// })?; +/// // Reset the volume on exit. +/// Ok(()) +/// })?; +/// // Turn the power off on exit. +/// Ok(()) +/// })?; +/// // Reinstall the old control panel on exit. +/// Ok(()) +/// } +/// +/// fn play_guitar(amp: &Amplifier) -> Result<(), String> { +/// Err(String::from("Blown fuse")) +/// } +/// ``` +#[proc_macro_derive(ContextManager, attributes(context))] +pub fn context_manager(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as ItemStruct); + let mut visitor = ManagerVisitor::new(&input.ident); + visitor.visit_item_struct(&input); + let output = visitor.make_block(); + TokenStream::from(output) +} + +/// Derive macro `ContextHelper` provides support functions for use with +/// context functions created by `ContextManager`; for more information, +/// see documentation for that macro. +#[proc_macro_derive(ContextHelper, attributes(context))] +pub fn context_helper(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as ItemStruct); + let mut visitor = HelperVisitor::new(&input.ident); + visitor.visit_item_struct(&input); + let output = visitor.make_block(); + TokenStream::from(output) +} diff --git a/meta/src/manager.rs b/meta/src/manager.rs new file mode 100644 index 00000000..24a17379 --- /dev/null +++ b/meta/src/manager.rs @@ -0,0 +1,103 @@ +use crate::utils; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::visit::Visit; +use syn::{Field, Ident, PathSegment}; + +pub struct ManagerVisitor { + name: Ident, + functions: Vec, +} + +impl ManagerVisitor { + pub fn new(name: &Ident) -> Self { + Self { + name: name.clone(), + functions: Vec::new(), + } + } + + fn make_with(name: &Ident, segment: &PathSegment) -> TokenStream2 { + let concat = format!("with_{}", name); + let concat = Ident::new(&concat, name.span()); + quote! { + /// Sets the field value (which must implement the `Copy` and + /// `Clone` traits) and restores the previous value after the + /// body function has returned. + pub fn #concat(&mut self, #name: #segment, mut body: B) -> Result + where + B: FnMut(&mut Self) -> Result, + { + // Native types are implicitly copied on clone. + let prev = self.#name.clone(); + self.#name = #name; + let result = body(self); + self.#name = prev; + result + } + } + } + + fn make_with_ref(name: &Ident, segment: &PathSegment) -> TokenStream2 { + let concat = format!("with_{}_ref", name); + let concat = Ident::new(&concat, name.span()); + quote! { + /// Sets the field value by swapping the references, and + /// restores the previous value after the body function has + /// returned. + pub fn #concat(&mut self, #name: &mut #segment, mut body: B) -> Result + where + B: FnMut(&mut Self) -> Result, + { + use std::mem::swap; + swap(&mut self.#name, #name); + let result = body(self); + swap(&mut self.#name, #name); + result + } + } + } + + fn make_with_fn(name: &Ident, segment: &PathSegment) -> TokenStream2 { + let concat = format!("with_{}_fn", name); + let concat = Ident::new(&concat, name.span()); + quote! { + /// Sets the field value on the encapsulated struct using + /// its member function, and restores the previous value + /// after the body function has returned. + /// + /// Additionally, to add setter functions designed to work + /// with `with_foo_fn()`, annotate the encapsulated struct + /// with `#[derive(ContextHelper)`, and required fields with + /// `#[context]`. + pub fn #concat(&mut self, mut setter: S, value: V, mut body: B) -> Result + where + S: FnMut(&mut #segment, V) -> V, + B: FnMut(&mut Self) -> Result, + { + let prev = setter(&mut self.#name, value); + let result = body(self); + setter(&mut self.#name, prev); + result + } + } + } + + pub fn make_block(&self) -> TokenStream2 { + utils::make_block(&self.name, &self.functions) + } +} + +impl<'a> Visit<'a> for ManagerVisitor { + fn visit_field(&mut self, field: &'a Field) { + if field.attrs.iter().any(utils::is_context) { + if let Some(name) = &field.ident { + if let Some(segment) = utils::get_type(field) { + self.functions.push(Self::make_with(name, segment)); + self.functions.push(Self::make_with_ref(name, segment)); + self.functions.push(Self::make_with_fn(name, segment)); + } + } + } + } +} diff --git a/meta/src/utils.rs b/meta/src/utils.rs new file mode 100644 index 00000000..46fda097 --- /dev/null +++ b/meta/src/utils.rs @@ -0,0 +1,38 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::{Attribute, Field, Ident, Meta, PathSegment, Type}; + +/// Implements multiple functions for a struct implementation block. +pub fn make_block(name: &Ident, functions: &Vec) -> TokenStream2 { + // See [https://users.rust-lang.org/t/how-to-use-a-vector-of-tokenstreams-created-with-quote-within-quote/81092]. + quote! { + impl #name { + #(#functions)* + } + } +} + +/// Tests whether a given field attribute is `#[context]`, for both +/// `#[derive(ContextManager)]` and `#[derive(ContextHelper)]` enhanced +/// structs. +pub fn is_context(attr: &Attribute) -> bool { + if let Meta::Path(path) = &attr.meta { + if let Some(segment) = path.segments.last() { + if segment.ident == "context" { + return true; + } + } + } + false +} + +/// Gets the type of a given field. Note, we use the `PathSegment` not +/// the contained `Ident`, because that supports generic field types +/// like `Option`. +pub fn get_type(field: &Field) -> Option<&PathSegment> { + if let Type::Path(path) = &field.ty { + path.path.segments.last() + } else { + None + } +} diff --git a/src/modules/block.rs b/src/modules/block.rs index fb7c42c7..64df4c3a 100644 --- a/src/modules/block.rs +++ b/src/modules/block.rs @@ -35,28 +35,28 @@ impl SyntaxModule for Block { } fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { - meta.push_scope(); - while let Some(token) = meta.get_current_token() { - // Handle the end of line or command - if ["\n", ";"].contains(&token.word.as_str()) { - meta.increment_index(); - continue; - } - // Handle block end - else if token.word == "}" { - 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)) + meta.with_push_scope(|meta| { + while let Some(token) = meta.get_current_token() { + // Handle the end of line or command + if ["\n", ";"].contains(&token.word.as_str()) { + meta.increment_index(); + continue; + } + // Handle block end + else if token.word == "}" { + 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); } - self.statements.push(statement); - } - meta.pop_scope(); - Ok(()) + Ok(()) + }) } } diff --git a/src/modules/formatter.rs b/src/modules/formatter.rs index 8871c38f..a966ede1 100644 --- a/src/modules/formatter.rs +++ b/src/modules/formatter.rs @@ -7,7 +7,7 @@ use std::{io::{BufWriter, Write}, process::{Command, Stdio}}; #[derive(Debug, Clone, Copy)] #[allow(non_camel_case_types)] pub enum BashFormatter { - /// https://github.com/mvdan/sh + /// shfmt } @@ -71,4 +71,4 @@ impl BashFormatter { } } } -} \ No newline at end of file +} diff --git a/src/modules/function/declaration.rs b/src/modules/function/declaration.rs index f23d81c6..b0fbc321 100644 --- a/src/modules/function/declaration.rs +++ b/src/modules/function/declaration.rs @@ -1,5 +1,4 @@ use std::collections::HashSet; -use std::mem::swap; use heraclitus_compiler::prelude::*; use itertools::izip; @@ -9,6 +8,7 @@ use crate::modules::expression::expr::Expr; use crate::modules::types::{Type, Typed}; use crate::modules::variable::variable_name_extensions; use crate::utils::cc_flags::get_ccflag_by_name; +use crate::utils::context::Context; use crate::utils::function_cache::FunctionInstance; use crate::utils::function_interface::FunctionInterface; use crate::utils::metadata::{ParserMetadata, TranslateMetadata}; @@ -148,95 +148,94 @@ impl SyntaxModule for FunctionDeclaration { let mut optional = false; context!({ // Set the compiler flags - swap(&mut meta.context.cc_flags, &mut flags); - // Get the arguments - token(meta, "(")?; - loop { - if token(meta, ")").is_ok() { - break + meta.with_context_fn(Context::set_cc_flags, flags, |meta| { + // Get the arguments + token(meta, "(")?; + loop { + if token(meta, ")").is_ok() { + break + } + let is_ref = token(meta, "ref").is_ok(); + let name_token = meta.get_current_token(); + let name = variable(meta, variable_name_extensions())?; + // 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); + } + } + if let Type::Failable(_) = arg_type { + return error!(meta, name_token, "Failable types cannot be used as arguments"); + } + 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 arg_type != Type::Generic && arg_type != expr.get_type() { + return error!(meta, name_token, "Optional argument does not match annotated type"); + } + self.arg_optionals.push(expr); + }, + Err(_) => { + if optional { + return error!(meta, name_token, "All arguments following an optional argument must also be optional"); + } + }, + } + match token(meta, ")") { + Ok(_) => break, + Err(_) => token(meta, ",")? + }; } - let is_ref = token(meta, "ref").is_ok(); - let name_token = meta.get_current_token(); - let name = variable(meta, variable_name_extensions())?; - // Optionally parse the argument type - let mut arg_type = Type::Generic; + let mut returns_tok = None; + // Optionally parse the return type 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()); + returns_tok = meta.get_current_token(); + self.returns = parse_type(meta)? }, - Err(_) => { - self.arg_refs.push(is_ref); - self.arg_names.push(name.clone()); - self.arg_types.push(Type::Generic); - } + Err(_) => self.returns = Type::Generic } - if let Type::Failable(_) = arg_type { - return error!(meta, name_token, "Failable types cannot be used as arguments"); + // Parse the body + token(meta, "{")?; + let (index_begin, index_end, is_failable) = skip_function_body(meta); + if is_failable && !matches!(self.returns, Type::Failable(_) | Type::Generic) { + return error!(meta, returns_tok, "Failable functions must return a Failable type"); + } else if !is_failable && matches!(self.returns, Type::Failable(_)) { + return error!(meta, returns_tok, "Non-failable functions cannot return a Failable type"); } - 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 arg_type != Type::Generic && arg_type != expr.get_type() { - return error!(meta, name_token, "Optional argument does not match annotated type"); - } - self.arg_optionals.push(expr); - }, - Err(_) => { - if optional { - return error!(meta, name_token, "All arguments following an optional argument must also be optional"); - } - }, - } - - match token(meta, ")") { - Ok(_) => break, - Err(_) => token(meta, ",")? - }; - } - let mut returns_tok = None; - // Optionally parse the return type - match token(meta, ":") { - Ok(_) => { - returns_tok = meta.get_current_token(); - self.returns = parse_type(meta)? - }, - Err(_) => self.returns = Type::Generic - } - // Parse the body - token(meta, "{")?; - let (index_begin, index_end, is_failable) = skip_function_body(meta); - if is_failable && !matches!(self.returns, Type::Failable(_) | Type::Generic) { - return error!(meta, returns_tok, "Failable functions must return a Failable type"); - } else if !is_failable && matches!(self.returns, Type::Failable(_)) { - return error!(meta, returns_tok, "Non-failable functions cannot return a Failable type"); - } - // 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); - 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, 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)?; - // Restore the compiler flags - swap(&mut meta.context.cc_flags, &mut flags); + // 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); + 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(()) }, |pos| { error_pos!(meta, pos, format!("Failed to parse function declaration '{}'", self.name)) diff --git a/src/modules/function/invocation_utils.rs b/src/modules/function/invocation_utils.rs index a23ccaf9..769ccce4 100644 --- a/src/modules/function/invocation_utils.rs +++ b/src/modules/function/invocation_utils.rs @@ -1,10 +1,9 @@ -use std::mem::swap; use itertools::izip; use heraclitus_compiler::prelude::*; use similar_string::find_best_similarity; +use crate::modules::block::Block; use crate::modules::types::Type; use crate::utils::ParserMetadata; -use crate::modules::block::Block; use crate::utils::context::FunctionDecl; // Convert a number to an ordinal number @@ -47,33 +46,28 @@ fn run_function_with_args(meta: &mut ParserMetadata, mut fun: FunctionDecl, args } } } - let mut ctx = meta.fun_cache.get_context(fun.id).unwrap().clone(); + let mut context = meta.fun_cache.get_context(fun.id).unwrap().clone(); let mut block = Block::new(); - let mut binop_border = None; // Swap the contexts to use the function context - swap(&mut ctx, &mut meta.context); - // Swap the binop border to clear it - swap(&mut binop_border, &mut meta.binop_border); - // Create a sub context for new variables - meta.push_scope(); - for (kind, name, is_ref) in izip!(args, &fun.arg_names, &fun.arg_refs) { - meta.add_param(name, kind.clone(), *is_ref); - } - // Set the expected return type if specified - if fun.returns != Type::Generic { - meta.context.fun_ret_type = Some(fun.returns.clone()); - } - // Parse the function body - syntax(meta, &mut block)?; - // Pop function body - meta.pop_scope(); - // Restore old context - swap(&mut ctx, &mut meta.context); - // Restore old binop border - swap(&mut binop_border, &mut meta.binop_border); + 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); + } + // Set the expected return type if specified + if fun.returns != Type::Generic { + meta.context.fun_ret_type = Some(fun.returns.clone()); + } + // Parse the function body + syntax(meta, &mut block)?; + Ok(()) + })?; + Ok(()) + })?; // Set the new return type or null if nothing was returned if let Type::Generic = fun.returns { - fun.returns = ctx.fun_ret_type.clone().unwrap_or_else(|| Type::Null); + fun.returns = context.fun_ret_type.clone().unwrap_or_else(|| Type::Null); }; // Set the new argument types fun.arg_types = args.to_vec(); diff --git a/src/modules/imports/import.rs b/src/modules/imports/import.rs index 92e0e3c1..e318f325 100644 --- a/src/modules/imports/import.rs +++ b/src/modules/imports/import.rs @@ -1,5 +1,4 @@ use std::fs; -use std::mem::swap; use heraclitus_compiler::prelude::*; use crate::compiler::AmberCompiler; use crate::docs::module::DocumentationModule; @@ -103,17 +102,16 @@ impl Import { let mut block = Block::new(); // Save snapshot of current file let position = PositionInfo::from_token(meta, self.token_import.clone()); - let mut ctx = Context::new(Some(self.path.value.clone()), tokens) + let mut context = Context::new(Some(self.path.value.clone()), tokens) .file_import(&meta.context.trace, position); - swap(&mut ctx, &mut meta.context); - // Parse imported code - syntax(meta, &mut block)?; - // Restore snapshot of current file - swap(&mut ctx, &mut meta.context); + meta.with_context_ref(&mut context, |meta| { + // Parse imported code + syntax(meta, &mut block) + })?; // Persist compiled file to cache - meta.import_cache.add_import_metadata(Some(self.path.value.clone()), block, ctx.pub_funs.clone()); + meta.import_cache.add_import_metadata(Some(self.path.value.clone()), block, context.pub_funs.clone()); // Handle exports (add to current file) - self.handle_export(meta, ctx.pub_funs)?; + self.handle_export(meta, context.pub_funs)?; Ok(()) } Err(err) => Err(Failure::Loud(err)) diff --git a/src/modules/loops/infinite_loop.rs b/src/modules/loops/infinite_loop.rs index eea5c893..9f040651 100644 --- a/src/modules/loops/infinite_loop.rs +++ b/src/modules/loops/infinite_loop.rs @@ -1,8 +1,7 @@ -use std::mem::swap; - use heraclitus_compiler::prelude::*; use crate::docs::module::DocumentationModule; use crate::translate::module::TranslateModule; +use crate::utils::context::Context; use crate::utils::metadata::{ParserMetadata, TranslateMetadata}; use crate::modules::block::Block; @@ -24,23 +23,24 @@ impl SyntaxModule for InfiniteLoop { token(meta, "loop")?; token(meta, "{")?; // Save loop context state and set it to true - let mut new_is_loop_ctx = true; - swap(&mut new_is_loop_ctx, &mut meta.context.is_loop_ctx); - // Parse loop - syntax(meta, &mut self.block)?; - token(meta, "}")?; - // Restore loop context state - swap(&mut new_is_loop_ctx, &mut meta.context.is_loop_ctx); + meta.with_context_fn(Context::set_is_loop_ctx, true, |meta| { + // Parse loop + syntax(meta, &mut self.block)?; + token(meta, "}")?; + Ok(()) + })?; Ok(()) } } impl TranslateModule for InfiniteLoop { fn translate(&self, meta: &mut TranslateMetadata) -> String { - ["while :".to_string(), + [ + "while :".to_string(), "do".to_string(), self.block.translate(meta), - "done".to_string()].join("\n") + "done".to_string(), + ].join("\n") } } diff --git a/src/modules/loops/iter_loop.rs b/src/modules/loops/iter_loop.rs index f333d572..bbb3f73c 100644 --- a/src/modules/loops/iter_loop.rs +++ b/src/modules/loops/iter_loop.rs @@ -1,11 +1,10 @@ -use std::mem::swap; - use heraclitus_compiler::prelude::*; use crate::docs::module::DocumentationModule; use crate::modules::expression::expr::Expr; use crate::modules::types::{Typed, Type}; use crate::modules::variable::variable_name_extensions; use crate::translate::module::TranslateModule; +use crate::utils::context::Context; use crate::utils::metadata::{ParserMetadata, TranslateMetadata}; use crate::modules::block::Block; @@ -49,20 +48,20 @@ impl SyntaxModule for IterLoop { }; token(meta, "{")?; // Create iterator variable - meta.push_scope(); - meta.add_var(&self.iter_name, self.iter_type.clone()); - if let Some(index) = self.iter_index.as_ref() { - meta.add_var(index, Type::Num); - } - // Save loop context state and set it to true - let mut new_is_loop_ctx = true; - swap(&mut new_is_loop_ctx, &mut meta.context.is_loop_ctx); - // Parse loop - syntax(meta, &mut self.block)?; - token(meta, "}")?; - // Restore loop context state - swap(&mut new_is_loop_ctx, &mut meta.context.is_loop_ctx); - meta.pop_scope(); + meta.with_push_scope(|meta| { + meta.add_var(&self.iter_name, self.iter_type.clone()); + if let Some(index) = self.iter_index.as_ref() { + meta.add_var(index, Type::Num); + } + // 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)?; + token(meta, "}")?; + Ok(()) + })?; + Ok(()) + })?; Ok(()) }, |pos| { error_pos!(meta, pos, "Syntax error in loop") diff --git a/src/modules/main.rs b/src/modules/main.rs index f69251a9..2f95d7cc 100644 --- a/src/modules/main.rs +++ b/src/modules/main.rs @@ -44,15 +44,15 @@ impl SyntaxModule for Main { } token(meta, "{")?; // Create a new scope for variables - meta.push_scope(); - // Create variables - for arg in self.args.iter() { - meta.add_var(arg, Type::Array(Box::new(Type::Text))); - } - // Parse the block - syntax(meta, &mut self.block)?; - // Remove the scope made for variables - meta.pop_scope(); + meta.with_push_scope(|meta| { + // Create variables + for arg in self.args.iter() { + meta.add_var(arg, Type::Array(Box::new(Type::Text))); + } + // Parse the block + syntax(meta, &mut self.block)?; + Ok(()) + })?; token(meta, "}")?; meta.context.is_main_ctx = false; Ok(()) diff --git a/src/utils/context.rs b/src/utils/context.rs index d08e1ec7..0988ab14 100644 --- a/src/utils/context.rs +++ b/src/utils/context.rs @@ -1,10 +1,10 @@ +use super::{cc_flags::CCFlags, function_interface::FunctionInterface}; use crate::modules::expression::expr::Expr; use crate::modules::types::Type; +use amber_meta::ContextHelper; use heraclitus_compiler::prelude::*; use std::collections::{HashMap, HashSet}; -use super::{cc_flags::CCFlags, function_interface::FunctionInterface}; - #[derive(Clone, Debug)] pub struct FunctionDecl { pub name: String, @@ -94,7 +94,7 @@ impl ScopeUnit { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, ContextHelper)] pub struct Context { /// The current index in the expression pub index: usize, @@ -109,6 +109,7 @@ pub struct Context { /// Determines if the context is in a function pub is_fun_ctx: bool, /// Determines if the context is in a loop + #[context] pub is_loop_ctx: bool, /// Determines if the context is in the main block pub is_main_ctx: bool, @@ -119,6 +120,7 @@ pub struct Context { /// The return type of the currently parsed function pub fun_ret_type: Option, /// List of compiler flags + #[context] pub cc_flags: HashSet, } diff --git a/src/utils/metadata/parser.rs b/src/utils/metadata/parser.rs index 180489c5..9a41f4a0 100644 --- a/src/utils/metadata/parser.rs +++ b/src/utils/metadata/parser.rs @@ -1,6 +1,7 @@ use std::collections::BTreeSet; use heraclitus_compiler::prelude::*; +use amber_meta::ContextManager; use crate::modules::block::Block; use crate::modules::types::Type; use crate::utils::context::{Context, ScopeUnit, VariableDecl, FunctionDecl}; @@ -8,12 +9,10 @@ use crate::utils::function_interface::FunctionInterface; use crate::utils::import_cache::ImportCache; use crate::utils::function_cache::FunctionCache; -#[derive(Debug)] +#[derive(Debug, ContextManager)] pub struct ParserMetadata { /// Code if the parser is in eval mode pub eval_code: Option, - /// Determines where the binary operator should end - pub binop_border: Option, /// Used for debugging by Heraclitus pub debug: Option, /// Cache of already imported modules @@ -25,6 +24,7 @@ pub struct ParserMetadata { /// Global variable id pub var_id: usize, /// Context of the parser + #[context] pub context: Context, /// List of all failure messages pub messages: Vec, @@ -48,13 +48,14 @@ impl ParserMetadata { } /// Pushes a new scope to the stack - pub fn push_scope(&mut self) { - self.context.scopes.push(ScopeUnit::new()) - } - - /// Pops the last scope from the stack - pub fn pop_scope(&mut self) -> Option { - self.context.scopes.pop() + pub fn with_push_scope(&mut self, mut body: B) -> SyntaxResult + where + B: FnMut(&mut Self) -> SyntaxResult + { + self.context.scopes.push(ScopeUnit::new()); + let result = body(self); + self.context.scopes.pop(); + result } /* Variables */ @@ -162,7 +163,6 @@ impl Metadata for ParserMetadata { fn new(tokens: Vec, path: Option, code: Option) -> Self { ParserMetadata { eval_code: code, - binop_border: None, debug: None, import_cache: ImportCache::new(path.clone()), fun_cache: FunctionCache::new(), @@ -174,8 +174,8 @@ impl Metadata for ParserMetadata { } } - fn get_trace(&self) -> Vec { - self.context.trace.clone() + fn get_token_at(&self, index: usize) -> Option { + self.context.expr.get(index).cloned() } fn get_index(&self) -> usize { @@ -186,10 +186,6 @@ impl Metadata for ParserMetadata { self.context.index = index } - fn get_token_at(&self, index: usize) -> Option { - self.context.expr.get(index).cloned() - } - fn get_debug(&mut self) -> Option { self.debug } @@ -198,11 +194,15 @@ impl Metadata for ParserMetadata { self.debug = Some(indent) } + fn get_path(&self) -> Option { + self.context.path.clone() + } + fn get_code(&self) -> Option<&String> { self.eval_code.as_ref() } - fn get_path(&self) -> Option { - self.context.path.clone() + fn get_trace(&self) -> Vec { + self.context.trace.clone() } }