diff --git a/src/Rust/vvs_parser/src/ast/punctuated.rs b/src/Rust/vvs_parser/src/ast/punctuated.rs index cb070f68f3c502458ad97a7220b3e112f553c865..f1e40f7bdf7bb89d30bb2c0249b3646cdbf9788a 100644 --- a/src/Rust/vvs_parser/src/ast/punctuated.rs +++ b/src/Rust/vvs_parser/src/ast/punctuated.rs @@ -128,6 +128,18 @@ impl<T> Punctuated<T> { self.pairs.first() } + /// Returns the first value in the sequence + /// ```rust + /// # use vvs_parser::prelude::ast::*; + /// let mut punctuated = Punctuated::new(); + /// assert_eq!(punctuated.first(), None); + /// punctuated.push(Pair::new(1, None)); + /// assert_eq!(punctuated.first_value(), Some(&1)); + /// ``` + pub fn first_value(&self) -> Option<&T> { + self.pairs.first().map(Pair::value) + } + /// Returns the last pair in the sequence /// ```rust /// # use vvs_parser::prelude::ast::*; @@ -139,6 +151,17 @@ impl<T> Punctuated<T> { self.pairs.last() } + /// Returns the last value in the sequence + /// ```rust + /// # use vvs_parser::prelude::ast::*; + /// let mut punctuated = Punctuated::new(); + /// punctuated.push(Pair::new(1, None)); + /// assert_eq!(punctuated.last_value(), Some(&1)); + /// ``` + pub fn last_value(&self) -> Option<&T> { + self.pairs.last().map(Pair::value) + } + /// Returns an iterator over pairs as references /// ```rust /// # use vvs_parser::prelude::ast::*; diff --git a/src/Rust/vvs_parser/src/vivy/frontend_pipeline.rs b/src/Rust/vvs_parser/src/vivy/frontend_pipeline.rs index 53bc5d77bf182921be794636546e8a6e68e3b9b3..ee70bd1e09c30a09d61a3e7998793e817b9730f7 100644 --- a/src/Rust/vvs_parser/src/vivy/frontend_pipeline.rs +++ b/src/Rust/vvs_parser/src/vivy/frontend_pipeline.rs @@ -18,6 +18,7 @@ use crate::{ use derive_more::Display; use hashbrown::{HashMap, HashSet}; use std::{borrow::Cow, cell::RefCell, fmt, fs, mem, path::Path, rc::Rc}; +use symbol_table::SymbolTableLocked; /// Process a program. pub struct FrontendPipeline<'a> { @@ -31,7 +32,7 @@ pub struct FrontendPipeline<'a> { imports: HashMap<ShortString, Rc<(Ast, Cow<'static, str>)>>, /// List of imported symbol tables. - symbols: Vec<Rc<SymbolTable<'a, TypeInfo>>>, + symbols: Vec<Rc<SymbolTable<'a, TypeInfo, SymbolTableLocked>>>, /// The search path. search: &'a SearchPath, @@ -280,8 +281,8 @@ impl<'a> FrontendPipeline<'a> { let _ = ErrorReport::new().add_errors(self.name(), &self.program, err).report(); })?; - let (ast, StmtCheckerResult { decl_opts, setd_opts, main, imported }) = - StmtChecker::new(ast, (self.name().as_str(), self.program.as_ref())) + let StmtCheckerResult { decl_opts, setd_opts, main, imported } = + StmtChecker::new(&ast, (self.name().as_str(), self.program.as_ref())) .is_main(self.is_main) .process()?; let ImportAllResult { mut declared_options, unused } = self.import_all(imported)?; @@ -292,13 +293,18 @@ impl<'a> FrontendPipeline<'a> { .map(|(name, value)| (self.name().clone(), name, value.into())), ); - let symbols = SymbolTable::new(&ast, self.options.as_deref()) - .import(self.symbols.iter().map(Rc::as_ref)) - .take(); + let symbols = SymbolTable::try_from(&ast) + .map_err(|err| { + let _ = ErrorReport::new() + .add_errors(self.name(), &self.program, [err]) + .report(); + })? + .import(self.symbols.iter().map(Rc::as_ref)); let loc = (self.name().as_str(), self.program.as_ref()); - let (ast, _) = TypeCollector::new(ast, loc, &mut self.library.borrow_mut()).process()?; - let (ast, _) = TypeChecker::new(ast, loc, &self.library.borrow(), symbols.sub_scope()).process()?; + TypeCollector::new(&ast, loc, &mut self.library.borrow_mut()).process()?; + TypeChecker::new(&ast, loc, &self.library.borrow(), symbols).process()?; + let (ast, OptsTransformResult { unused }) = match self.options.clone() { None => (ast, Default::default()), Some(options) => { diff --git a/src/Rust/vvs_parser/src/vivy/library.rs b/src/Rust/vvs_parser/src/vivy/library.rs index 95d15e5fda3e38fd8201408661bce30db20f1e4e..bb8466a340b9bb827287588b926b9271fdf5cb96 100644 --- a/src/Rust/vvs_parser/src/vivy/library.rs +++ b/src/Rust/vvs_parser/src/vivy/library.rs @@ -1,7 +1,7 @@ //! Store informations about methods and fields for types. use crate::{ast::TypeInfo, ShortString}; -use derive_more::{Display, IntoIterator, IsVariant}; +use derive_more::{IntoIterator, IsVariant}; use hashbrown::HashMap; use std::{fmt, sync::LazyLock}; diff --git a/src/Rust/vvs_parser/src/vivy/passes/mod.rs b/src/Rust/vvs_parser/src/vivy/passes/mod.rs index 6c6f8e65044f33f3832a102c727ceadebf32045d..b101e45a4e91089a54927ea4643d3d83e8507b87 100644 --- a/src/Rust/vvs_parser/src/vivy/passes/mod.rs +++ b/src/Rust/vvs_parser/src/vivy/passes/mod.rs @@ -5,8 +5,6 @@ mod stmt_checker; mod type_checker; mod type_collector; -pub use self::{opts_transform::*, stmt_checker::*, type_checker::*, type_collector::*}; - /// Macro used to declare a pass, can be a transform pass or a visit pass (for the role). /// ```ignore /// declare_pass! { @@ -17,21 +15,100 @@ pub use self::{opts_transform::*, stmt_checker::*, type_checker::*, type_collect /// }; /// impl VisitorMut for OptsTransform {} /// ``` -#[macro_export] macro_rules! declare_pass { - ( - $(#[$meta: meta])* - $role: ident :: $name: ident : $lifetime: lifetime { + ($(#[$meta: meta])* $role: ident :: $name: ident : $lifetime: lifetime { $($field_attr: ident : $field_ty: ty),* $(,)? } + $(aux { $($info_attr: ident : $info_ty: ty),+$(,)? })? + $(opt { $($opts_attr: ident : $opts_ty: ty),+$(,)? })? + $(result { $($res_attr: ident : $res_ty: ty),+$(,)? })? + ) => { + $crate::vivy::passes::declare_pass! { @struct-decl $(#[$meta])* $role :: $name : $lifetime + { $( $field_attr : $field_ty),* } + { $($($info_attr : $info_ty ),+)? } + { $($($opts_attr : $opts_ty ),+)? } + { $($($res_attr : $res_ty ),+)? } + } + + impl <$lifetime> $name <$lifetime> { + $crate::vivy::passes::declare_pass! { @constructor $role :: $name : $lifetime + { $( $field_attr : $field_ty),* } + { $($($info_attr : $info_ty ),+)? } + { $($($opts_attr : $opts_ty ),+)? } + { $($($res_attr : $res_ty ),+)? } + } + + /// Add a new ast error to the error list. + #[allow(dead_code)] + fn error(&mut self, token: impl Into<$crate::tokenizer::TokenReference>, error: impl Into<std::borrow::Cow<'static, str>>) { + self.report.add_errors(self.name, self.source, [$crate::ast::AstError::from_parts(token.into(), error)]); + } + + /// Add a new ast warning to the error list. + #[allow(dead_code)] + fn warning(&mut self, token: impl Into<$crate::tokenizer::TokenReference>, error: impl Into<std::borrow::Cow<'static, str>>) { + self.report.add_warnings(self.name, self.source, [$crate::ast::AstError::from_parts(token.into(), error)]); + } - $(aux_infos { $($info_attr: ident : $info_ty: ty),+$(,)? })? - $(opt_infos { $($opts_attr: ident : $opts_ty: ty),+$(,)? })? - $(results { $($res_attr: ident : $res_ty: ty),+$(,)? })? + paste::paste! { + $($( + #[doc = "Set the optional information '" $opts_attr "' for the [" $name "] pass."] + #[allow(clippy::wrong_self_convention)] + pub fn $opts_attr(self, $opts_attr: $opts_ty) -> Self { + Self { $opts_attr: Some($opts_attr), ..self } + } + )+)? + + #[doc = "Apply the [" $name "] pass and get the resulting [vvs_parser::ast::Ast] or the list of the errors."] + #[allow(unused_imports, unused_variables)] + pub fn process(mut self) -> $crate::vivy::passes::declare_pass!(@pass-returns $role :: $name) { + use $crate::visitors::{Visitor as _, VisitorMut as _}; + let ast = self.program.take().unwrap(); + let ast = self.visit_ast(ast); + (!self.report.report()).then_some(()).ok_or(())?; + $crate::vivy::passes::declare_pass!(@returns $role :: $name (ast, [< $name Result >] { + $($($res_attr: self.$res_attr),+)? + })) + } + } + } + }; + + + (@constructor $role: ident :: $name: ident : $lifetime: lifetime + { $($field_attr: ident : $field_ty: ty),* } + { $($info_attr: ident : $info_ty: ty),* } + { $($opts_attr: ident : $opts_ty: ty),* } + { $($res_attr: ident : $res_ty: ty),* } + ) => { + paste::paste! { + #[doc = "Create a new [" $name "] pass."] + pub fn new( + program: $crate::vivy::passes::declare_pass!(@decl-program $role $lifetime), + (name, source): (&$lifetime str, &$lifetime str) + $(, $info_attr: $info_ty)* + ) -> Self { + Self { + program: Some(program), + report: Default::default(), + name, source, $($info_attr,)* + $($field_attr: Default::default(),)* + $($opts_attr: None,)* + $($res_attr: Default::default(),)* + } + } + } + }; + + (@struct-decl $(#[$meta: meta])* $role: ident :: $name: ident : $lifetime: lifetime + { $( $field_attr: ident : $field_ty: ty),*$(,)? } + { $($($info_attr: ident : $info_ty: ty),+$(,)?)? } + { $($($opts_attr: ident : $opts_ty: ty),+$(,)?)? } + { $($($res_attr: ident : $res_ty: ty),+$(,)?)? } ) => { $(#[$meta])* pub struct $name <$lifetime> { - program: Option<$crate::ast::Ast>, + program: Option<$crate::vivy::passes::declare_pass!(@decl-program $role $lifetime)>, name: &$lifetime str, source: &$lifetime str, report: $crate::vivy::error_report::ErrorReport<$lifetime>, @@ -49,80 +126,24 @@ macro_rules! declare_pass { } } - impl <$lifetime> $name <$lifetime> { - paste::paste! { - #[doc = "Create a new [" $name "] pass."] - pub fn new( - program: $crate::ast::Ast, - (name, source): (&$lifetime str, &$lifetime str) - $($(, $info_attr: $info_ty)+)? - ) -> Self { - Self { - program: Some(program), name, source, - report: $crate::vivy::error_report::ErrorReport::new(), - $($($info_attr),+,)? - $( $field_attr: Default::default(),)* - $($($opts_attr: None),+,)? - $($($res_attr: Default::default()),+,)? - } - } - - /// Add a new ast error to the error list. - #[allow(dead_code)] - fn error(&mut self, token: impl Into<$crate::tokenizer::TokenReference>, error: impl Into<std::borrow::Cow<'static, str>>) { - self.report.add_errors( - self.name, - self.source, - [$crate::Error::AstError($crate::ast::AstError::from_parts(token.into(), error))] - ); - } - - /// Add a new ast warning to the error list. - #[allow(dead_code)] - fn warning(&mut self, token: impl Into<$crate::tokenizer::TokenReference>, error: impl Into<std::borrow::Cow<'static, str>>) { - self.report.add_warnings( - self.name, - self.source, - [$crate::Error::AstError($crate::ast::AstError::from_parts(token.into(), error))] - ); - } - - $($( - #[doc = "Set the optional information '" $opts_attr "' for the [" $name "] pass."] - #[allow(clippy::wrong_self_convention)] - pub fn $opts_attr(self, $opts_attr: $opts_ty) -> Self { - Self { $opts_attr: Some($opts_attr), ..self } - } - )+)? - - #[doc = "Apply the [" $name "] pass and get the resulting [vvs_parser::ast::Ast] or the list of the errors."] - pub fn process(mut self) -> Result<($crate::ast::Ast, [< $name Result >]), ()> { - let ast = self.program.take().unwrap(); - let ast = $crate::declare_pass!(@apply_on_ast $role (self, ast)); - match self.report.report() { - true => Err(()), - false => Ok((ast, [< $name Result >] { - $($($res_attr: self.$res_attr),+)? - })), - } - } - } - } }; - (@apply_on_ast transform ($self: ident, $ast: expr)) => {{ - use $crate::visitors::VisitorMut; - $self.visit_ast($ast) - }}; + (@pass-returns visit :: $name: ident) => { paste::paste! { Result<[< $name Result >], ()> } }; + (@pass-returns transform :: $name: ident) => { paste::paste! { Result<($crate::ast::Ast, [< $name Result >]), ()> } }; - (@apply_on_ast visit ($self: ident, $ast: expr)) => {{ - use $crate::visitors::Visitor; - $self.visit_ast(&$ast); - $ast - }}; + (@returns visit :: $name: ident ($ast: expr, $result: expr)) => { Ok(($result)) }; + (@returns transform :: $name: ident ($ast: expr, $result: expr)) => { Ok((($ast, $result))) }; + + (@decl-program visit $lifetime: lifetime) => { &$lifetime $crate::ast::Ast }; + (@decl-program transform $lifetime: lifetime) => { $crate::ast::Ast }; } +use declare_pass; + +pub use self::{opts_transform::*, stmt_checker::*, type_checker::*, type_collector::*}; -/// Trait used to provide a way for users to create their own passes +/// Trait used to provide a way for users to create their own passes. +/// +/// We don't want to expose the [declare_pass] macro for simplicity sake. pub trait UserFrontendPass { /// Get the name of the pass. For logging purposes. fn name(&self) -> &'static str; diff --git a/src/Rust/vvs_parser/src/vivy/passes/opts_transform.rs b/src/Rust/vvs_parser/src/vivy/passes/opts_transform.rs index 6c60caf8b37f6b40b1aa3d9b881199b8f9ca5e8e..d6976e49b2803a4a1c2e42aa39e4530137b6d3ac 100644 --- a/src/Rust/vvs_parser/src/vivy/passes/opts_transform.rs +++ b/src/Rust/vvs_parser/src/vivy/passes/opts_transform.rs @@ -1,13 +1,13 @@ -use crate::{ast::*, node::Node, tokenizer::*, ShortString}; +use crate::{ast::*, node::Node, tokenizer::*, vivy::passes::declare_pass, ShortString}; -crate::declare_pass! { +declare_pass! { /// The option optimization is here to substitute the options with the values chosen by the /// user. #[allow(dead_code)] transform::OptsTransform: 'a {} - aux_infos { options: &'a OptionTable } - results { unused: Vec<(ShortString, ShortString)> } + aux { options: &'a OptionTable } + result { unused: Vec<(ShortString, ShortString)> } } impl crate::visitors::VisitorMut for OptsTransform<'_> { diff --git a/src/Rust/vvs_parser/src/vivy/passes/stmt_checker.rs b/src/Rust/vvs_parser/src/vivy/passes/stmt_checker.rs index 7dbb413a4eeb258749e0d7fb77d369e8a6f546f3..0bb444e7dab648e2f7e1ee2a2a192d4502125e01 100644 --- a/src/Rust/vvs_parser/src/vivy/passes/stmt_checker.rs +++ b/src/Rust/vvs_parser/src/vivy/passes/stmt_checker.rs @@ -3,18 +3,21 @@ use crate::{ node::Node, tokenizer::{Token, TokenKind, TokenReference, TokenType}, visitors::Visit, - vivy::main_program::{MainProgram, MainProgramCallListItem, MainProgramStep}, + vivy::{ + main_program::{MainProgram, MainProgramCallListItem, MainProgramStep}, + passes::declare_pass, + }, ShortString, }; -crate::declare_pass! { +declare_pass! { /// The statement checker is here to verify that statements are not used in incorrect positions. visit::StmtChecker: 'a { in_job: bool, } - opt_infos { is_main: bool } - results { + opt { is_main: bool } + result { imported: Vec<TokenReference>, setd_opts: Vec<(ShortString, ShortString, Expression)>, decl_opts: Vec<(ShortString, OptionDefaultValue)>, diff --git a/src/Rust/vvs_parser/src/vivy/passes/type_checker.rs b/src/Rust/vvs_parser/src/vivy/passes/type_checker.rs index 712253f5417f5617ee3684b2ef2418dcaa666607..71556dbb4f257f7e5479e2af43560d27e189c17f 100644 --- a/src/Rust/vvs_parser/src/vivy/passes/type_checker.rs +++ b/src/Rust/vvs_parser/src/vivy/passes/type_checker.rs @@ -3,22 +3,25 @@ use crate::{ node::Node, tokenizer::*, traits::DefaultRef, - vivy::{library::*, symbol_table::SymbolTable}, + vivy::{library::*, passes::declare_pass, symbol_table::*}, }; -crate::declare_pass! { +declare_pass! { /// The type checker is here to verify type corectness of the program and infer types, etc. To /// check the types we will need to do custom things... + /// + /// TODO: Verify unique functions at top level, with correct names visit::TypeChecker: 'a {} - aux_infos { + aux { library: &'a Library, - symbol_table: SymbolTable<'a, TypeInfo>, + symbols: SymbolTable<'a, TypeInfo, SymbolTableLocked>, } } impl crate::visitors::Visitor for TypeChecker<'_> { fn visit_ast(&mut self, ast: &Ast) { + log::error!("todo: verify unique functions at top level, with correct names"); for error in Vec::from_iter(ast.nodes().stmts().flat_map(|stmt| match stmt { Stmt::JobDeclaration(decl) => self.type_compute().callable(decl.body(), true).err(), Stmt::FunctionDeclaration(decl) => self.type_compute().callable(decl.body(), false).err(), @@ -28,8 +31,7 @@ impl crate::visitors::Visitor for TypeChecker<'_> { Stmt::TypeDeclaration(_) => unimplemented!("custom types are tbd"), _ => unreachable!("already checked that they are not here"), })) { - self.report - .add_errors(self.name, self.source, [crate::Error::AstError(*error)]); + self.report.add_errors(self.name, self.source, [*error]); } if !ast.eof().is_kind(TokenKind::Eof) { self.error(ast.eof(), "expected eof token here"); @@ -39,21 +41,21 @@ impl crate::visitors::Visitor for TypeChecker<'_> { impl TypeChecker<'_> { fn type_compute(&self) -> TypeCompute { - TypeCompute::new(self.library, self.symbol_table.sub_scope()) + TypeCompute::new(self.library, self.symbols.sub_scope()) } } /// The thing that will actually compute types and returns errors in case of problems. struct TypeCompute<'a> { library: &'a Library, - symbols: SymbolTable<'a, TypeInfo>, + symbols: SymbolTable<'a, TypeInfo, SymbolTableMutable>, in_loop: bool, in_job: bool, returns: &'a TypeInfo, } type TypeComputeError = Box<AstError>; -type TypeComputeResult<'a> = Result<(TypeInfo, SymbolTable<'a, TypeInfo>), TypeComputeError>; +type TypeComputeResult<'a> = Result<(TypeInfo, SymbolTable<'a, TypeInfo, SymbolTableMutable>), TypeComputeError>; macro_rules! box_error { ($token: expr, $format: literal $(,)?) => { Box::new(AstError::from_parts($token.into(), format!($format ))) }; @@ -65,12 +67,12 @@ fn unsupported<T>(token: &TokenReference, whats: &str) -> Result<T, TypeComputeE } impl<'a> TypeCompute<'a> { - fn new(library: &'a Library, symbols: SymbolTable<'a, TypeInfo>) -> Self { + fn new(library: &'a Library, symbols: SymbolTable<'a, TypeInfo, SymbolTableMutable>) -> Self { Self { library, symbols, returns: TypeInfo::default_ref(), in_loop: false, in_job: false } } fn sub(&'a self) -> Self { - Self { symbols: self.symbols.sub_scope(), ..*self } + Self { symbols: self.symbols.clone(), ..*self } } fn set(mut self, symbol: &TokenReference, ty: TypeInfo) -> Result<Self, TypeComputeError> { @@ -155,7 +157,7 @@ impl<'a> TypeCompute<'a> { let mut arguments = args.iter().enumerate().zip(arguments.iter()); match arguments.find_map(|((idx, arg), type_info)| { self.sub() - .expression_as_type(type_info, arg) + .expr_as_type(type_info, arg) .is_err() .then(|| (idx, arg.tokens().next().unwrap(), type_info)) }) { @@ -166,9 +168,9 @@ impl<'a> TypeCompute<'a> { } } - fn expression(self, expression: &'a Expression) -> TypeComputeResult<'a> { + fn expr(self, expression: &'a Expression) -> TypeComputeResult<'a> { match expression { - Expression::Parentheses { expression, .. } => self.expression(expression), + Expression::Parentheses { expression, .. } => self.expr(expression), Expression::FunctionCall(call) => self.suffixes(call.prefix(), call.suffixes()), Expression::Number(_) => Ok((TypeInfo::number().clone(), self.symbols)), @@ -194,7 +196,7 @@ impl<'a> TypeCompute<'a> { Expression::UnaryOperator { unop, expression } => { use {TypeInfo::*, UnOp::*}; - let (ty, symbols) = self.expression(expression)?; + let (ty, symbols) = self.expr(expression)?; let is_container = matches!(ty, Array { .. } | Basic(_) | Optional { .. } | Table { .. } | Tuple { .. }); match unop { @@ -208,8 +210,8 @@ impl<'a> TypeCompute<'a> { Expression::BinaryOperator { lhs, binop, rhs } => { use BinOp::*; - let ty = self.sub().expression(lhs)?.0; - self.sub().expression_as_type(&ty, rhs)?; + let ty = self.sub().expr(lhs)?.0; + self.sub().expr_as_type(&ty, rhs)?; match binop { TwoEqual(_) | TildeEqual(_) => Ok((TypeInfo::number().clone(), self.symbols)), TwoDots(_) if ty == *TypeInfo::string() => Ok((TypeInfo::number().clone(), self.symbols)), @@ -238,7 +240,7 @@ impl<'a> TypeCompute<'a> { fn suffixes(self, prefix: &'a Prefix, suffixes: impl Iterator<Item = &'a Suffix> + 'a) -> TypeComputeResult<'a> { let prefix = match prefix { - Prefix::Expression(expression) => self.sub().expression(expression)?.0, + Prefix::Expression(expression) => self.sub().expr(expression)?.0, Prefix::Name(token) => self .symbols .get(token.token_type().as_str()) @@ -264,7 +266,7 @@ impl<'a> TypeCompute<'a> { }, Suffix::Index(Index::Brackets { expression, .. }) => self .sub() - .expression_as_type(TypeInfo::number(), expression) + .expr_as_type(TypeInfo::number(), expression) .map(|_| *type_info), _ => unreachable!(), }, @@ -356,8 +358,8 @@ impl<'a> TypeCompute<'a> { Ok((ty, self.symbols)) } - fn expression_as_type(self, as_ty: &TypeInfo, expression: &'a Expression) -> Result<(), TypeComputeError> { - let (ty, _) = self.sub().expression(expression)?; + fn expr_as_type(self, as_ty: &TypeInfo, expression: &'a Expression) -> Result<(), TypeComputeError> { + let (ty, _) = self.sub().expr(expression)?; (ty == *as_ty).then_some(()).ok_or_else(|| { box_error!(expression.tokens().next().unwrap(), "the expression should be of type '{as_ty}', got: '{ty}'",) }) @@ -370,22 +372,20 @@ impl<'a> TypeCompute<'a> { .zip(assignment.expressions()); let variables = variables.map(|((name, ty), val)| { let ty = match ty { - None => self.sub().expression(val)?.0, + None => self.sub().expr(val)?.0, Some(ty) => { - self.sub().expression_as_type(ty, val)?; + self.sub().expr_as_type(ty, val)?; ty.clone() } }; Ok::<_, TypeComputeError>((name.token_type().as_str().into(), ty)) }); - let symbols = self - .symbols + self.symbols .extend(variables.collect::<Result<Vec<_>, TypeComputeError>>()?) .map_err(|variable| { box_error!(assignment.local_token(), "can't declare {variable} because it shadows a global variable",) - })? - .take(); - Ok((TypeInfo::default(), symbols)) + })?; + Ok((TypeInfo::default(), self.symbols)) } fn compound_assignment(self, assignment: &'a CompoundAssignment) -> TypeComputeResult<'a> { @@ -404,11 +404,11 @@ impl<'a> TypeCompute<'a> { match variable { Var::Expression(_) => todo!(), Var::Name(variable) => match variable.token_type() { - TokenType::Identifier { identifier } if self.symbols.root().get(identifier).is_some() => { + TokenType::Identifier { identifier } if self.symbols.is_global(identifier) => { Err(box_error!(variable, "try to assign a global variable")) } TokenType::Identifier { identifier } => match self.symbols.get(identifier).cloned() { - Some(ty) => self.expression_as_type(&ty, expression), + Some(ty) => self.expr_as_type(&ty, expression), None => Err(box_error!(variable, "try to assign an undefined variable")), }, _ => unreachable!(), @@ -426,10 +426,10 @@ impl<'a> TypeCompute<'a> { Stmt::Do(block) => self.block(block.block()), Stmt::If(if_stmt) => { - self.sub().expression_as_type(TypeInfo::number(), if_stmt.condition())?; + self.sub().expr_as_type(TypeInfo::number(), if_stmt.condition())?; self.sub().block(if_stmt.block())?; for elseif in if_stmt.else_if().into_iter().flatten() { - self.sub().expression_as_type(TypeInfo::number(), elseif.condition())?; + self.sub().expr_as_type(TypeInfo::number(), elseif.condition())?; self.sub().block(elseif.block())?; } if let Some(block) = if_stmt.else_block() { @@ -442,22 +442,21 @@ impl<'a> TypeCompute<'a> { let symbols = self.sub().block(repeat_stmt.block())?.1; TypeCompute { symbols, ..self } .in_loop() - .expression_as_type(TypeInfo::number(), repeat_stmt.until())?; + .expr_as_type(TypeInfo::number(), repeat_stmt.until())?; Ok((TypeInfo::default(), self.symbols)) } Stmt::While(while_stmt) => { - self.sub() - .expression_as_type(TypeInfo::number(), while_stmt.condition())?; + self.sub().expr_as_type(TypeInfo::number(), while_stmt.condition())?; self.sub().in_loop().block(while_stmt.block())?; Ok((TypeInfo::default(), self.symbols)) } Stmt::NumericFor(for_stmt) => { - self.sub().expression_as_type(TypeInfo::number(), for_stmt.start())?; - self.sub().expression_as_type(TypeInfo::number(), for_stmt.end())?; + self.sub().expr_as_type(TypeInfo::number(), for_stmt.start())?; + self.sub().expr_as_type(TypeInfo::number(), for_stmt.end())?; if let Some(step) = for_stmt.step() { - self.sub().expression_as_type(TypeInfo::number(), step)?; + self.sub().expr_as_type(TypeInfo::number(), step)?; } self.sub() .set(for_stmt.index_variable(), TypeInfo::number().clone())? @@ -492,7 +491,7 @@ impl<'a> TypeCompute<'a> { unsupported(returns.token(), "tuples to return multiple things") } LastStmt::Return(returns) if returns.returns().len() == 1 => (self.returns != TypeInfo::default_ref()) - .then(|| self.expression(returns.returns().iter().next().unwrap())) + .then(|| self.expr(returns.returns().iter().next().unwrap())) .ok_or_else(|| { box_error!(returns.token(), "function or job returns something, but this return statement is empty") })?, @@ -510,7 +509,7 @@ impl<'a> TypeCompute<'a> { } fn block(self, block: &'a Block) -> TypeComputeResult<'a> { - let symbols = block.stmts().try_fold(self.symbols.sub_scope(), |symbols, stmt| { + let symbols = block.stmts().try_fold(self.symbols.clone(), |symbols, stmt| { Ok::<_, TypeComputeError>(TypeCompute { symbols, ..self }.stmt(stmt)?.1) })?; Ok(match block.last_stmt() { diff --git a/src/Rust/vvs_parser/src/vivy/passes/type_collector.rs b/src/Rust/vvs_parser/src/vivy/passes/type_collector.rs index e24536a60e14a80d3169e6c8eec9ac588402cb25..09435236789998e289e799d9feac1e4a933ac37a 100644 --- a/src/Rust/vvs_parser/src/vivy/passes/type_collector.rs +++ b/src/Rust/vvs_parser/src/vivy/passes/type_collector.rs @@ -1,11 +1,11 @@ -use crate::vivy::library::*; +use crate::vivy::{library::*, passes::declare_pass}; -crate::declare_pass! { +declare_pass! { /// Collector of custom type, to register them in the library. #[allow(dead_code)] visit::TypeCollector: 'a {} - aux_infos { library: &'a mut Library } + aux { library: &'a mut Library } } impl crate::visitors::Visitor for TypeCollector<'_> {} diff --git a/src/Rust/vvs_parser/src/vivy/symbol_table.rs b/src/Rust/vvs_parser/src/vivy/symbol_table.rs index 156e6036de0283dee5bee35afef324f90e506459..72203c7cfe96b6b3d365a4a9ae20d24812660704 100644 --- a/src/Rust/vvs_parser/src/vivy/symbol_table.rs +++ b/src/Rust/vvs_parser/src/vivy/symbol_table.rs @@ -1,42 +1,80 @@ -use crate::{traits::DefaultRef, ShortString}; +use crate::{ + ast::*, + tokenizer::{Symbol, Token, TokenReference, TokenType}, + traits::DefaultRef, + ShortString, +}; use hashbrown::HashMap; -use std::ops; +use std::{ + marker, + ops::{self, Deref}, +}; + +#[derive(Debug, Clone)] +pub struct SymbolTableLocked; + +#[derive(Debug, Clone)] +pub struct SymbolTableMutable; /// The symbol table stores the available symbols with theyr type or whatever. -pub struct SymbolTable<'a, T> { - /// A possible parent for this scope. The invariant is: either [SymbolTable::parent] or - /// [SymbolTable::imported] can be non-empty/null. - parent: Option<&'a SymbolTable<'a, T>>, +#[derive(Clone)] +pub struct SymbolTable<'a, T: Clone, State> { + /// A possible parent for this scope. + parent: Option<&'a SymbolTable<'a, T, SymbolTableLocked>>, - /// Imported scopes. The invariant is: either [SymbolTable::parent] or [SymbolTable::imported] - /// can be non-empty/null. - imported: Vec<&'a SymbolTable<'a, T>>, + /// Imported scopes. + imported: Vec<&'a SymbolTable<'a, T, SymbolTableLocked>>, /// List of symbols from this scope. symbols: HashMap<ShortString, T>, + + /// The state of the table, can be locked or mutable. + _state: marker::PhantomData<State>, } -impl<T> Default for SymbolTable<'_, T> { +impl<T: Clone, State> Default for SymbolTable<'_, T, State> { fn default() -> Self { - Self { parent: None, symbols: HashMap::new(), imported: vec![] } + Self { parent: None, symbols: HashMap::new(), imported: vec![], _state: marker::PhantomData } } } -impl<'a, T> SymbolTable<'a, T> { +impl<'a, T: Clone> SymbolTable<'a, T, SymbolTableLocked> { /// Create a new sub-scoped symbol table. - pub fn sub_scope(&'a self) -> Self { - Self { parent: Some(self), symbols: Default::default(), imported: vec![] } + pub fn sub_scope(&'a self) -> SymbolTable<'a, T, SymbolTableMutable> { + SymbolTable { parent: Some(self), symbols: Default::default(), imported: vec![], _state: marker::PhantomData } + } + + /// Get the root scope. + pub fn root(&self) -> &SymbolTable<'a, T, SymbolTableLocked> { + let mut root = self; + while let Some(parent) = root.parent { + root = parent + } + root + } +} + +impl<'a, T: Clone> SymbolTable<'a, T, SymbolTableMutable> { + /// Lock the table. + pub fn lock(self) -> SymbolTable<'a, T, SymbolTableLocked> { + let Self { parent, imported, symbols, .. } = self; + SymbolTable::<'a, T, SymbolTableLocked> { parent, imported, symbols, _state: marker::PhantomData } } /// Add a new variable to the current scope. We override symbols (non-UB shadowing of /// variables). Note that we can't shadow or set variables from the root scope as it denotes /// the global state. In case of error we return the name of the problematic variable. pub fn set(&mut self, symbol: impl Into<ShortString> + AsRef<str>, value: T) -> Result<&mut Self, ShortString> { - if self.root().get(symbol.as_ref()).is_none() { - self.symbols.insert(symbol.into(), value); - Ok(self) - } else { - Err(symbol.into()) + match self.parent { + Some(parent) if parent.root().get(symbol.as_ref()).is_none() => { + self.symbols.insert(symbol.into(), value); + Ok(self) + } + None => { + self.symbols.insert(symbol.into(), value); + Ok(self) + } + Some(_) => Err(symbol.into()), } } @@ -46,38 +84,41 @@ impl<'a, T> SymbolTable<'a, T> { .try_fold(self, |this, (symbol, value)| this.set(symbol, value)) } - /// Imports other scopes into this one. Note that if this scope is not the root one, then we - /// will panic... - pub fn import(&mut self, iter: impl IntoIterator<Item = &'a SymbolTable<'a, T>>) -> &mut Self { - debug_assert!(self.parent.is_none(), "can't have a parent and import another scope"); + /// Imports other scopes into this one. + pub fn import( + mut self, + iter: impl IntoIterator<Item = &'a SymbolTable<'a, T, SymbolTableLocked>>, + ) -> SymbolTable<'a, T, SymbolTableLocked> { self.imported.extend(iter); - self - } - - /// Get the root scope. - pub fn root(&self) -> &Self { - let mut root = self; - while let Some(parent) = root.parent { - root = parent; - } - root + self.lock() } +} +impl<'a, T: Clone, State> SymbolTable<'a, T, State> { /// Get a variable/callback/function from this scope or any parent scope. pub fn get(&self, symbol: impl AsRef<str>) -> Option<&T> { self.symbols .get(symbol.as_ref()) - .or_else(|| self.parent?.get(symbol.as_ref())) .or_else(|| self.imported.iter().find_map(|scope| scope.get(symbol.as_ref()))) + .or_else(|| self.parent?.get(symbol.as_ref())) + } + + /// Tells if a variable/callback/function is present in this scope or any parent scope. + pub fn has(&self, symbol: impl AsRef<str>) -> bool { + self.get(symbol).is_some() } - /// Consume the mutable reference to give back the owned table. - pub(crate) fn take(&mut self) -> Self { - std::mem::take(self) + /// Tells if a symbol is global or not. + pub fn is_global(&self, symbol: impl AsRef<str>) -> bool { + (self.parent.is_none() && self.get(symbol.as_ref()).is_some()) + || self + .parent + .map(SymbolTable::root) + .map_or(false, |root| root.get(symbol).is_some()) } } -impl<T> FromIterator<(ShortString, T)> for SymbolTable<'_, T> { +impl<T: Clone> FromIterator<(ShortString, T)> for SymbolTable<'_, T, SymbolTableMutable> { fn from_iter<I: IntoIterator<Item = (ShortString, T)>>(iter: I) -> Self { let mut table = Self::default(); let _ = table.extend(iter); @@ -85,7 +126,7 @@ impl<T> FromIterator<(ShortString, T)> for SymbolTable<'_, T> { } } -impl<Idx: AsRef<str>, T: DefaultRef + 'static> ops::Index<Idx> for SymbolTable<'_, T> { +impl<Idx: AsRef<str>, T: Clone + DefaultRef + 'static, State> ops::Index<Idx> for SymbolTable<'_, T, State> { type Output = T; /// Same as [SymbolTable::get] but we returns a reference to a default value if the variable was not found. @@ -94,10 +135,77 @@ impl<Idx: AsRef<str>, T: DefaultRef + 'static> ops::Index<Idx> for SymbolTable<' } } -impl<'a> SymbolTable<'a, crate::ast::TypeInfo> { - /// Create a new type symbol table, e.g. a [SymbolTable] where the values are - /// [crate::ast::TypeInfo]. This can be usefull for type checking passes. - pub fn new(ast: &crate::ast::Ast, options: Option<&crate::ast::OptionTable>) -> Self { - todo!() +fn fold_table_on_stmt<'a>( + symbol: SymbolTable<'a, TypeInfo, SymbolTableMutable>, + stmt: &'a Stmt, +) -> Result<SymbolTable<'a, TypeInfo, SymbolTableMutable>, Box<AstError>> { + let handle_decl = |mut symbol: SymbolTable<'a, TypeInfo, SymbolTableMutable>, name: &str, body: &FunctionBody| { + let (arrow, return_type) = body + .return_type() + .map(|type_specifier| (type_specifier.punctuation().clone(), Box::new(type_specifier.type_info().clone()))) + .unwrap_or_else(|| { + let arrow = Token::new(TokenType::Symbol { symbol: Symbol::ThinArrow }); + (TokenReference::new(vec![], arrow, vec![]), Box::new(TypeInfo::default())) + }); + + let arguments = Punctuated::from_iter(body.parameters().pairs().zip(body.type_specifiers()).map( + |(param, ty)| { + let punctuation = ty.map(|ty| ty.punctuation()).cloned().unwrap_or_else(|| { + let token = Token::new(crate::tokenizer::TokenType::Symbol { symbol: Symbol::TwoDots }); + TokenReference::new(vec![], token, vec![]) + }); + let name = Some((param.value().clone().deref().clone(), punctuation)); + let type_info = ty.map(TypeSpecifier::type_info).cloned().unwrap_or_default(); + let type_argument = TypeArgument::new(type_info).with_name(name); + match param.punctuation() { + Some(punctuation) => Pair::Punctuated(type_argument, punctuation.clone()), + None => Pair::End(type_argument), + } + }, + )); + + let parentheses = body.parameters_parentheses().clone(); + let _ = symbol.set(name, TypeInfo::Callback { parentheses, arguments, arrow, return_type }); + Ok(symbol) + }; + + match stmt { + // Handled by the type collector + Stmt::FunctionDeclaration(d) if d.name().method_name().is_some() => Ok(symbol), + + // Function declarations errors + Stmt::FunctionDeclaration(d) if d.name().names().len() != 1 => todo!("error: invalid name"), + Stmt::FunctionDeclaration(d) if matches!(d.name().names().first_value(), Some(n) if symbol.has(n.token_type().as_str())) => + { + todo!("error: multiple definitions") + } + Stmt::LocalFunction(local) if symbol.has(local.name().token_type().as_str()) => { + todo!("error: multiple definitions") + } + + // Function declarations + Stmt::LocalFunction(local) => handle_decl(symbol, local.name().token_type().as_str(), local.body()), + Stmt::FunctionDeclaration(decl) => { + let name = decl.name().names().into_iter().next().unwrap().token_type().as_str(); + handle_decl(symbol, name, decl.body()) + } + + // Options & constants + Stmt::OptionDecl(_) => todo!(), + Stmt::LocalAssignment(_) => todo!("handle constants"), + + // Ignore + _ => Ok(symbol), + } +} + +impl<'a> TryFrom<&'a Ast> for SymbolTable<'a, TypeInfo, SymbolTableMutable> { + type Error = AstError; + + fn try_from(ast: &'a Ast) -> Result<Self, Self::Error> { + ast.nodes() + .stmts() + .try_fold(Self::default(), fold_table_on_stmt) + .map_err(|boxed| *boxed) } }