From 8a1dd1fb90dba4f16632461ca69783ab7c53b4b3 Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Tue, 26 Mar 2024 16:03:00 +0000 Subject: [PATCH] introduce runner trait and merge parser and sem errors --- Cargo.toml | 4 +- crates/diagnostics/src/lib.rs | 6 +-- crates/folidity/src/cmd/check.rs | 25 ++++------- crates/folidity/src/cmd/mod.rs | 17 ++++++++ crates/parser/src/ast.rs | 2 + crates/parser/src/folidity.lalrpop | 2 +- crates/parser/src/lib.rs | 13 +++--- crates/parser/src/tests.rs | 3 ++ crates/semantics/src/ast.rs | 9 +++- crates/semantics/src/bounds.rs | 2 + crates/semantics/src/contract.rs | 21 +++++---- crates/semantics/src/lib.rs | 69 +++++++++++++++++++++--------- crates/semantics/src/tests.rs | 28 ++++++++---- crates/verifier/Cargo.toml | 1 + 14 files changed, 135 insertions(+), 67 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0cf265a..0b75e36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ members = [ "crates/folidity", "crates/parser", "crates/semantics", - "crates/verifier", + "crates/verifier", ] [workspace.package] @@ -23,7 +23,7 @@ version = "1.0.0" folidity-parser = { path = "crates/parser" } folidity-diagnostics = { path = "crates/diagnostics" } folidity-semantics = { path = "crates/semantics" } -folidity-verifier = { path = "crates/semantics" } +folidity-verifier = { path = "crates/verifier" } derive-node = { path = "crates/derive_node" } logos = "0.14" lalrpop-util = "0.20" diff --git a/crates/diagnostics/src/lib.rs b/crates/diagnostics/src/lib.rs index cf86aef..2b8e9f7 100644 --- a/crates/diagnostics/src/lib.rs +++ b/crates/diagnostics/src/lib.rs @@ -14,7 +14,7 @@ pub fn disable_pretty_print() { yansi::disable(); } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum ErrorType { Lexer, Parser, @@ -36,7 +36,7 @@ impl Display for ErrorType { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum Level { Info, Warning, @@ -54,7 +54,7 @@ impl<'a> From for ariadne::ReportKind<'a> { } /// Error report. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Report { /// Location of an error pub loc: Span, diff --git a/crates/folidity/src/cmd/check.rs b/crates/folidity/src/cmd/check.rs index 3bb80b0..73a6d05 100644 --- a/crates/folidity/src/cmd/check.rs +++ b/crates/folidity/src/cmd/check.rs @@ -1,16 +1,13 @@ use anyhow::Result; -use ariadne::{ - Color, - Fmt, -}; use folidity_parser::parse; -use folidity_semantics::resolve_semantics; +use folidity_semantics::ContractDefinition; use std::ffi::OsString; use clap::Args; use super::{ build_report, + exec, read_contract, }; @@ -28,18 +25,12 @@ impl CheckCommand { let parse_result = parse(&contract_contents); match parse_result { Ok(tree) => { - let def = resolve_semantics(&tree); - if def.diagnostics.is_empty() { - println!("{}", "Contract has no known errors".fg(Color::Green)); - Ok(()) - } else { - build_report( - &contract_contents, - &def.diagnostics, - self.contract.to_str().unwrap(), - ); - anyhow::bail!("Syntactical checking failed.") - } + let _ = exec::<_, ContractDefinition>( + &tree, + &contract_contents, + self.contract.to_str().unwrap(), + )?; + Ok(()) } Err(errors) => { build_report(&contract_contents, &errors, self.contract.to_str().unwrap()); diff --git a/crates/folidity/src/cmd/mod.rs b/crates/folidity/src/cmd/mod.rs index b667fbd..52e9a8c 100644 --- a/crates/folidity/src/cmd/mod.rs +++ b/crates/folidity/src/cmd/mod.rs @@ -11,6 +11,10 @@ use anyhow::{ }; use clap::Subcommand; use folidity_diagnostics::Report; +use folidity_semantics::{ + CompilationError, + Runner, +}; use yansi::Paint; use self::{ @@ -72,3 +76,16 @@ pub fn build_report(content: &str, diagnostics: &[Report], file_name: &str) { .unwrap(); } } + +/// Execute the compilation stage using the runner. +pub fn exec>(input: &S, contract_contents: &str, file_name: &str) -> Result { + W::run(input).map_err(|e| { + let reports = e.diagnostics(); + build_report(contract_contents, reports, file_name); + match e { + CompilationError::Syntax(_) => anyhow::anyhow!("Syntactical error occurred"), + CompilationError::Formal(_) => anyhow::anyhow!("Verification failed"), + CompilationError::Emit(_) => anyhow::anyhow!("Compilation failed"), + } + }) +} diff --git a/crates/parser/src/ast.rs b/crates/parser/src/ast.rs index 2a2b930..82be9bf 100644 --- a/crates/parser/src/ast.rs +++ b/crates/parser/src/ast.rs @@ -1,9 +1,11 @@ use super::Span; use derive_node::Node; +use folidity_diagnostics::Report; #[derive(Clone, Debug, PartialEq)] pub struct Source { pub declarations: Vec, + pub diagnostics: Vec, } #[derive(Clone, Debug, PartialEq, Node, Default)] diff --git a/crates/parser/src/folidity.lalrpop b/crates/parser/src/folidity.lalrpop index a42251f..aa1e972 100644 --- a/crates/parser/src/folidity.lalrpop +++ b/crates/parser/src/folidity.lalrpop @@ -7,7 +7,7 @@ use lalrpop_util::ParseError; grammar<'input, 'err>(errors: &'err mut Vec, LexicalError>>); pub FolidityTree: ast::Source = { - Declaration* => ast::Source { declarations: <> } + Declaration* => ast::Source { declarations: <>, diagnostics: vec![] } } Declaration: ast::Declaration = { diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index abbc6d7..01de0ba 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -15,6 +15,9 @@ use lexer::{ }; use std::ops::Range; +#[cfg(test)] +mod tests; + pub type Span = Range; lalrpop_mod!(pub folidity); @@ -44,8 +47,11 @@ pub fn parse(src: &str) -> Result> { reports.push(parser_error_to_report(&e)); Err(reports) } - Ok(_) if !reports.is_empty() => Err(reports), - Ok(tree) => Ok(tree), + // Ok(_) if !reports.is_empty() => Err(reports), + Ok(mut tree) => { + tree.diagnostics.extend(reports); + Ok(tree) + } } } @@ -114,6 +120,3 @@ fn parser_error_to_report(error: &ParseError, LexicalError>) -> ParseError::User { error } => Report::from(error.clone()), } } - -#[cfg(test)] -mod tests; diff --git a/crates/parser/src/tests.rs b/crates/parser/src/tests.rs index 057e697..e7e61f8 100644 --- a/crates/parser/src/tests.rs +++ b/crates/parser/src/tests.rs @@ -182,6 +182,7 @@ fn test_factorial() -> Result<(), String> { fn test_factorial_tree() -> Result<(), String> { let tree = unwrap_tree(FACTORIAL_SRC)?; let parsed = Source { + diagnostics: vec![], declarations: vec![ Declaration::StateDeclaration(Box::new(StateDeclaration { loc: 1..17, @@ -427,6 +428,7 @@ fn () lists() { fn test_lists() -> Result<(), String> { let tree = unwrap_tree(LISTS_SRC)?; let parsed = Source { + diagnostics: vec![], declarations: vec![Declaration::FunDeclaration(Box::new(FunctionDeclaration { loc: 1..148, is_init: false, @@ -583,6 +585,7 @@ fn test_structs_enums() -> Result<(), String> { let parsed = unwrap_tree(STRUCTS_SRC)?; let tree = Source { + diagnostics: vec![], declarations: vec![ Declaration::StructDeclaration(Box::new(StructDeclaration { loc: 1..47, diff --git a/crates/semantics/src/ast.rs b/crates/semantics/src/ast.rs index 493e61f..73713cd 100644 --- a/crates/semantics/src/ast.rs +++ b/crates/semantics/src/ast.rs @@ -253,7 +253,7 @@ pub struct StructDeclaration { pub fields: Vec, } -#[derive(Clone, Debug, PartialEq, Node)] +#[derive(Clone, Debug, Node)] pub struct ModelDeclaration { /// Location span of the model. pub loc: Span, @@ -268,6 +268,8 @@ pub struct ModelDeclaration { pub bounds: Vec, /// Is the parent model recursive. pub recursive_parent: bool, + /// Scope table for the bounds context. + pub scope: Scope, } #[derive(Clone, Debug, PartialEq)] @@ -278,7 +280,7 @@ pub enum StateBody { Model(SymbolInfo), } -#[derive(Clone, Debug, PartialEq, Node)] +#[derive(Clone, Debug, Node)] pub struct StateDeclaration { /// Location span of the model. pub loc: Span, @@ -293,6 +295,8 @@ pub struct StateDeclaration { pub bounds: Vec, /// Is the parent state recursive. pub recursive_parent: bool, + /// Scope table for the bounds context. + pub scope: Scope, } #[derive(Clone, Debug, PartialEq, Node)] @@ -520,6 +524,7 @@ impl TypeVariant { /// Extracts literal value, `T`, from the expression, if possible. pub trait TryGetValue { + #[allow(clippy::result_unit_err)] fn try_get(&self) -> Result; } diff --git a/crates/semantics/src/bounds.rs b/crates/semantics/src/bounds.rs index 06543e5..e1a44e7 100644 --- a/crates/semantics/src/bounds.rs +++ b/crates/semantics/src/bounds.rs @@ -62,6 +62,7 @@ pub fn resolve_bounds(contract: &mut ContractDefinition, delay: &DelayedDeclarat continue; }; + contract.models[model_delay.i].scope = scope; contract.models[model_delay.i].bounds = bounds; } @@ -115,6 +116,7 @@ pub fn resolve_bounds(contract: &mut ContractDefinition, delay: &DelayedDeclarat }; contract.states[state_delay.i].bounds = bounds; + contract.states[state_delay.i].scope = scope; } for func_delay in &delay.functions { diff --git a/crates/semantics/src/contract.rs b/crates/semantics/src/contract.rs index b0da3f9..cc3e2fe 100644 --- a/crates/semantics/src/contract.rs +++ b/crates/semantics/src/contract.rs @@ -11,14 +11,17 @@ use folidity_parser::{ }; use indexmap::IndexMap; -use crate::ast::{ - EnumDeclaration, - Function, - ModelDeclaration, - Param, - StateBody, - StateDeclaration, - StructDeclaration, +use crate::{ + ast::{ + EnumDeclaration, + Function, + ModelDeclaration, + Param, + StateBody, + StateDeclaration, + StructDeclaration, + }, + symtable::Scope, }; use crate::{ @@ -309,6 +312,7 @@ impl ContractDefinition { parent: None, bounds: Vec::new(), recursive_parent: false, + scope: Scope::default(), }); delay @@ -339,6 +343,7 @@ impl ContractDefinition { from: None, bounds: Vec::new(), recursive_parent: false, + scope: Scope::default(), }); delay diff --git a/crates/semantics/src/lib.rs b/crates/semantics/src/lib.rs index 1c42a13..3719131 100644 --- a/crates/semantics/src/lib.rs +++ b/crates/semantics/src/lib.rs @@ -1,10 +1,11 @@ use bounds::resolve_bounds; -use contract::ContractDefinition; +pub use contract::ContractDefinition; +use folidity_diagnostics::Report; use folidity_parser::ast::Source; use functions::resolve_func_body; use types::check_inheritance; -mod ast; +pub mod ast; mod bounds; mod contract; mod expression; @@ -17,29 +18,55 @@ mod types; #[cfg(test)] mod tests; -/// Resolves the contract's parsed tree into the semantically analysed and typed-checked -/// definition. -/// -/// # Errors -/// [`ContractDefinition`] may contain errors stored in the `diagnostics` field. -pub fn resolve_semantics(source: &Source) -> ContractDefinition { - let mut definition = ContractDefinition::default(); - let mut delay = definition.resolve_declarations(source); - definition.resolve_fields(&delay); +#[derive(Debug, Clone)] +pub enum CompilationError { + Syntax(Vec), + Formal(Vec), + Emit(Vec), +} - check_inheritance(&mut definition, &delay); +impl CompilationError { + pub fn diagnostics(&self) -> &Vec { + match self { + CompilationError::Syntax(r) => r, + CompilationError::Formal(r) => r, + CompilationError::Emit(r) => r, + } + } +} - // todo: add built-in function to environment. +/// Recursively walk the tree and modify the program artifacts. +pub trait Runner { + fn run(source: &S) -> Result + where + Self: std::marker::Sized; +} - // we can now resolve functions and create scopes. - definition.resolve_functions(source, &mut delay); +impl Runner for ContractDefinition { + fn run(source: &Source) -> Result { + let mut definition = ContractDefinition::default(); + definition.diagnostics.extend(source.diagnostics.clone()); + let mut delay = definition.resolve_declarations(source); + definition.resolve_fields(&delay); - // now we can resolve model bounds on all declarations. - resolve_bounds(&mut definition, &delay); + check_inheritance(&mut definition, &delay); - for f in &delay.functions { - let _ = resolve_func_body(&f.decl, f.i, &mut definition); - } + // todo: add built-in function to environment. - definition + // we can now resolve functions and create scopes. + definition.resolve_functions(source, &mut delay); + + // now we can resolve model bounds on all declarations. + resolve_bounds(&mut definition, &delay); + + for f in &delay.functions { + let _ = resolve_func_body(&f.decl, f.i, &mut definition); + } + + if !definition.diagnostics.is_empty() { + return Err(CompilationError::Syntax(definition.diagnostics)); + } + + Ok(definition) + } } diff --git a/crates/semantics/src/tests.rs b/crates/semantics/src/tests.rs index 501b3a5..a38c31b 100644 --- a/crates/semantics/src/tests.rs +++ b/crates/semantics/src/tests.rs @@ -1,7 +1,8 @@ use crate::{ ast::TypeVariant, - resolve_semantics, symtable::VariableSym, + ContractDefinition, + Runner, }; use folidity_parser::parse; @@ -16,9 +17,13 @@ enum MyEnum { B } +model ParentModel { + a: int +} + model MyModel: ParentModel { c: int, - b: string, + b: string } state NoState(MyModel) @@ -28,10 +33,13 @@ state NoState(MyModel) fn test_first_pass() { let tree = parse(DECL_SRC).unwrap(); - let def = resolve_semantics(&tree); + let res = ContractDefinition::run(&tree); + let Ok(def) = res else { + panic!("{:#?}", res.err().unwrap()) + }; assert_eq!(def.structs.len(), 1); assert_eq!(def.enums.len(), 1); - assert_eq!(def.models.len(), 1); + assert_eq!(def.models.len(), 2); assert_eq!(def.states.len(), 1); let e = &def.enums[0]; @@ -139,7 +147,9 @@ fn test_program() { panic!("{:#?}", &result.err().unwrap()); }; - let contract = resolve_semantics(tree); + let res = ContractDefinition::run(tree); + assert!(res.is_ok(), "{:#?}", res.err().unwrap()); + let contract = res.unwrap(); assert_eq!(contract.diagnostics.len(), 0, "{:#?}", contract.diagnostics); assert_eq!(contract.models.len(), 2); assert_eq!(contract.states.len(), 3); @@ -240,15 +250,17 @@ fn () fail_move_state() when () -> (StartState s) { #[test] fn test_err_program() { folidity_diagnostics::disable_pretty_print(); - let result = parse(NOT_WORKING); let Ok(tree) = &result else { panic!("{:#?}", &result.err().unwrap()); }; - let contract = resolve_semantics(tree); + let result = ContractDefinition::run(tree); + let Err(e) = result else { + panic!("The contract is expected to fail") + }; + let mut errors = e.diagnostics().iter().map(|r| r.message.clone()); // assert_eq!(contract.diagnostics.len(), 0, "{:#?}", contract.diagnostics); - let mut errors = contract.diagnostics.iter().map(|r| r.message.clone()); assert_eq!( "Expected function to return a value of type bool", &errors.next().unwrap() diff --git a/crates/verifier/Cargo.toml b/crates/verifier/Cargo.toml index e4c05b2..a4081ca 100644 --- a/crates/verifier/Cargo.toml +++ b/crates/verifier/Cargo.toml @@ -13,3 +13,4 @@ description = "Formal verification library for the Folidity compiler" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +folidity-semantics = { workspace = true } \ No newline at end of file