diff --git a/a.out b/a.out new file mode 100644 index 0000000000..3652d7385f --- /dev/null +++ b/a.out @@ -0,0 +1,13 @@ +; ModuleID = 'main' +source_filename = "main" + +%prg_interface = type { %__prg_my_struct } +%__prg_my_struct = type {} + +@prg_instance = global %prg_interface zeroinitializer + +define void @prg(%prg_interface* %0) { +entry: + %my_struct = getelementptr inbounds %prg_interface, %prg_interface* %0, i32 0, i32 0 + ret void +} diff --git a/examples/simple_program.st b/examples/simple_program.st index 1b6f2a626b..e99d328c29 100644 --- a/examples/simple_program.st +++ b/examples/simple_program.st @@ -1,4 +1,8 @@ -TYPE MyType: - PROGRAM - END_PROGRAM - END_TYPE \ No newline at end of file + TYPE the_struct : STRUCT END_STRUCT END_TYPE + + PROGRAM prg + VAR + my_struct : STRUCT + END_STRUCT + END_VAR + END_PROGRAM \ No newline at end of file diff --git a/src/ast.rs b/src/ast.rs index e7f2b5f812..f04838aa6b 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -2,7 +2,9 @@ use crate::compile_error::CompileError; use std::{ fmt::{Debug, Display, Formatter, Result}, - iter, result, unimplemented, + iter, + ops::Range, + result, unimplemented, }; mod pre_processor; @@ -178,6 +180,7 @@ impl Variable { ) -> DataTypeDeclaration { let new_data_type = DataTypeDeclaration::DataTypeReference { referenced_type: type_name, + location: self.data_type.get_location(), }; std::mem::replace(&mut self.data_type, new_data_type) } @@ -208,6 +211,10 @@ impl SourceRange { pub fn sub_range(&self, start: usize, len: usize) -> SourceRange { SourceRange::new((self.get_start() + start)..(self.get_start() + len)) } + + pub fn to_range(&self) -> Range { + self.range.clone() + } } impl From> for SourceRange { @@ -216,27 +223,67 @@ impl From> for SourceRange { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, PartialEq)] pub enum DataTypeDeclaration { - DataTypeReference { referenced_type: String }, - DataTypeDefinition { data_type: DataType }, + DataTypeReference { + referenced_type: String, + location: SourceRange, + }, + DataTypeDefinition { + data_type: DataType, + location: SourceRange, + }, +} + +impl Debug for DataTypeDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + DataTypeDeclaration::DataTypeReference { + referenced_type, .. + } => f + .debug_struct("DataTypeReference") + .field("referenced_type", referenced_type) + .finish(), + DataTypeDeclaration::DataTypeDefinition { data_type, .. } => f + .debug_struct("DataTypeDefinition") + .field("data_type", data_type) + .finish(), + } + } } impl DataTypeDeclaration { pub fn get_name(&self) -> Option<&str> { match self { - DataTypeDeclaration::DataTypeReference { referenced_type } => { - Some(referenced_type.as_str()) - } - DataTypeDeclaration::DataTypeDefinition { data_type } => data_type.get_name(), + DataTypeDeclaration::DataTypeReference { + referenced_type, .. + } => Some(referenced_type.as_str()), + DataTypeDeclaration::DataTypeDefinition { data_type, .. } => data_type.get_name(), + } + } + + pub fn get_location(&self) -> SourceRange { + match self { + DataTypeDeclaration::DataTypeReference { location, .. } => location.clone(), + DataTypeDeclaration::DataTypeDefinition { location, .. } => location.clone(), } } } -#[derive(PartialEq, Debug)] +#[derive(PartialEq)] pub struct UserTypeDeclaration { pub data_type: DataType, pub initializer: Option, + pub location: SourceRange, +} + +impl Debug for UserTypeDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + f.debug_struct("UserTypeDeclaration") + .field("data_type", &self.data_type) + .field("initializer", &self.initializer) + .finish() + } } #[derive(Clone, PartialEq)] @@ -347,6 +394,7 @@ impl DataType { pub fn replace_data_type_with_reference_to( &mut self, type_name: String, + location: &SourceRange, ) -> Option { if let DataType::ArrayType { referenced_type, .. @@ -357,6 +405,7 @@ impl DataType { } let new_data_type = DataTypeDeclaration::DataTypeReference { referenced_type: type_name, + location: location.clone(), }; let old_data_type = std::mem::replace(referenced_type, Box::new(new_data_type)); Some(*old_data_type) diff --git a/src/ast/pre_processor.rs b/src/ast/pre_processor.rs index bc178215db..421c5c54fe 100644 --- a/src/ast/pre_processor.rs +++ b/src/ast/pre_processor.rs @@ -2,7 +2,10 @@ use crate::ast::UserTypeDeclaration; // Copyright (c) 2020 Ghaith Hachem and Mathias Rieder use std::vec; -use super::super::ast::{CompilationUnit, DataType, DataTypeDeclaration, Variable}; +use super::{ + super::ast::{CompilationUnit, DataType, DataTypeDeclaration, Variable}, + SourceRange, +}; pub fn pre_process(unit: &mut CompilationUnit) { //process all local variables from POUs @@ -53,6 +56,7 @@ fn should_generate_implicit_type(variable: &Variable) -> bool { DataTypeDeclaration::DataTypeReference { .. } => false, DataTypeDeclaration::DataTypeDefinition { data_type: DataType::VarArgs { .. }, + .. } => false, DataTypeDeclaration::DataTypeDefinition { .. } => true, } @@ -64,15 +68,18 @@ fn pre_process_variable_data_type( types: &mut Vec, ) { let new_type_name = format!("__{}_{}", container_name, variable.name); - if let DataTypeDeclaration::DataTypeDefinition { mut data_type } = - variable.replace_data_type_with_reference_to(new_type_name.clone()) + if let DataTypeDeclaration::DataTypeDefinition { + mut data_type, + location, + } = variable.replace_data_type_with_reference_to(new_type_name.clone()) { // create index entry - add_nested_datatypes(new_type_name.as_str(), &mut data_type, types); + add_nested_datatypes(new_type_name.as_str(), &mut data_type, types, &location); data_type.set_name(new_type_name); types.push(UserTypeDeclaration { data_type, initializer: None, + location, }); } //make sure it gets generated @@ -82,16 +89,25 @@ fn add_nested_datatypes( container_name: &str, datatype: &mut DataType, types: &mut Vec, + location: &SourceRange, ) { let new_type_name = format!("{}_", container_name); - if let Some(DataTypeDeclaration::DataTypeDefinition { mut data_type }) = - datatype.replace_data_type_with_reference_to(new_type_name.clone()) + if let Some(DataTypeDeclaration::DataTypeDefinition { + mut data_type, + location: inner_location, + }) = datatype.replace_data_type_with_reference_to(new_type_name.clone(), location) { data_type.set_name(new_type_name.clone()); - add_nested_datatypes(new_type_name.as_str(), &mut data_type, types); + add_nested_datatypes( + new_type_name.as_str(), + &mut data_type, + types, + &inner_location, + ); types.push(UserTypeDeclaration { data_type, initializer: None, + location: location.clone(), }); } } diff --git a/src/index.rs b/src/index.rs index e7c7678042..6ccd730751 100644 --- a/src/index.rs +++ b/src/index.rs @@ -2,7 +2,7 @@ use indexmap::IndexMap; use crate::{ - ast::{Implementation, SourceRange, Statement}, + ast::{Implementation, PouType, SourceRange, Statement}, compile_error::CompileError, typesystem::*, }; @@ -85,10 +85,21 @@ pub enum DataTypeType { AliasType, // a Custom-Alias-dataType } -#[derive(Debug, Clone)] +#[derive(Clone)] +pub enum ImplementationType { + Program, + Function, + FunctionBlock, + Action, + Class, + Method, +} + +#[derive(Clone)] pub struct ImplementationIndexEntry { call_name: String, type_name: String, + implementation_type: ImplementationType, } impl ImplementationIndexEntry { @@ -98,13 +109,31 @@ impl ImplementationIndexEntry { pub fn get_type_name(&self) -> &str { &self.type_name } + pub fn get_implementation_type(&self) -> &ImplementationType { + &self.implementation_type + } } impl From<&Implementation> for ImplementationIndexEntry { fn from(implementation: &Implementation) -> Self { + let pou_type = &implementation.pou_type; ImplementationIndexEntry { call_name: implementation.name.clone(), type_name: implementation.type_name.clone(), + implementation_type: pou_type.into(), + } + } +} + +impl From<&PouType> for ImplementationType { + fn from(it: &PouType) -> Self { + match it { + PouType::Program => ImplementationType::Program, + PouType::Function => ImplementationType::Function, + PouType::FunctionBlock => ImplementationType::FunctionBlock, + PouType::Action => ImplementationType::Action, + PouType::Class => ImplementationType::Class, + PouType::Method => ImplementationType::Method, } } } @@ -114,7 +143,7 @@ impl From<&Implementation> for ImplementationIndexEntry { /// The index contains information about all referencable elements. /// /// -#[derive(Debug)] +#[derive()] pub struct Index { /// all global variables global_variables: IndexMap, @@ -139,7 +168,7 @@ impl Index { types: IndexMap::new(), implementations: IndexMap::new(), void_type: DataType { - name: "void".to_string(), + name: VOID_TYPE.into(), initial_value: None, information: DataTypeInformation::Void, }, @@ -334,12 +363,18 @@ impl Index { &self.implementations } - pub fn register_implementation(&mut self, call_name: &str, type_name: &str) { + pub fn register_implementation( + &mut self, + call_name: &str, + type_name: &str, + impl_type: ImplementationType, + ) { self.implementations.insert( call_name.into(), ImplementationIndexEntry { call_name: call_name.into(), type_name: type_name.into(), + implementation_type: impl_type, }, ); } diff --git a/src/index/tests/index_tests.rs b/src/index/tests/index_tests.rs index 51766e17f7..718ccc7942 100644 --- a/src/index/tests/index_tests.rs +++ b/src/index/tests/index_tests.rs @@ -628,11 +628,12 @@ fn find_effective_type_finds_the_inner_effective_type() { #[test] fn pre_processing_generates_inline_enums_global() { // GIVEN a global inline enum - let lexer = lex(r#" + let src = r#" VAR_GLOBAL inline_enum : (a,b,c); END_VAR - "#); + "#; + let lexer = lex(src); let (mut ast, ..) = parser::parse(lexer); // WHEN the AST ist pre-processed @@ -654,9 +655,14 @@ fn pre_processing_generates_inline_enums_global() { assert_eq!( &DataTypeDeclaration::DataTypeReference { referenced_type: "__global_inline_enum".to_string(), + location: (46..53).into(), }, var_data_type ); + assert_eq!( + src[var_data_type.get_location().to_range()].to_string(), + "(a,b,c)".to_string() + ); assert_eq!( &"__global_inline_enum".to_string(), @@ -697,7 +703,8 @@ fn pre_processing_generates_inline_structs_global() { variables: vec![Variable { name: "a".to_string(), data_type: DataTypeDeclaration::DataTypeReference { - referenced_type: "INT".to_string() + referenced_type: "INT".to_string(), + location: (57..60).into(), }, location: (54..55).into(), initializer: None, @@ -711,6 +718,7 @@ fn pre_processing_generates_inline_structs_global() { assert_eq!( &DataTypeDeclaration::DataTypeReference { referenced_type: "__global_inline_struct".to_string(), + location: (47..72).into(), }, var_data_type ); @@ -747,6 +755,7 @@ fn pre_processing_generates_inline_enums() { assert_eq!( &DataTypeDeclaration::DataTypeReference { referenced_type: "__foo_inline_enum".to_string(), + location: (59..66).into(), }, var_data_type ); @@ -769,6 +778,7 @@ fn pre_processing_generates_inline_structs() { //STRUCT //THEN an implicit datatype should have been generated for the struct + let new_struct_type = &ast.types[0].data_type; if let DataType::StructType { variables, .. } = new_struct_type { assert_eq!(variables[0].location, SourceRange::new(67..68)); @@ -782,7 +792,8 @@ fn pre_processing_generates_inline_structs() { variables: vec![Variable { name: "a".to_string(), data_type: DataTypeDeclaration::DataTypeReference { - referenced_type: "INT".to_string() + referenced_type: "INT".to_string(), + location: (70..73).into(), }, location: (67..68).into(), initializer: None, @@ -796,6 +807,7 @@ fn pre_processing_generates_inline_structs() { assert_eq!( &DataTypeDeclaration::DataTypeReference { referenced_type: "__foo_inline_struct".to_string(), + location: (60..85).into(), }, var_data_type ); @@ -838,9 +850,11 @@ fn pre_processing_generates_inline_arrays() { }, referenced_type: Box::new(DataTypeDeclaration::DataTypeReference { referenced_type: "INT".to_string(), + location: SourceRange::undefined(), }), }, initializer: None, + location: (59..77).into(), }; assert_eq!(format!("{:?}", expected), format!("{:?}", new_array_type)); @@ -849,6 +863,7 @@ fn pre_processing_generates_inline_arrays() { assert_eq!( &DataTypeDeclaration::DataTypeReference { referenced_type: "__foo_inline_array".to_string(), + location: (59..77).into(), }, var_data_type ); @@ -892,9 +907,11 @@ fn pre_processing_generates_inline_array_of_array() { }, referenced_type: Box::new(DataTypeDeclaration::DataTypeReference { referenced_type: "INT".to_string(), + location: SourceRange::undefined(), }), }, initializer: None, + location: (59..92).into(), }; assert_eq!(format!("{:?}", expected), format!("{:?}", new_array_type)); @@ -918,17 +935,21 @@ fn pre_processing_generates_inline_array_of_array() { }, referenced_type: Box::new(DataTypeDeclaration::DataTypeReference { referenced_type: "__foo_inline_array_".to_string(), + location: SourceRange::undefined(), }), }, initializer: None, + location: (59..92).into(), }; assert_eq!(format!("{:?}", expected), format!("{:?}", new_array_type)); // AND the original variable should now point to the new DataType let var_data_type = &ast.units[0].variable_blocks[0].variables[0].data_type; + println!("{:#?}", var_data_type.get_location()); assert_eq!( &DataTypeDeclaration::DataTypeReference { referenced_type: "__foo_inline_array".to_string(), + location: (59..92).into(), }, var_data_type ); @@ -967,12 +988,14 @@ fn pre_processing_nested_array_in_struct() { name: "field1".to_string(), data_type: DataTypeDeclaration::DataTypeReference { referenced_type: "__MyStruct_field1".to_string(), + location: SourceRange::undefined(), }, location: SourceRange::undefined(), initializer: None, }], }, initializer: None, + location: (14..97).into(), }; assert_eq!(format!("{:?}", expected), format!("{:?}", new_array_type)); @@ -996,9 +1019,11 @@ fn pre_processing_nested_array_in_struct() { }, referenced_type: Box::new(DataTypeDeclaration::DataTypeReference { referenced_type: "INT".to_string(), + location: SourceRange::undefined(), }), }, initializer: None, + location: (59..77).into(), }; assert_eq!(format!("{:?}", expected), format!("{:?}", new_array_type)); } @@ -1041,9 +1066,11 @@ fn pre_processing_generates_inline_array_of_array_of_array() { }, referenced_type: Box::new(DataTypeDeclaration::DataTypeReference { referenced_type: "INT".to_string(), + location: SourceRange::undefined(), }), }, initializer: None, + location: (74..107).into(), }; assert_eq!(format!("{:?}", expected), format!("{:?}", new_array_type)); @@ -1067,9 +1094,11 @@ fn pre_processing_generates_inline_array_of_array_of_array() { }, referenced_type: Box::new(DataTypeDeclaration::DataTypeReference { referenced_type: "__foo_inline_array__".to_string(), + location: SourceRange::undefined(), }), }, initializer: None, + location: (59..107).into(), }; assert_eq!(format!("{:?}", expected), format!("{:?}", new_array_type)); @@ -1093,9 +1122,11 @@ fn pre_processing_generates_inline_array_of_array_of_array() { }, referenced_type: Box::new(DataTypeDeclaration::DataTypeReference { referenced_type: "__foo_inline_array_".to_string(), + location: SourceRange::undefined(), }), }, initializer: None, + location: (59..107).into(), }; assert_eq!(format!("{:?}", expected), format!("{:?}", new_array_type)); @@ -1104,6 +1135,7 @@ fn pre_processing_generates_inline_array_of_array_of_array() { assert_eq!( &DataTypeDeclaration::DataTypeReference { referenced_type: "__foo_inline_array".to_string(), + location: (59..107).into(), }, var_data_type ); diff --git a/src/index/visitor.rs b/src/index/visitor.rs index 091f10a0f7..aaa9f663d4 100644 --- a/src/index/visitor.rs +++ b/src/index/visitor.rs @@ -64,6 +64,7 @@ pub fn visit_pou(index: &mut Index, pou: &Pou) { for var in &block.variables { if let DataTypeDeclaration::DataTypeDefinition { data_type: ast::DataType::VarArgs { referenced_type }, + .. } = &var.data_type { let name = referenced_type @@ -128,9 +129,14 @@ pub fn visit_pou(index: &mut Index, pou: &Pou) { } fn visit_implementation(index: &mut Index, implementation: &Implementation) { - index.register_implementation(&implementation.name, &implementation.type_name); + let pou_type = &implementation.pou_type; + index.register_implementation( + &implementation.name, + &implementation.type_name, + pou_type.into(), + ); //if we are registing an action, also register a datatype for it - if implementation.pou_type == PouType::Action { + if pou_type == &PouType::Action { index.register_type( &implementation.name, None, @@ -204,13 +210,14 @@ fn visit_data_type(index: &mut Index, type_declatation: &UserTypeDeclaration) { information, ); for (count, var) in variables.iter().enumerate() { - if let DataTypeDeclaration::DataTypeDefinition { data_type } = &var.data_type { + if let DataTypeDeclaration::DataTypeDefinition { data_type, .. } = &var.data_type { //first we need to handle the inner type visit_data_type( index, &UserTypeDeclaration { data_type: data_type.clone(), initializer: None, + location: SourceRange::undefined(), }, ) } diff --git a/src/lexer.rs b/src/lexer.rs index 2b64780757..30f6df071d 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -31,8 +31,8 @@ macro_rules! expect_token { ($lexer:expr, $token:expr, $return_value:expr) => { if $lexer.token != $token { $lexer.accept_diagnostic(Diagnostic::unexpected_token_found( - format!("{:?}", $token), - $lexer.slice().to_string(), + format!("{:?}", $token).as_str(), + $lexer.slice(), $lexer.location(), )); return $return_value; @@ -65,8 +65,8 @@ impl<'a> ParseSession<'a> { pub fn expect(&self, token: Token) -> Result<(), Diagnostic> { if self.token != token { Err(Diagnostic::unexpected_token_found( - format!("{:?}", token), - self.slice().to_string(), + format!("{:?}", token).as_str(), + self.slice(), self.location(), )) } else { @@ -87,7 +87,7 @@ impl<'a> ParseSession<'a> { pub fn consume_or_report(&mut self, token: Token) { if !self.allow(&token) { self.accept_diagnostic(Diagnostic::missing_token( - format!("{:?}", token), + format!("{:?}", token).as_str(), self.location(), )); } @@ -170,8 +170,8 @@ impl<'a> ParseSession<'a> { if let Some(expected_token) = self.closing_keywords.pop() { if !expected_token.contains(&self.token) { self.accept_diagnostic(Diagnostic::unexpected_token_found( - format!("{:?}", expected_token[0]), - format!("'{}'", self.slice()), + format!("{:?}", expected_token[0]).as_str(), + format!("'{}'", self.slice()).as_str(), self.location(), )); } else { @@ -216,8 +216,9 @@ impl<'a> ParseSession<'a> { .last() .and_then(|it| it.first()) .unwrap_or(&Token::End) //only show first expected token - ), - format!("'{}'", self.slice_region(range.clone())), + ) + .as_str(), + format!("'{}'", self.slice_region(range.clone())).as_str(), SourceRange::new(range), )); } @@ -226,7 +227,10 @@ impl<'a> ParseSession<'a> { if self.closing_keywords.len() > hit + 1 { let closing = self.closing_keywords.last().unwrap(); let expected_tokens = format!("{:?}", closing); - self.accept_diagnostic(Diagnostic::missing_token(expected_tokens, self.location())); + self.accept_diagnostic(Diagnostic::missing_token( + expected_tokens.as_str(), + self.location(), + )); } } } diff --git a/src/lib.rs b/src/lib.rs index 36ebdf2e93..9f0a55d4cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,6 +36,7 @@ use inkwell::targets::{ use parser::ParsedAst; use resolver::TypeAnnotator; use std::{fs::File, io::Read}; +use validation::Validator; use crate::ast::CompilationUnit; mod ast; @@ -47,49 +48,91 @@ mod lexer; mod parser; mod resolver; mod typesystem; +mod validation; #[macro_use] extern crate pretty_assertions; #[derive(PartialEq, Debug, Clone)] pub enum Diagnostic { - SyntaxError { message: String, range: SourceRange }, - ImprovementSuggestion { message: String, range: SourceRange }, + SyntaxError { + message: String, + range: SourceRange, + err_no: ErrNo, + }, + ImprovementSuggestion { + message: String, + range: SourceRange, + }, +} + +#[allow(non_camel_case_types)] +#[derive(PartialEq, Debug, Clone)] +pub enum ErrNo { + undefined, + + //syntax + syntax__generic_error, + syntax__missing_token, + syntax__unexpected_token, + + //semantic + // pou related + pou__missing_return_type, + pou__unexpected_return_type, + pou__empty_variable_block, + + //reference related + reference__unresolved, + //variable related + + //type related } impl Diagnostic { - pub fn syntax_error(message: String, range: SourceRange) -> Diagnostic { - Diagnostic::SyntaxError { message, range } + pub fn syntax_error(message: &str, range: SourceRange) -> Diagnostic { + Diagnostic::SyntaxError { + message: message.to_string(), + range, + err_no: ErrNo::syntax__generic_error, + } } - pub fn unexpected_token_found( - expected: String, - found: String, - range: SourceRange, - ) -> Diagnostic { - Diagnostic::syntax_error( - format!( + pub fn unexpected_token_found(expected: &str, found: &str, range: SourceRange) -> Diagnostic { + Diagnostic::SyntaxError { + message: format!( "Unexpected token: expected {} but found {}", expected, found ), range, - ) + err_no: ErrNo::syntax__unexpected_token, + } } pub fn return_type_not_supported(pou_type: &PouType, range: SourceRange) -> Diagnostic { - Diagnostic::syntax_error( - format!( + Diagnostic::SyntaxError { + message: format!( "POU Type {:?} does not support a return type. Did you mean Function?", pou_type ), range, - ) + err_no: ErrNo::pou__unexpected_return_type, + } + } + + pub fn function_return_missing(range: SourceRange) -> Diagnostic { + Diagnostic::SyntaxError { + message: "Function Return type missing".into(), + range, + err_no: ErrNo::pou__missing_return_type, + } } - pub fn missing_token(epxected_token: String, range: SourceRange) -> Diagnostic { + pub fn missing_token(epxected_token: &str, range: SourceRange) -> Diagnostic { Diagnostic::SyntaxError { message: format!("Missing expected Token {}", epxected_token), range, + err_no: ErrNo::syntax__missing_token, } } @@ -97,6 +140,23 @@ impl Diagnostic { Diagnostic::SyntaxError { message: "Missing Actions Container Name".to_string(), range, + err_no: ErrNo::undefined, + } + } + + pub fn unrseolved_reference(reference: &str, location: SourceRange) -> Diagnostic { + Diagnostic::SyntaxError { + message: format!("Could not resolve reference to '{:}", reference), + range: location, + err_no: ErrNo::reference__unresolved, + } + } + + pub fn empty_variable_block(location: SourceRange) -> Diagnostic { + Diagnostic::SyntaxError { + message: "Variable block is empty".into(), + range: location, + err_no: ErrNo::pou__empty_variable_block, } } @@ -319,64 +379,83 @@ pub fn compile_module<'c, T: SourceContainer>( encoding: Option<&'static Encoding>, ) -> Result, CompileError> { let mut full_index = Index::new(); - let mut unit = CompilationUnit::default(); - // let mut diagnostics : Vec = vec![]; let mut files: SimpleFiles = SimpleFiles::new(); + let mut all_units = Vec::new(); + + // ### PHASE 1 ### + // parse & index everything for container in sources { let location: String = container.get_location().into(); let e = container .load_source(encoding) .map_err(|err| CompileError::io_read_error(err, location.clone()))?; + let file_id = files.add(location.clone(), e.source.clone()); let (mut parse_result, diagnostics) = parse(e.source.as_str()); //pre-process the ast (create inlined types) ast::pre_process(&mut parse_result); //index the pou full_index.import(index::visitor::visit(&parse_result)); - all_units.push(parse_result); + all_units.push((file_id, diagnostics, parse_result)); + } + // ### PHASE 2 ### + // annotation & validation everything + for (file_id, syntax_errors, container) in all_units.iter() { + let annotations = TypeAnnotator::visit_unit(&full_index, container); + + let mut validator = Validator::new(); + validator.visit_unit(&annotations, &container); //log errors - let file_id = files.add(location, e.source.clone()); - for error in diagnostics { - let diag = diagnostic::Diagnostic::error() - .with_message(error.get_message()) - .with_labels(vec![Label::primary( - file_id, - error.get_location().get_start()..error.get_location().get_end(), - )]); - let writer = StandardStream::stderr(ColorChoice::Always); - let config = codespan_reporting::term::Config { - display_style: term::DisplayStyle::Rich, - tab_width: 2, - styles: Styles::default(), - chars: Chars::default(), - start_context_lines: 5, - end_context_lines: 3, - }; - - term::emit(&mut writer.lock(), &config, &files, &diag).map_err(|err| { - CompileError::codegen_error( - format!("Cannot print errors {:#?}", err), - SourceRange::undefined(), - ) - })?; - } + report_diagnostics(*file_id, syntax_errors.iter(), &files)?; + report_diagnostics(*file_id, validator.diagnostics().iter(), &files)?; } - //annotate the ASTs - for u in all_units { - let _type_map = TypeAnnotator::visit_unit(&full_index, &u); - //TODO validate and find solution for type_map - unit.import(u); //TODO this needs to be changed so we have unique AstIds + // ### PHASE 3 ### + // - codegen + let mut code_gen_unit = CompilationUnit::default(); + for (_, _, u) in all_units { + code_gen_unit.import(u); } - //and finally codegen let code_generator = codegen::CodeGen::new(context, "main"); - code_generator.generate(unit, &full_index)?; + code_generator.generate(code_gen_unit, &full_index)?; Ok(code_generator) } +fn report_diagnostics( + file_id: usize, + semantic_diagnostics: std::slice::Iter, + files: &SimpleFiles, +) -> Result<(), CompileError> { + for error in semantic_diagnostics { + let diag = diagnostic::Diagnostic::error() + .with_message(error.get_message()) + .with_labels(vec![Label::primary( + file_id, + error.get_location().get_start()..error.get_location().get_end(), + )]); + let writer = StandardStream::stderr(ColorChoice::Always); + let config = codespan_reporting::term::Config { + display_style: term::DisplayStyle::Rich, + tab_width: 2, + styles: Styles::default(), + chars: Chars::default(), + start_context_lines: 5, + end_context_lines: 3, + }; + + term::emit(&mut writer.lock(), &config, files, &diag).map_err(|err| { + CompileError::codegen_error( + format!("Cannot print errors {:#?}", err), + SourceRange::undefined(), + ) + })?; + } + Ok(()) +} + fn parse(source: &str) -> ParsedAst { let lexer = lexer::lex(source); parser::parse(lexer) diff --git a/src/parser.rs b/src/parser.rs index 321c43da58..a79a2a2c74 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -66,8 +66,8 @@ pub fn parse(mut lexer: ParseSession) -> ParsedAst { KeywordEndActions | End => return (unit, lexer.diagnostics), _ => { lexer.accept_diagnostic(Diagnostic::unexpected_token_found( - "StartKeyword".to_string(), - lexer.slice().to_string(), + "StartKeyword", + lexer.slice(), lexer.location(), )); lexer.advance(); @@ -103,8 +103,8 @@ fn parse_actions( } _ => { lexer.accept_diagnostic(Diagnostic::unexpected_token_found( - "KeywordAction".to_string(), - lexer.slice().to_string(), + "KeywordAction", + lexer.slice(), lexer.location(), )); return impls; @@ -217,8 +217,8 @@ fn parse_pou( //check if we ended on the right end-keyword if closing_tokens.contains(&lexer.last_token) && lexer.last_token != expected_end_token { lexer.accept_diagnostic(Diagnostic::unexpected_token_found( - format!("{:?}", expected_end_token), - lexer.slice_region(lexer.last_range.clone()).into(), + format!("{:?}", expected_end_token).as_str(), + lexer.slice_region(lexer.last_range.clone()), SourceRange::new(lexer.last_range.clone()), )); } @@ -256,13 +256,17 @@ fn parse_return_type(lexer: &mut ParseSession, pou_type: PouType) -> Option Option { Some(pou_name) } else { lexer.accept_diagnostic(Diagnostic::unexpected_token_found( - "Identifier".into(), - pou_name, + "Identifier", + pou_name.as_str(), lexer.location(), )); None @@ -418,8 +422,8 @@ fn parse_action( //lets see if we ended on the right END_ keyword if closing_tokens.contains(&lexer.last_token) && lexer.last_token != KeywordEndAction { lexer.accept_diagnostic(Diagnostic::unexpected_token_found( - format!("{:?}", KeywordEndAction), - lexer.slice().into(), + format!("{:?}", KeywordEndAction).as_str(), + lexer.slice(), lexer.location(), )) } @@ -430,15 +434,19 @@ fn parse_action( // TYPE ... END_TYPE fn parse_type(lexer: &mut ParseSession) -> Option { lexer.advance(); // consume the TYPE + let start = lexer.location().get_start(); let name = lexer.slice_and_advance(); lexer.consume_or_report(KeywordColon); let result = parse_full_data_type_definition(lexer, Some(name)); - if let Some((DataTypeDeclaration::DataTypeDefinition { data_type }, initializer)) = result { + + if let Some((DataTypeDeclaration::DataTypeDefinition { data_type, .. }, initializer)) = result { + let end = lexer.last_range.end; lexer.consume_or_report(KeywordEndType); Some(UserTypeDeclaration { data_type, initializer, + location: (start..end).into(), }) } else { None @@ -463,6 +471,7 @@ fn parse_full_data_type_definition( data_type: DataType::VarArgs { referenced_type: None, }, + location: lexer.last_range.clone().into(), }, None, )) @@ -474,6 +483,7 @@ fn parse_full_data_type_definition( data_type: DataType::VarArgs { referenced_type: Some(Box::new(type_def)), }, + location: lexer.last_range.clone().into(), }, None, ) @@ -490,12 +500,14 @@ fn parse_data_type_definition( lexer: &mut ParseSession, name: Option, ) -> Option { + let start = lexer.location().get_start(); if lexer.allow(&KeywordStruct) { // Parse struct let variables = parse_variable_list(lexer); Some(( DataTypeDeclaration::DataTypeDefinition { data_type: DataType::StructType { name, variables }, + location: (start..lexer.range().end).into(), }, None, )) @@ -510,8 +522,8 @@ fn parse_data_type_definition( } else { //no datatype? lexer.accept_diagnostic(Diagnostic::unexpected_token_found( - "DataTypeDefinition".into(), - format!("{:?}", lexer.token), + "DataTypeDefinition", + format!("{:?}", lexer.token).as_str(), lexer.location(), )); None @@ -522,6 +534,7 @@ fn parse_type_reference_type_definition( lexer: &mut ParseSession, name: Option, ) -> Option<(DataTypeDeclaration, Option)> { + let start = lexer.location().get_start(); //Subrange let referenced_type = lexer.slice_and_advance(); @@ -541,6 +554,7 @@ fn parse_type_reference_type_definition( None }; + let end = lexer.last_range.end; if name.is_some() || bounds.is_some() { let data_type = DataTypeDeclaration::DataTypeDefinition { data_type: DataType::SubRangeType { @@ -548,11 +562,15 @@ fn parse_type_reference_type_definition( referenced_type, bounds, }, + location: (start..end).into(), }; Some((data_type, initial_value)) } else { Some(( - DataTypeDeclaration::DataTypeReference { referenced_type }, + DataTypeDeclaration::DataTypeReference { + referenced_type, + location: (start..end).into(), + }, initial_value, )) } @@ -593,10 +611,12 @@ fn parse_string_type_definition( lexer: &mut ParseSession, name: Option, ) -> Option<(DataTypeDeclaration, Option)> { + let start = lexer.location().get_start(); let is_wide = lexer.token == KeywordWideString; lexer.advance(); let size = parse_string_size_expression(lexer); + let end = lexer.last_range.end; Some(( DataTypeDeclaration::DataTypeDefinition { @@ -605,6 +625,7 @@ fn parse_string_type_definition( is_wide, size, }, + location: (start..end).into(), }, lexer .allow(&KeywordAssignment) @@ -616,14 +637,16 @@ fn parse_enum_type_definition( lexer: &mut ParseSession, name: Option, ) -> Option<(DataTypeDeclaration, Option)> { + let start = lexer.last_range.start; let elements = parse_any_in_region(lexer, vec![KeywordParensClose], |lexer| { // Parse Enum - we expect at least one element let mut elements = Vec::new(); - expect_token!(lexer, Identifier, None); - elements.push(lexer.slice_and_advance()); - - // parse additional elements separated by ',' + //we expect at least one element + if lexer.token == Identifier { + elements.push(lexer.slice_and_advance()); + } + //parse additional elements separated by , while lexer.allow(&KeywordComma) { expect_token!(lexer, Identifier, None); elements.push(lexer.slice_and_advance()); @@ -634,6 +657,7 @@ fn parse_enum_type_definition( Some(( DataTypeDeclaration::DataTypeDefinition { data_type: DataType::EnumType { name, elements }, + location: (start..lexer.last_range.end).into(), }, None, )) @@ -643,6 +667,7 @@ fn parse_array_type_definition( lexer: &mut ParseSession, name: Option, ) -> Option<(DataTypeDeclaration, Option)> { + let start = lexer.last_range.start; let range = parse_any_in_region(lexer, vec![KeywordOf], |lexer| { // Parse Array range @@ -659,6 +684,7 @@ fn parse_array_type_definition( let inner_type_defintion = parse_data_type_definition(lexer, None); inner_type_defintion.map(|(reference, initializer)| { + let location = SourceRange::new(start..reference.get_location().get_end()); ( DataTypeDeclaration::DataTypeDefinition { data_type: DataType::ArrayType { @@ -666,6 +692,7 @@ fn parse_array_type_definition( bounds: range, referenced_type: Box::new(reference), }, + location, }, initializer, ) @@ -796,7 +823,7 @@ fn parse_variable_line(lexer: &mut ParseSession) -> Vec { if !lexer.allow(&KeywordComma) { let next_token_start = lexer.location().get_start(); lexer.accept_diagnostic(Diagnostic::missing_token( - format!("{:?} or {:?}", KeywordColon, KeywordComma), + format!("{:?} or {:?}", KeywordColon, KeywordComma).as_str(), SourceRange::new(identifier_end..next_token_start), )); } @@ -805,7 +832,7 @@ fn parse_variable_line(lexer: &mut ParseSession) -> Vec { // colon has to come before the data type if !lexer.allow(&KeywordColon) { lexer.accept_diagnostic(Diagnostic::missing_token( - format!("{:?}", KeywordColon), + format!("{:?}", KeywordColon).as_str(), lexer.location(), )); } diff --git a/src/parser/control_parser.rs b/src/parser/control_parser.rs index a290e47993..aa830d25be 100644 --- a/src/parser/control_parser.rs +++ b/src/parser/control_parser.rs @@ -216,7 +216,7 @@ fn parse_case_statement(lexer: &mut ParseSession) -> Statement { //If no current condition is available, log a diagnostic and add an empty condition if current_condition.is_none() { lexer.accept_diagnostic(Diagnostic::syntax_error( - "Missing Case-Condition".into(), + "Missing Case-Condition", lexer.location(), )); current_condition = Some(Box::new(Statement::EmptyStatement { diff --git a/src/parser/expressions_parser.rs b/src/parser/expressions_parser.rs index 2aba34989b..9d0cd12ed4 100644 --- a/src/parser/expressions_parser.rs +++ b/src/parser/expressions_parser.rs @@ -255,8 +255,8 @@ fn parse_leaf_expression(lexer: &mut ParseSession) -> Statement { LiteralFalse => parse_bool_literal(lexer, false), KeywordSquareParensOpen => parse_array_literal(lexer), _ => Err(Diagnostic::unexpected_token_found( - "Literal".to_string(), - lexer.slice().to_string(), + "Literal", + lexer.slice(), lexer.location(), )), }; @@ -412,7 +412,7 @@ fn parse_literal_number(lexer: &mut ParseSession) -> Result() - .map_err(|e| Diagnostic::syntax_error(format!("{}", e), location.clone()))?; + .map_err(|e| Diagnostic::syntax_error(format!("{}", e).as_str(), location.clone()))?; let element = parse_expression(lexer); lexer.expect(KeywordParensClose)?; let end = lexer.range().end; @@ -436,7 +436,10 @@ fn parse_literal_number(lexer: &mut ParseSession) -> Result(text: &str, location: &SourceRange) -> Result { text.parse::().map_err(|_| { - Diagnostic::syntax_error(format!("Failed parsing number {}", text), location.clone()) + Diagnostic::syntax_error( + format!("Failed parsing number {}", text).as_str(), + location.clone(), + ) }) } @@ -573,7 +576,7 @@ fn parse_literal_time(lexer: &mut ParseSession) -> Result char = chars.find(|(_, ch)| !ch.is_digit(10) && !ch.eq(&'.')); char.ok_or_else(|| { Diagnostic::syntax_error( - "Invalid TIME Literal: Cannot parse segment.".to_string(), + "Invalid TIME Literal: Cannot parse segment.", location.clone(), ) }) @@ -584,7 +587,7 @@ fn parse_literal_time(lexer: &mut ParseSession) -> Result let unit = { let start = char.map(|(index, _)| index).ok_or_else(|| { Diagnostic::syntax_error( - "Invalid TIME Literal: Missing unit (d|h|m|s|ms|us|ns)".to_string(), + "Invalid TIME Literal: Missing unit (d|h|m|s|ms|us|ns)", location.clone(), ) })?; @@ -609,7 +612,7 @@ fn parse_literal_time(lexer: &mut ParseSession) -> Result //check if we assign out of order - every assignment before must have been a smaller position if prev_pos > position { return Err(Diagnostic::syntax_error( - "Invalid TIME Literal: segments out of order, use d-h-m-s-ms".to_string(), + "Invalid TIME Literal: segments out of order, use d-h-m-s-ms", location, )); } @@ -617,14 +620,14 @@ fn parse_literal_time(lexer: &mut ParseSession) -> Result if values[position].is_some() { return Err(Diagnostic::syntax_error( - "Invalid TIME Literal: segments must be unique".to_string(), + "Invalid TIME Literal: segments must be unique", location, )); } values[position] = Some(number); //store the number } else { return Err(Diagnostic::syntax_error( - format!("Invalid TIME Literal: illegal unit '{}'", unit), + format!("Invalid TIME Literal: illegal unit '{}'", unit).as_str(), location, )); } diff --git a/src/parser/tests/container_parser_tests.rs b/src/parser/tests/container_parser_tests.rs index 7445d9bf6c..d67143379f 100644 --- a/src/parser/tests/container_parser_tests.rs +++ b/src/parser/tests/container_parser_tests.rs @@ -88,11 +88,7 @@ fn actions_with_invalid_token() { let errors = parse(lexer).1; assert_eq!( errors.first().unwrap(), - &Diagnostic::unexpected_token_found( - "KeywordAction".to_string(), - "BRAVO".into(), - (13..18).into() - ) + &Diagnostic::unexpected_token_found("KeywordAction", "BRAVO".into(), (13..18).into()) ); } diff --git a/src/parser/tests/function_parser_tests.rs b/src/parser/tests/function_parser_tests.rs index 90890b4a1f..093b1f2ac7 100644 --- a/src/parser/tests/function_parser_tests.rs +++ b/src/parser/tests/function_parser_tests.rs @@ -16,7 +16,8 @@ fn simple_foo_function_can_be_parsed() { assert_eq!( prg.return_type.as_ref().unwrap(), &DataTypeDeclaration::DataTypeReference { - referenced_type: "INT".to_string() + referenced_type: "INT".to_string(), + location: (15..18).into(), } ); } @@ -119,6 +120,7 @@ fn varargs_parameters_can_be_parsed() { pou_type: PouType::Function, return_type: Some(DataTypeDeclaration::DataTypeReference { referenced_type: "DINT".into(), + location: SourceRange::undefined(), }), variable_blocks: vec![VariableBlock { constant: false, @@ -132,6 +134,7 @@ fn varargs_parameters_can_be_parsed() { data_type: DataType::VarArgs { referenced_type: None, }, + location: SourceRange::undefined(), }, initializer: None, location: SourceRange::undefined(), @@ -143,9 +146,11 @@ fn varargs_parameters_can_be_parsed() { referenced_type: Some(Box::new( DataTypeDeclaration::DataTypeReference { referenced_type: "INT".into(), + location: SourceRange::undefined(), }, )), }, + location: SourceRange::undefined(), }, initializer: None, location: SourceRange::undefined(), diff --git a/src/parser/tests/parse_errors/parse_error_containers_tests.rs b/src/parser/tests/parse_errors/parse_error_containers_tests.rs index f897bc21e3..b2ad91d1f2 100644 --- a/src/parser/tests/parse_errors/parse_error_containers_tests.rs +++ b/src/parser/tests/parse_errors/parse_error_containers_tests.rs @@ -63,15 +63,8 @@ fn missing_pou_name_2() { assert_eq!( diagnostics, vec![ - Diagnostic::syntax_error( - "Unexpected token: expected Literal but found :=".into(), - (36..38).into() - ), - Diagnostic::unexpected_token_found( - "KeywordSemicolon".into(), - "':= 2'".into(), - (36..40).into() - ) + Diagnostic::unexpected_token_found("Literal", ":=", (36..38).into()), + Diagnostic::unexpected_token_found("KeywordSemicolon", "':= 2'", (36..40).into()) ] ); @@ -102,7 +95,7 @@ fn illegal_end_pou_keyword() { let (compilation_unit, diagnostics) = parse(lexer); let expected = Diagnostic::unexpected_token_found( - format!("{:?}", Token::KeywordEndProgram), + format!("{:?}", Token::KeywordEndProgram).as_str(), "END_FUNCTION".into(), SourceRange::new(52..64), ); @@ -251,6 +244,7 @@ fn unclosed_var_container() { name: "a".into(), data_type: crate::ast::DataTypeDeclaration::DataTypeReference { referenced_type: "INT".into(), + location: SourceRange::undefined(), }, initializer: None, location: SourceRange::undefined(), diff --git a/src/parser/tests/parse_errors/parse_error_literals_tests.rs b/src/parser/tests/parse_errors/parse_error_literals_tests.rs index d62bb169aa..fce1daee34 100644 --- a/src/parser/tests/parse_errors/parse_error_literals_tests.rs +++ b/src/parser/tests/parse_errors/parse_error_literals_tests.rs @@ -16,8 +16,8 @@ fn illegal_literal_time_missing_segments_test() { assert_eq!( diagnostics, vec![Diagnostic::unexpected_token_found( - "KeywordSemicolon".into(), - "'#'".into(), + "KeywordSemicolon", + "'#'", SourceRange::new(35..36) )] ); @@ -38,7 +38,7 @@ fn time_literal_problems_can_be_recovered_from_during_parsing() { assert_eq!( diagnostics, vec![Diagnostic::syntax_error( - "Invalid TIME Literal: segments must be unique".into(), + "Invalid TIME Literal: segments must be unique", (34..44).into() )] ); @@ -56,7 +56,7 @@ fn illegal_literal_time_double_segments_test() { assert_eq!( diagnostics[0], Diagnostic::syntax_error( - "Invalid TIME Literal: segments must be unique".into(), + "Invalid TIME Literal: segments must be unique", SourceRange::new(34..44) ) ); @@ -74,8 +74,8 @@ fn illegal_literal_time_out_of_order_segments_test() { assert_eq!( diagnostics[0], Diagnostic::syntax_error( - "Invalid TIME Literal: segments out of order, use d-h-m-s-ms".into(), - SourceRange::new(34..42) + "Invalid TIME Literal: segments out of order, use d-h-m-s-ms", + SourceRange::new(34..42), ) ); } @@ -87,10 +87,7 @@ fn literal_hex_number_with_double_underscores() { assert_eq!( result.first().unwrap(), - &Diagnostic::SyntaxError { - message: "Unexpected token: expected KeywordSemicolon but found '__beef'".into(), - range: SourceRange::new(19..25) - } + &Diagnostic::unexpected_token_found("KeywordSemicolon", "'__beef'", (19..25).into()) ); } @@ -101,10 +98,7 @@ fn literal_dec_number_with_double_underscores() { assert_eq!( result.first().unwrap(), - &Diagnostic::SyntaxError { - message: "Unexpected token: expected KeywordSemicolon but found '__000'".into(), - range: SourceRange::new(14..19) - } + &Diagnostic::unexpected_token_found("KeywordSemicolon", "'__000'", (14..19).into()) ); } @@ -115,10 +109,7 @@ fn literal_bin_number_with_double_underscores() { assert_eq!( result.first().unwrap(), - &Diagnostic::SyntaxError { - message: "Unexpected token: expected KeywordSemicolon but found '__001_101_01'".into(), - range: SourceRange::new(16..28) - } + &Diagnostic::unexpected_token_found("KeywordSemicolon", "'__001_101_01'", (16..28).into()) ); } @@ -129,10 +120,7 @@ fn literal_oct_number_with_double_underscores() { assert_eq!( result.first().unwrap(), - &Diagnostic::SyntaxError { - message: "Unexpected token: expected KeywordSemicolon but found '__7'".into(), - range: SourceRange::new(15..18) - } + &Diagnostic::unexpected_token_found("KeywordSemicolon", "'__7'", (15..18).into()) ); } @@ -179,6 +167,7 @@ fn string_with_round_parens_can_be_parsed() { is_wide: false, }, initializer: None, + location: (18..42).into(), }, UserTypeDeclaration { data_type: DataType::StringType { @@ -192,10 +181,11 @@ fn string_with_round_parens_can_be_parsed() { }, initializer: Some(Statement::LiteralString { is_wide: false, - location: SourceRange::undefined(), + location: (69..102).into(), value: "abc".into(), id: 0, }), + location: SourceRange::undefined(), }, UserTypeDeclaration { data_type: DataType::StringType { @@ -208,6 +198,7 @@ fn string_with_round_parens_can_be_parsed() { is_wide: false, }, initializer: None, + location: SourceRange::undefined(), } ] ); diff --git a/src/parser/tests/parse_errors/parse_error_messages_test.rs b/src/parser/tests/parse_errors/parse_error_messages_test.rs index 7d7161363e..245a22524f 100644 --- a/src/parser/tests/parse_errors/parse_error_messages_test.rs +++ b/src/parser/tests/parse_errors/parse_error_messages_test.rs @@ -36,10 +36,7 @@ fn test_unexpected_token_error_message2() { ); let parse_result = parse(lexer); assert_eq!( - &Diagnostic::syntax_error( - "Unexpected token: expected StartKeyword but found SOME".into(), - (0..4).into() - ), + &Diagnostic::unexpected_token_found("StartKeyword", "SOME", (0..4).into()), parse_result.1.first().unwrap() ); } @@ -58,10 +55,7 @@ fn for_with_unexpected_token_1() { ); let parse_result = parse(lexer); assert_eq!( - &Diagnostic::syntax_error( - "Unexpected token: expected KeywordAssignment but found ALPHA".into(), - (36..41).into() - ), + &Diagnostic::unexpected_token_found("KeywordAssignment", "ALPHA", (36..41).into()), parse_result.1.first().unwrap() ); } @@ -80,10 +74,7 @@ fn for_with_unexpected_token_2() { ); let parse_result = parse(lexer); assert_eq!( - &Diagnostic::syntax_error( - "Unexpected token: expected KeywordTo but found BRAVO".into(), - (41..46).into() - ), + &Diagnostic::unexpected_token_found("KeywordTo", "BRAVO", (41..46).into()), parse_result.1.first().unwrap() ); } @@ -102,11 +93,9 @@ fn if_then_with_unexpected_token() { ", ); let parse_result = parse(lexer); + assert_eq!( - &Diagnostic::syntax_error( - "Unexpected token: expected KeywordThen but found CHARLIE".into(), - (38..45).into() - ), + &Diagnostic::unexpected_token_found("KeywordThen", "CHARLIE", (38..45).into()), parse_result.1.first().unwrap() ); } @@ -124,10 +113,7 @@ fn case_with_unexpected_token() { ); let parse_result = parse(lexer); assert_eq!( - &Diagnostic::syntax_error( - "Unexpected token: expected KeywordOf but found DELTA".into(), - (48..53).into() - ), + &Diagnostic::unexpected_token_found("KeywordOf", "DELTA", (48..53).into()), parse_result.1.first().unwrap() ); } diff --git a/src/parser/tests/parse_errors/parse_error_statements_tests.rs b/src/parser/tests/parse_errors/parse_error_statements_tests.rs index 2cb269a6cf..714db588bd 100644 --- a/src/parser/tests/parse_errors/parse_error_statements_tests.rs +++ b/src/parser/tests/parse_errors/parse_error_statements_tests.rs @@ -179,10 +179,7 @@ fn incomplete_statement_test() { assert_eq!( diagnostics[0], - Diagnostic::syntax_error( - "Unexpected token: expected Literal but found ;".into(), - SourceRange::new(41..42) - ) + Diagnostic::unexpected_token_found("Literal", ";", (41..42).into()) ); } @@ -227,10 +224,7 @@ fn incomplete_statement_in_parantheses_recovery_test() { assert_eq!( diagnostics[0], - Diagnostic::syntax_error( - "Unexpected token: expected Literal but found )".into(), - SourceRange::new(43..44) - ) + Diagnostic::unexpected_token_found("Literal", ")", (43..44).into()) ); } @@ -294,6 +288,7 @@ fn invalid_variable_name_error_recovery() { name: "c".into(), data_type: DataTypeDeclaration::DataTypeReference { referenced_type: "INT".into(), + location: SourceRange::undefined(), }, initializer: None, location: SourceRange::undefined(), @@ -306,7 +301,7 @@ fn invalid_variable_name_error_recovery() { assert_eq!( diagnostics[0], Diagnostic::unexpected_token_found( - format!("{:?}", Token::KeywordEndVar), + format!("{:?}", Token::KeywordEndVar).as_str(), "'4 : INT;'".into(), SourceRange::new(77..85) ) @@ -704,8 +699,9 @@ fn test_nested_repeat_with_missing_condition_and_end_repeat() { assert_eq!( diagnostics, vec![ - Diagnostic::syntax_error( - "Unexpected token: expected Literal but found END_PROGRAM".into(), + Diagnostic::unexpected_token_found( + "Literal".into(), + "END_PROGRAM".into(), (171..182).into() ), Diagnostic::missing_token("[KeywordEndRepeat]".into(), (171..182).into()), @@ -1063,9 +1059,10 @@ fn test_case_without_condition() { assert_eq!( diagnostics, - vec![Diagnostic::syntax_error( - "Unexpected token: expected Literal but found :".into(), + vec![Diagnostic::unexpected_token_found( + "Literal", + ":", (85..86).into() - ),] + )] ); } diff --git a/src/parser/tests/parser_tests.rs b/src/parser/tests/parser_tests.rs new file mode 100644 index 0000000000..8c852b3de6 --- /dev/null +++ b/src/parser/tests/parser_tests.rs @@ -0,0 +1,1571 @@ +// Copyright (c) 2020 Ghaith Hachem and Mathias Rieder +use crate::ast::*; +use crate::parser::parse; +use crate::parser::Statement::LiteralInteger; +use crate::Diagnostic; +use pretty_assertions::*; + +#[test] +fn empty_returns_empty_compilation_unit() { + let (result, ..) = parse(super::lex("")).unwrap(); + assert_eq!(result.units.len(), 0); +} + +#[test] +fn empty_global_vars_can_be_parsed() { + let lexer = super::lex("VAR_GLOBAL END_VAR"); + let result = parse(lexer).unwrap().0; + + let vars = &result.global_vars[0]; //globar_vars + let ast_string = format!("{:#?}", vars); + let expected_ast = r#"VariableBlock { + variables: [], + variable_block_type: Global, +}"#; + assert_eq!(ast_string, expected_ast) +} + +#[test] +fn global_vars_can_be_parsed() { + let lexer = super::lex("VAR_GLOBAL x : INT; y : BOOL; END_VAR"); + let result = parse(lexer).unwrap().0; + + let vars = &result.global_vars[0]; //globar_vars + let ast_string = format!("{:#?}", vars); + let expected_ast = r#"VariableBlock { + variables: [ + Variable { + name: "x", + data_type: DataTypeReference { + referenced_type: "INT", + }, + }, + Variable { + name: "y", + data_type: DataTypeReference { + referenced_type: "BOOL", + }, + }, + ], + variable_block_type: Global, +}"#; + assert_eq!(ast_string, expected_ast) +} + +#[test] +fn two_global_vars_can_be_parsed() { + let lexer = super::lex("VAR_GLOBAL a: INT; END_VAR VAR_GLOBAL x : INT; y : BOOL; END_VAR"); + let result = parse(lexer).unwrap().0; + + let vars = &result.global_vars; //global_vars + let ast_string = format!("{:#?}", vars); + let expected_ast = r#"[ + VariableBlock { + variables: [ + Variable { + name: "a", + data_type: DataTypeReference { + referenced_type: "INT", + }, + }, + ], + variable_block_type: Global, + }, + VariableBlock { + variables: [ + Variable { + name: "x", + data_type: DataTypeReference { + referenced_type: "INT", + }, + }, + Variable { + name: "y", + data_type: DataTypeReference { + referenced_type: "BOOL", + }, + }, + ], + variable_block_type: Global, + }, +]"#; + assert_eq!(ast_string, expected_ast) +} + +#[test] +fn simple_foo_program_can_be_parsed() { + let lexer = super::lex("PROGRAM foo END_PROGRAM"); + let result = parse(lexer).unwrap().0; + + let prg = &result.units[0]; + assert_eq!(prg.pou_type, PouType::Program); + assert_eq!(prg.name, "foo"); + assert!(prg.return_type.is_none()); +} + +#[test] +fn simple_foo_function_can_be_parsed() { + let lexer = super::lex("FUNCTION foo : INT END_FUNCTION"); + let result = parse(lexer).unwrap().0; + + let prg = &result.units[0]; + assert_eq!(prg.pou_type, PouType::Function); + assert_eq!(prg.name, "foo"); + + assert_eq!( + prg.return_type.as_ref().unwrap(), + &DataTypeDeclaration::DataTypeReference { + referenced_type: "INT".to_string(), + location: (15..18).into(), + } + ); +} + +#[test] +fn simple_foo_function_block_can_be_parsed() { + let lexer = super::lex("FUNCTION_BLOCK foo END_FUNCTION_BLOCK"); + let result = parse(lexer).unwrap().0; + + let prg = &result.units[0]; + assert_eq!(prg.pou_type, PouType::FunctionBlock); + assert_eq!(prg.name, "foo"); + assert!(prg.return_type.is_none()); +} + +#[test] +fn single_action_parsed() { + let lexer = super::lex("ACTION foo.bar END_ACTION"); + let result = parse(lexer).unwrap().0; + + let prg = &result.implementations[0]; + assert_eq!(prg.name, "foo.bar"); + assert_eq!(prg.type_name, "foo"); +} + +#[test] +fn two_actions_parsed() { + let lexer = super::lex("ACTION foo.bar END_ACTION ACTION fuz.bar END_ACTION"); + let result = parse(lexer).unwrap().0; + + let prg = &result.implementations[0]; + assert_eq!(prg.name, "foo.bar"); + assert_eq!(prg.type_name, "foo"); + + let prg2 = &result.implementations[1]; + assert_eq!(prg2.name, "fuz.bar"); + assert_eq!(prg2.type_name, "fuz"); +} + +#[test] +fn action_container_parsed() { + let lexer = super::lex("ACTIONS foo ACTION bar END_ACTION END_ACTIONS"); + let result = parse(lexer).unwrap().0; + + let prg = &result.implementations[0]; + assert_eq!(prg.name, "foo.bar"); + assert_eq!(prg.type_name, "foo"); +} + +#[test] +fn two_action_containers_parsed() { + let lexer = super::lex("ACTIONS foo ACTION bar END_ACTION ACTION buz END_ACTION END_ACTIONS"); + let result = parse(lexer).unwrap().0; + + let prg = &result.implementations[0]; + assert_eq!(prg.name, "foo.bar"); + assert_eq!(prg.type_name, "foo"); + + let prg2 = &result.implementations[1]; + assert_eq!(prg2.name, "foo.buz"); + assert_eq!(prg2.type_name, "foo"); +} + +#[test] +fn mixed_action_types_parsed() { + let lexer = super::lex("PROGRAM foo END_PROGRAM ACTIONS foo ACTION bar END_ACTION END_ACTIONS ACTION foo.buz END_ACTION"); + let result = parse(lexer).unwrap().0; + + let prg = &result.implementations[1]; + assert_eq!(prg.name, "foo.bar"); + assert_eq!(prg.type_name, "foo"); + + let prg2 = &result.implementations[2]; + assert_eq!(prg2.name, "foo.buz"); + assert_eq!(prg2.type_name, "foo"); +} + +#[test] +fn actions_with_no_container_error() { + let lexer = super::lex("ACTIONS ACTION bar END_ACTION ACTION buz END_ACTION END_ACTIONS"); + let err = parse(lexer).expect_err("Expecting parser failure"); + assert_eq!( + err, + Diagnostic::unexpected_token_found("Identifier".into(), "ACTION".into(), (8..14).into()) + ); +} + +#[test] +fn two_programs_can_be_parsed() { + let lexer = super::lex("PROGRAM foo END_PROGRAM PROGRAM bar END_PROGRAM"); + let result = parse(lexer).unwrap().0; + + let prg = &result.units[0]; + assert_eq!(prg.name, "foo"); + let prg2 = &result.units[1]; + assert_eq!(prg2.name, "bar"); +} + +#[test] +fn simple_program_with_varblock_can_be_parsed() { + let lexer = super::lex("PROGRAM buz VAR END_VAR END_PROGRAM"); + let result = parse(lexer).unwrap().0; + + let prg = &result.units[0]; + + assert_eq!(prg.variable_blocks.len(), 1); +} + +#[test] +fn simple_program_with_two_varblocks_can_be_parsed() { + let lexer = super::lex("PROGRAM buz VAR END_VAR VAR END_VAR END_PROGRAM"); + let result = parse(lexer).unwrap().0; + + let prg = &result.units[0]; + + assert_eq!(prg.variable_blocks.len(), 2); +} + +#[test] +fn a_program_needs_to_end_with_end_program() { + let lexer = super::lex("PROGRAM buz "); + let (_, diagnostics) = parse(lexer).unwrap(); + assert_eq!( + diagnostics, + vec![Diagnostic::unexpected_token_found( + "KeywordEndProgram".into(), + "''".into(), + (12..12).into() + ),] + ); +} + +#[test] +fn a_variable_declaration_block_needs_to_end_with_endvar() { + let lexer = super::lex("PROGRAM buz VAR END_PROGRAM "); + let (_, diagnostics) = parse(lexer).unwrap(); + + assert_eq!( + diagnostics, + vec![ + Diagnostic::missing_token("[KeywordEndVar]".into(), (16..27).into()), + Diagnostic::unexpected_token_found( + "KeywordEndVar".into(), + "'END_PROGRAM'".into(), + (16..27).into() + ), + ] + ); +} + +#[test] +fn empty_statements_are_are_parsed() { + let lexer = super::lex("PROGRAM buz ;;;; END_PROGRAM "); + let result = parse(lexer).unwrap().0; + + let prg = &result.implementations[0]; + assert_eq!( + format!("{:?}", prg.statements), + format!( + "{:?}", + vec![ + Statement::EmptyStatement { + location: SourceRange::undefined() + }, + Statement::EmptyStatement { + location: SourceRange::undefined() + }, + Statement::EmptyStatement { + location: SourceRange::undefined() + }, + Statement::EmptyStatement { + location: SourceRange::undefined() + }, + ] + ), + ); +} + +#[test] +fn empty_statements_are_parsed_before_a_statement() { + let lexer = super::lex("PROGRAM buz ;;;;x; END_PROGRAM "); + let result = parse(lexer).unwrap().0; + + let prg = &result.implementations[0]; + + assert_eq!( + format!("{:?}", prg.statements), + format!( + "{:?}", + vec![ + Statement::EmptyStatement { + location: SourceRange::undefined() + }, + Statement::EmptyStatement { + location: SourceRange::undefined() + }, + Statement::EmptyStatement { + location: SourceRange::undefined() + }, + Statement::EmptyStatement { + location: SourceRange::undefined() + }, + Statement::Reference { + name: "x".into(), + location: SourceRange::undefined() + }, + ] + ), + ); +} + +#[test] +fn empty_statements_are_ignored_after_a_statement() { + let lexer = super::lex("PROGRAM buz x;;;; END_PROGRAM "); + let result = parse(lexer).unwrap().0; + + let prg = &result.implementations[0]; + let statement = &prg.statements[0]; + + let ast_string = format!("{:#?}", statement); + let expected_ast = r#"Reference { + name: "x", +}"#; + assert_eq!(ast_string, expected_ast); +} + +#[test] +fn simple_program_with_variable_can_be_parsed() { + let lexer = super::lex("PROGRAM buz VAR x : INT; END_VAR END_PROGRAM"); + let result = parse(lexer).unwrap().0; + + let prg = &result.units[0]; + let variable_block = &prg.variable_blocks[0]; + let ast_string = format!("{:#?}", variable_block); + let expected_ast = r#"VariableBlock { + variables: [ + Variable { + name: "x", + data_type: DataTypeReference { + referenced_type: "INT", + }, + }, + ], + variable_block_type: Local, +}"#; + assert_eq!(ast_string, expected_ast); +} + +#[test] +fn simple_program_with_var_input_can_be_parsed() { + let lexer = super::lex("PROGRAM buz VAR_INPUT x : INT; END_VAR END_PROGRAM"); + let result = parse(lexer).unwrap().0; + + let prg = &result.units[0]; + let variable_block = &prg.variable_blocks[0]; + let ast_string = format!("{:#?}", variable_block); + let expected_ast = r#"VariableBlock { + variables: [ + Variable { + name: "x", + data_type: DataTypeReference { + referenced_type: "INT", + }, + }, + ], + variable_block_type: Input, +}"#; + assert_eq!(ast_string, expected_ast); +} + +#[test] +fn simple_program_with_var_output_can_be_parsed() { + let lexer = super::lex("PROGRAM buz VAR_OUTPUT x : INT; END_VAR END_PROGRAM"); + let result = parse(lexer).unwrap().0; + + let prg = &result.units[0]; + let variable_block = &prg.variable_blocks[0]; + let ast_string = format!("{:#?}", variable_block); + let expected_ast = r#"VariableBlock { + variables: [ + Variable { + name: "x", + data_type: DataTypeReference { + referenced_type: "INT", + }, + }, + ], + variable_block_type: Output, +}"#; + assert_eq!(ast_string, expected_ast); +} + +#[test] +fn simple_program_with_var_inout_can_be_parsed() { + let lexer = super::lex("PROGRAM buz VAR_IN_OUT x : INT; END_VAR END_PROGRAM"); + let result = parse(lexer).unwrap().0; + + let prg = &result.units[0]; + let variable_block = &prg.variable_blocks[0]; + let ast_string = format!("{:#?}", variable_block); + let expected_ast = r#"VariableBlock { + variables: [ + Variable { + name: "x", + data_type: DataTypeReference { + referenced_type: "INT", + }, + }, + ], + variable_block_type: InOut, +}"#; + assert_eq!(ast_string, expected_ast); +} + +#[test] +fn a_function_with_varargs_can_be_parsed() { + let lexer = super::lex("FUNCTION foo : INT VAR_INPUT x : INT; y : ...; END_VAR END_FUNCTION"); + let result = parse(lexer).unwrap().0; + + let prg = &result.units[0]; + let variable_block = &prg.variable_blocks[0]; + let ast_string = format!("{:#?}", variable_block); + let expected_ast = r#"VariableBlock { + variables: [ + Variable { + name: "x", + data_type: DataTypeReference { + referenced_type: "INT", + }, + }, + Variable { + name: "y", + data_type: DataTypeDefinition { + data_type: VarArgs { + referenced_type: None, + }, + }, + }, + ], + variable_block_type: Input, +}"#; + assert_eq!(ast_string, expected_ast); +} + +#[test] +fn a_function_with_typed_varargs_can_be_parsed() { + let lexer = + super::lex("FUNCTION foo : INT VAR_INPUT x : INT; y : INT...; END_VAR END_FUNCTION"); + let result = parse(lexer).unwrap().0; + + let prg = &result.units[0]; + let variable_block = &prg.variable_blocks[0]; + let ast_string = format!("{:#?}", variable_block); + let expected_ast = r#"VariableBlock { + variables: [ + Variable { + name: "x", + data_type: DataTypeReference { + referenced_type: "INT", + }, + }, + Variable { + name: "y", + data_type: DataTypeDefinition { + data_type: VarArgs { + referenced_type: Some( + DataTypeReference { + referenced_type: "INT", + }, + ), + }, + }, + }, + ], + variable_block_type: Input, +}"#; + assert_eq!(ast_string, expected_ast); +} + +#[test] +fn simple_struct_type_can_be_parsed() { + let (result, ..) = parse(super::lex( + r#" + TYPE SampleStruct : + STRUCT + One:INT; + Two:INT; + Three:INT; + END_STRUCT + END_TYPE + "#, + )) + .unwrap(); + + let ast_string = format!("{:#?}", &result.types[0]); + + let expected_ast = format!( + "{:#?}", + &UserTypeDeclaration { + data_type: DataType::StructType { + name: Some("SampleStruct".to_string(),), + variables: vec!( + Variable { + name: "One".to_string(), + data_type: DataTypeDeclaration::DataTypeReference { + referenced_type: "INT".to_string(), + location: SourceRange::undefined(), + }, + initializer: None, + location: SourceRange::undefined(), + }, + Variable { + name: "Two".to_string(), + data_type: DataTypeDeclaration::DataTypeReference { + referenced_type: "INT".to_string(), + location: SourceRange::undefined(), + }, + initializer: None, + location: SourceRange::undefined(), + }, + Variable { + name: "Three".to_string(), + data_type: DataTypeDeclaration::DataTypeReference { + referenced_type: "INT".to_string(), + location: SourceRange::undefined(), + }, + initializer: None, + location: SourceRange::undefined(), + }, + ), + }, + initializer: None, + location: (14..147).into(), + } + ); + assert_eq!(ast_string, expected_ast); +} + +#[test] +fn struct_with_inline_array_can_be_parsed() { + let (result, ..) = parse(super::lex( + r#" + TYPE SampleStruct : + STRUCT + One: ARRAY[0..1] OF INT; + END_STRUCT + END_TYPE + "#, + )) + .unwrap(); + + let ast_string = format!("{:#?}", &result.types[0]); + + let expected_ast = r#"UserTypeDeclaration { + data_type: StructType { + name: Some( + "SampleStruct", + ), + variables: [ + Variable { + name: "One", + data_type: DataTypeDefinition { + data_type: ArrayType { + name: None, + bounds: RangeStatement { + start: LiteralInteger { + value: "0", + }, + end: LiteralInteger { + value: "1", + }, + }, + referenced_type: DataTypeReference { + referenced_type: "INT", + }, + }, + }, + }, + ], + }, + initializer: None, + location: SourceRange { + range: 14..111, + }, +}"#; + assert_eq!(ast_string, expected_ast); +} + +#[test] +fn simple_enum_type_can_be_parsed() { + let (result, ..) = parse(super::lex( + r#" + TYPE SampleEnum : (red, yellow, green); + END_TYPE + "#, + )) + .unwrap(); + + let ast_string = format!("{:#?}", &result.types[0]); + + let epxtected_ast = &UserTypeDeclaration { + data_type: DataType::EnumType { + name: Some("SampleEnum".to_string()), + elements: vec!["red".to_string(), "yellow".to_string(), "green".to_string()], + }, + initializer: None, + location: (14..48).into(), + }; + let expected_string = format!("{:#?}", epxtected_ast); + assert_eq!(ast_string, expected_string); +} + +#[test] +fn type_alias_can_be_parsed() { + let (result, ..) = parse(super::lex( + r#" + TYPE + MyInt : INT; + END_TYPE + "#, + )) + .unwrap(); + + let ast_string = format!("{:#?}", &result.types[0]); + let exptected_ast = format!( + "{:#?}", + &UserTypeDeclaration { + data_type: DataType::SubRangeType { + name: Some("MyInt".to_string()), + referenced_type: "INT".to_string(), + bounds: None, + }, + initializer: None, + location: (27..39).into(), + } + ); + + assert_eq!(ast_string, exptected_ast); +} + +#[test] +fn array_type_can_be_parsed_test() { + let (result, ..) = parse(super::lex( + r#" + TYPE MyArray : ARRAY[0..8] OF INT; END_TYPE + "#, + )) + .unwrap(); + + let ast_string = format!("{:#?}", &result.types[0]); + + let expected_ast = format!( + "{:#?}", + &UserTypeDeclaration { + data_type: DataType::ArrayType { + name: Some("MyArray".to_string()), + bounds: Statement::RangeStatement { + start: Box::new(Statement::LiteralInteger { + value: "0".to_string(), + location: SourceRange::undefined(), + }), + end: Box::new(Statement::LiteralInteger { + value: "8".to_string(), + location: SourceRange::undefined(), + }), + }, + referenced_type: Box::new(DataTypeDeclaration::DataTypeReference { + referenced_type: "INT".to_string(), + location: SourceRange::undefined(), + }), + }, + initializer: None, + location: (18..47).into(), + } + ); + + assert_eq!(ast_string, expected_ast); +} + +#[test] +fn string_type_can_be_parsed_test() { + let (result, ..) = parse(super::lex( + r#" + TYPE MyString : STRING[253]; END_TYPE + TYPE MyString : STRING[253] := 'abc'; END_TYPE + "#, + )) + .unwrap(); + + let ast_string = format!("{:#?}", &result.types); + + let expected_ast = format!( + "{:#?}", + vec![ + UserTypeDeclaration { + data_type: DataType::StringType { + name: Some("MyString".to_string()), + size: Some(LiteralInteger { + value: "253".to_string(), + location: SourceRange::undefined(), + }), + is_wide: false, + }, + initializer: None, + location: (18..41).into(), + }, + UserTypeDeclaration { + data_type: DataType::StringType { + name: Some("MyString".to_string()), + size: Some(LiteralInteger { + value: "253".to_string(), + location: SourceRange::undefined(), + }), + is_wide: false, + }, + initializer: Some(Statement::LiteralString { + is_wide: false, + location: SourceRange::undefined(), + value: "abc".into(), + }), + location: (68..100).into(), + } + ] + ); + + assert_eq!(ast_string, expected_ast); +} + +#[test] +fn wide_string_type_can_be_parsed_test() { + let (result, ..) = parse(super::lex( + r#" + TYPE MyString : WSTRING[253]; END_TYPE + "#, + )) + .unwrap(); + + let ast_string = format!("{:#?}", &result.types[0]); + + let expected_ast = format!( + "{:#?}", + &UserTypeDeclaration { + data_type: DataType::StringType { + name: Some("MyString".to_string()), + size: Some(LiteralInteger { + value: "253".to_string(), + location: (10..11).into(), + }), + is_wide: true, + }, + initializer: None, + location: (18..42).into(), + } + ); + + assert_eq!(ast_string, expected_ast); +} + +#[test] +fn array_type_initialization_with_literals_can_be_parsed_test() { + let (result, ..) = parse(super::lex( + r#" + TYPE MyArray : ARRAY[0..2] OF INT := [1,2,3]; END_TYPE + "#, + )) + .unwrap(); + + let initializer = &result.types[0].initializer; + let ast_string = format!("{:#?}", &initializer); + + let expected_ast = r#"Some( + LiteralArray { + elements: Some( + ExpressionList { + expressions: [ + LiteralInteger { + value: "1", + }, + LiteralInteger { + value: "2", + }, + LiteralInteger { + value: "3", + }, + ], + }, + ), + }, +)"#; + assert_eq!(ast_string, expected_ast); +} + +#[test] +fn array_initializer_in_pou_can_be_parsed() { + let (result, ..) = parse(super::lex( + r#" + PROGRAM main + VAR + my_array: ARRAY[0..2] OF INT := [5,6,7]; + END_VAR + END_PROGRAM + "#, + )) + .unwrap(); + + let member = &result.units[0].variable_blocks[0].variables[0]; + if let Some(initializer) = &member.initializer { + let ast_string = format!("{:#?}", initializer); + let expected_ast = r#"LiteralArray { + elements: Some( + ExpressionList { + expressions: [ + LiteralInteger { + value: "5", + }, + LiteralInteger { + value: "6", + }, + LiteralInteger { + value: "7", + }, + ], + }, + ), +}"#; + assert_eq!(ast_string, expected_ast); + } else { + panic!("variable was not parsed as an Array"); + } +} + +#[test] +fn inline_struct_declaration_can_be_parsed() { + let (result, ..) = parse(super::lex( + r#" + VAR_GLOBAL + my_struct : STRUCT + One:INT; + Two:INT; + Three:INT; + END_STRUCT + END_VAR + "#, + )) + .unwrap(); + + let ast_string = format!("{:#?}", &result.global_vars[0].variables[0]); + let expected_ast = r#"Variable { + name: "my_struct", + data_type: DataTypeDefinition { + data_type: StructType { + name: None, + variables: [ + Variable { + name: "One", + data_type: DataTypeReference { + referenced_type: "INT", + }, + }, + Variable { + name: "Two", + data_type: DataTypeReference { + referenced_type: "INT", + }, + }, + Variable { + name: "Three", + data_type: DataTypeReference { + referenced_type: "INT", + }, + }, + ], + }, + }, +}"#; + + assert_eq!(ast_string, expected_ast); +} + +#[test] +fn inline_enum_declaration_can_be_parsed() { + let (result, ..) = parse(super::lex( + r#" + VAR_GLOBAL + my_enum : (red, yellow, green); + END_VAR + "#, + )) + .unwrap(); + + let ast_string = format!("{:#?}", &result.global_vars[0].variables[0]); + + let v = Variable { + name: "my_enum".to_string(), + data_type: DataTypeDeclaration::DataTypeDefinition { + data_type: DataType::EnumType { + name: None, + elements: vec!["red".to_string(), "yellow".to_string(), "green".to_string()], + }, + location: SourceRange::undefined(), + }, + initializer: None, + location: SourceRange::undefined(), + }; + let expected_ast = format!("{:#?}", &v); + assert_eq!(ast_string, expected_ast); +} + +#[test] +fn multilevel_inline_struct_and_enum_declaration_can_be_parsed() { + let (result, ..) = parse(super::lex( + r#" + VAR_GLOBAL + my_struct : STRUCT + inner_enum: (red, yellow, green); + inner_struct: STRUCT + field: INT; + END_STRUCT + END_STRUCT + END_VAR + "#, + )) + .unwrap(); + + let ast_string = format!("{:#?}", &result.global_vars[0].variables[0]); + let expected_ast = r#"Variable { + name: "my_struct", + data_type: DataTypeDefinition { + data_type: StructType { + name: None, + variables: [ + Variable { + name: "inner_enum", + data_type: DataTypeDefinition { + data_type: EnumType { + name: None, + elements: [ + "red", + "yellow", + "green", + ], + }, + }, + }, + Variable { + name: "inner_struct", + data_type: DataTypeDefinition { + data_type: StructType { + name: None, + variables: [ + Variable { + name: "field", + data_type: DataTypeReference { + referenced_type: "INT", + }, + }, + ], + }, + }, + }, + ], + }, + }, +}"#; + + assert_eq!(ast_string, expected_ast); +} + +#[test] +fn test_unexpected_token_error_message() { + let source = "PROGRAM prg + VAR ; + END_VAR + END_PROGRAM + "; + let lexer = super::lex(source); + let (_, diagnostics) = parse(lexer).unwrap(); + + assert_eq!( + format!("{:?}", diagnostics), + format!( + "{:?}", + vec![Diagnostic::unexpected_token_found( + "KeywordEndVar".into(), + "';'".into(), + (32..33).into() + ),] + ) + ); +} + +#[test] +fn programs_can_be_external() { + let lexer = super::lex("@EXTERNAL PROGRAM foo END_PROGRAM"); + let parse_result = parse(lexer).unwrap().0; + let implementation = &parse_result.implementations[0]; + assert_eq!(LinkageType::External, implementation.linkage); +} + +#[test] +fn test_unexpected_token_error_message2() { + let lexer = super::lex( + "SOME PROGRAM prg + VAR ; + END_VAR + END_PROGRAM + ", + ); + let parse_result = parse(lexer); + + if let Err { 0: msg } = parse_result { + assert_eq!( + Diagnostic::syntax_error("Unexpected token: 'SOME'".into(), (0..4).into()), + msg + ); + } else { + panic!("Expected parse error but didn't get one."); + } +} +#[test] +fn test_unclosed_body_error_message() { + let lexer = super::lex( + " + + PROGRAM My_PRG + + ", + ); + let (_, diagnostics) = parse(lexer).unwrap(); + + assert_eq!( + diagnostics, + vec![Diagnostic::unexpected_token_found( + "KeywordEndProgram".into(), + "''".into(), + (46..46).into() + )] + ); +} + +#[test] +fn initial_scalar_values_can_be_parsed() { + let lexer = super::lex( + " + VAR_GLOBAL + x : INT := 7; + END_VAR + + TYPE MyStruct : + STRUCT + a: INT := 69; + b: BOOL := TRUE; + c: REAL := 5.25; + END_STRUCT + END_TYPE + + TYPE MyInt : INT := 789; + END_TYPE + + PROGRAM MY_PRG + VAR + y : REAL := 11.3; + END_VAR + END_PROGRAM + ", + ); + let (parse_result, ..) = parse(lexer).unwrap(); + + let x = &parse_result.global_vars[0].variables[0]; + let expected = r#"Variable { + name: "x", + data_type: DataTypeReference { + referenced_type: "INT", + }, + initializer: Some( + LiteralInteger { + value: "7", + }, + ), +}"#; + assert_eq!(expected, format!("{:#?}", x).as_str()); + + let struct_type = &parse_result.types[0]; + let expected = r#"UserTypeDeclaration { + data_type: StructType { + name: Some( + "MyStruct", + ), + variables: [ + Variable { + name: "a", + data_type: DataTypeReference { + referenced_type: "INT", + }, + initializer: Some( + LiteralInteger { + value: "69", + }, + ), + }, + Variable { + name: "b", + data_type: DataTypeReference { + referenced_type: "BOOL", + }, + initializer: Some( + LiteralBool { + value: true, + }, + ), + }, + Variable { + name: "c", + data_type: DataTypeReference { + referenced_type: "REAL", + }, + initializer: Some( + LiteralReal { + value: "5.25", + }, + ), + }, + ], + }, + initializer: None, + location: SourceRange { + range: 92..260, + }, +}"#; + assert_eq!(expected, format!("{:#?}", struct_type).as_str()); + + let my_int_type = &parse_result.types[1]; + let expected = r#"UserTypeDeclaration { + data_type: SubRangeType { + name: Some( + "MyInt", + ), + referenced_type: "INT", + bounds: None, + }, + initializer: Some( + LiteralInteger { + value: "789", + }, + ), + location: SourceRange { + range: 300..319, + }, +}"#; + assert_eq!(expected, format!("{:#?}", my_int_type).as_str()); + + let y = &parse_result.units[0].variable_blocks[0].variables[0]; + let expected = r#"Variable { + name: "y", + data_type: DataTypeReference { + referenced_type: "REAL", + }, + initializer: Some( + LiteralReal { + value: "11.3", + }, + ), +}"#; + + assert_eq!(expected, format!("{:#?}", y).as_str()); +} + +#[test] +fn array_initializer_can_be_parsed() { + let lexer = super::lex( + " + VAR_GLOBAL + x : ARRAY[0..2] OF INT := [7,8,9]; + END_VAR + ", + ); + let (parse_result, ..) = parse(lexer).unwrap(); + let x = &parse_result.global_vars[0].variables[0]; + let expected = r#"Variable { + name: "x", + data_type: DataTypeDefinition { + data_type: ArrayType { + name: None, + bounds: RangeStatement { + start: LiteralInteger { + value: "0", + }, + end: LiteralInteger { + value: "2", + }, + }, + referenced_type: DataTypeReference { + referenced_type: "INT", + }, + }, + }, + initializer: Some( + LiteralArray { + elements: Some( + ExpressionList { + expressions: [ + LiteralInteger { + value: "7", + }, + LiteralInteger { + value: "8", + }, + LiteralInteger { + value: "9", + }, + ], + }, + ), + }, + ), +}"#; + assert_eq!(expected, format!("{:#?}", x).as_str()); +} + +#[test] +fn multi_dim_array_initializer_can_be_parsed() { + let lexer = super::lex( + " + VAR_GLOBAL + x : MyMultiArray := [[1,2],[3,4],[5,6]]; + END_VAR + ", + ); + let (parse_result, ..) = parse(lexer).unwrap(); + let x = &parse_result.global_vars[0].variables[0]; + let expected = r#"Variable { + name: "x", + data_type: DataTypeReference { + referenced_type: "MyMultiArray", + }, + initializer: Some( + LiteralArray { + elements: Some( + ExpressionList { + expressions: [ + LiteralArray { + elements: Some( + ExpressionList { + expressions: [ + LiteralInteger { + value: "1", + }, + LiteralInteger { + value: "2", + }, + ], + }, + ), + }, + LiteralArray { + elements: Some( + ExpressionList { + expressions: [ + LiteralInteger { + value: "3", + }, + LiteralInteger { + value: "4", + }, + ], + }, + ), + }, + LiteralArray { + elements: Some( + ExpressionList { + expressions: [ + LiteralInteger { + value: "5", + }, + LiteralInteger { + value: "6", + }, + ], + }, + ), + }, + ], + }, + ), + }, + ), +}"#; + assert_eq!(expected, format!("{:#?}", x).as_str()); +} + +#[test] +fn array_initializer_multiplier_can_be_parsed() { + let lexer = super::lex( + " + VAR_GLOBAL + x : ARRAY[0..2] OF INT := [3(7)]; + END_VAR + ", + ); + let (parse_result, ..) = parse(lexer).unwrap(); + let x = &parse_result.global_vars[0].variables[0]; + let expected = r#"Variable { + name: "x", + data_type: DataTypeDefinition { + data_type: ArrayType { + name: None, + bounds: RangeStatement { + start: LiteralInteger { + value: "0", + }, + end: LiteralInteger { + value: "2", + }, + }, + referenced_type: DataTypeReference { + referenced_type: "INT", + }, + }, + }, + initializer: Some( + LiteralArray { + elements: Some( + MultipliedStatement { + multiplier: 3, + element: LiteralInteger { + value: "7", + }, + }, + ), + }, + ), +}"#; + assert_eq!(expected, format!("{:#?}", x).as_str()); +} + +#[test] +fn struct_initializer_can_be_parsed() { + let lexer = super::lex( + " + VAR_GLOBAL + x : Point := (x := 1, y:= 2); + END_VAR + ", + ); + let (parse_result, ..) = parse(lexer).unwrap(); + let x = &parse_result.global_vars[0].variables[0]; + let expected = r#"Variable { + name: "x", + data_type: DataTypeReference { + referenced_type: "Point", + }, + initializer: Some( + ExpressionList { + expressions: [ + Assignment { + left: Reference { + name: "x", + }, + right: LiteralInteger { + value: "1", + }, + }, + Assignment { + left: Reference { + name: "y", + }, + right: LiteralInteger { + value: "2", + }, + }, + ], + }, + ), +}"#; + assert_eq!(expected, format!("{:#?}", x).as_str()); +} + +#[test] +fn string_variable_declaration_can_be_parsed() { + let lexer = super::lex( + " + VAR_GLOBAL + x : STRING; + y : STRING[500]; + wx : WSTRING; + wy : WSTRING[500]; + END_VAR + ", + ); + let (parse_result, ..) = parse(lexer).unwrap(); + let x = &parse_result.global_vars[0].variables[0]; + let expected = r#"Variable { + name: "x", + data_type: DataTypeDefinition { + data_type: StringType { + name: None, + is_wide: false, + size: None, + }, + }, +}"#; + assert_eq!(expected, format!("{:#?}", x).as_str()); + + let x = &parse_result.global_vars[0].variables[1]; + let expected = r#"Variable { + name: "y", + data_type: DataTypeDefinition { + data_type: StringType { + name: None, + is_wide: false, + size: Some( + LiteralInteger { + value: "500", + }, + ), + }, + }, +}"#; + assert_eq!(expected, format!("{:#?}", x).as_str()); + + let x = &parse_result.global_vars[0].variables[2]; + let expected = r#"Variable { + name: "wx", + data_type: DataTypeDefinition { + data_type: StringType { + name: None, + is_wide: true, + size: None, + }, + }, +}"#; + assert_eq!(expected, format!("{:#?}", x).as_str()); + + let x = &parse_result.global_vars[0].variables[3]; + let expected = r#"Variable { + name: "wy", + data_type: DataTypeDefinition { + data_type: StringType { + name: None, + is_wide: true, + size: Some( + LiteralInteger { + value: "500", + }, + ), + }, + }, +}"#; + assert_eq!(expected, format!("{:#?}", x).as_str()); +} + +#[test] +fn subrangetype_can_be_parsed() { + let lexer = super::lex( + " + VAR_GLOBAL + x : UINT(0..1000); + END_VAR + ", + ); + let (parse_result, ..) = parse(lexer).unwrap(); + + let x = &parse_result.global_vars[0].variables[0]; + let expected = Variable { + name: "x".to_string(), + data_type: DataTypeDeclaration::DataTypeDefinition { + data_type: DataType::SubRangeType { + name: None, + bounds: Some(Statement::RangeStatement { + start: Box::new(LiteralInteger { + value: "0".to_string(), + location: SourceRange::undefined(), + }), + end: Box::new(LiteralInteger { + value: "1000".to_string(), + location: SourceRange::undefined(), + }), + }), + referenced_type: "UINT".to_string(), + }, + location: SourceRange::undefined(), + }, + initializer: None, + location: (0..0).into(), + }; + assert_eq!(format!("{:#?}", expected), format!("{:#?}", x).as_str()); +} + +#[test] +fn varargs_parameters_can_be_parsed() { + let lexer = super::lex( + " + FUNCTION foo : DINT + VAR_INPUT + args1 : ...; + args2 : INT...; + END_VAR + END_FUNCTION + ", + ); + let (parse_result, diagnostics) = parse(lexer).unwrap(); + + assert_eq!( + format!("{:#?}", diagnostics), + format!("{:#?}", Vec::::new()).as_str() + ); + + let x = &parse_result.units[0]; + let expected = Pou { + name: "foo".into(), + pou_type: PouType::Function, + return_type: Some(DataTypeDeclaration::DataTypeReference { + referenced_type: "DINT".into(), + location: SourceRange::undefined(), + }), + variable_blocks: vec![VariableBlock { + variable_block_type: VariableBlockType::Input, + variables: vec![ + Variable { + name: "args1".into(), + data_type: DataTypeDeclaration::DataTypeDefinition { + data_type: DataType::VarArgs { + referenced_type: None, + }, + location: SourceRange::undefined(), + }, + initializer: None, + location: SourceRange::undefined(), + }, + Variable { + name: "args2".into(), + data_type: DataTypeDeclaration::DataTypeDefinition { + data_type: DataType::VarArgs { + referenced_type: Some(Box::new( + DataTypeDeclaration::DataTypeReference { + referenced_type: "INT".into(), + location: SourceRange::undefined(), + }, + )), + }, + location: SourceRange::undefined(), + }, + initializer: None, + location: SourceRange::undefined(), + }, + ], + }], + location: SourceRange::undefined(), + }; + assert_eq!(format!("{:#?}", expected), format!("{:#?}", x).as_str()); +} diff --git a/src/parser/tests/statement_parser_tests.rs b/src/parser/tests/statement_parser_tests.rs index 795c9bf2fe..dee89615fb 100644 --- a/src/parser/tests/statement_parser_tests.rs +++ b/src/parser/tests/statement_parser_tests.rs @@ -151,6 +151,7 @@ fn inline_enum_declaration_can_be_parsed() { name: None, elements: vec!["red".to_string(), "yellow".to_string(), "green".to_string()], }, + location: SourceRange::undefined(), }, initializer: None, location: SourceRange::undefined(), diff --git a/src/parser/tests/type_parser_tests.rs b/src/parser/tests/type_parser_tests.rs index 67588eb9d6..b9fba81a4a 100644 --- a/src/parser/tests/type_parser_tests.rs +++ b/src/parser/tests/type_parser_tests.rs @@ -28,6 +28,7 @@ fn simple_struct_type_can_be_parsed() { name: "One".to_string(), data_type: DataTypeDeclaration::DataTypeReference { referenced_type: "INT".to_string(), + location: SourceRange::undefined(), }, initializer: None, location: SourceRange::undefined(), @@ -36,6 +37,7 @@ fn simple_struct_type_can_be_parsed() { name: "Two".to_string(), data_type: DataTypeDeclaration::DataTypeReference { referenced_type: "INT".to_string(), + location: SourceRange::undefined(), }, initializer: None, location: SourceRange::undefined(), @@ -44,6 +46,7 @@ fn simple_struct_type_can_be_parsed() { name: "Three".to_string(), data_type: DataTypeDeclaration::DataTypeReference { referenced_type: "INT".to_string(), + location: SourceRange::undefined(), }, initializer: None, location: SourceRange::undefined(), @@ -51,6 +54,7 @@ fn simple_struct_type_can_be_parsed() { ), }, initializer: None, + location: SourceRange::undefined(), } ); assert_eq!(ast_string, expected_ast); @@ -71,6 +75,7 @@ fn simple_enum_type_can_be_parsed() { elements: vec!["red".to_string(), "yellow".to_string(), "green".to_string()], }, initializer: None, + location: SourceRange::undefined(), }; let expected_string = format!("{:#?}", epxtected_ast); assert_eq!(ast_string, expected_string); @@ -94,6 +99,7 @@ fn type_alias_can_be_parsed() { bounds: None, }, initializer: None, + location: SourceRange::undefined(), } ); @@ -128,9 +134,11 @@ fn array_type_can_be_parsed_test() { }, referenced_type: Box::new(DataTypeDeclaration::DataTypeReference { referenced_type: "INT".to_string(), + location: SourceRange::undefined(), }), }, initializer: None, + location: SourceRange::undefined(), } ); @@ -160,6 +168,7 @@ fn string_type_can_be_parsed_test() { is_wide: false, }, initializer: None, + location: SourceRange::undefined(), }, UserTypeDeclaration { data_type: DataType::StringType { @@ -177,6 +186,7 @@ fn string_type_can_be_parsed_test() { value: "abc".into(), id: 0, }), + location: SourceRange::undefined(), } ] ); @@ -205,6 +215,7 @@ fn wide_string_type_can_be_parsed_test() { is_wide: true, }, initializer: None, + location: SourceRange::undefined(), } ); @@ -241,6 +252,7 @@ fn subrangetype_can_be_parsed() { }), referenced_type: "UINT".to_string(), }, + location: SourceRange::undefined(), }, initializer: None, location: (0..0).into(), diff --git a/src/resolver.rs b/src/resolver.rs index f211881e90..a7dc07e9bd 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -12,10 +12,11 @@ use crate::{ AstId, CompilationUnit, DataType, DataTypeDeclaration, Operator, Pou, Statement, UserTypeDeclaration, Variable, }, - index::Index, + index::{ImplementationIndexEntry, ImplementationType, Index, VariableIndexEntry}, typesystem::{ self, get_bigger_type_borrow, DataTypeInformation, BOOL_TYPE, DATE_AND_TIME_TYPE, DATE_TYPE, DINT_TYPE, LINT_TYPE, REAL_TYPE, STRING_TYPE, TIME_OF_DAY_TYPE, TIME_TYPE, + VOID_TYPE, }, }; @@ -43,7 +44,7 @@ macro_rules! visit_all_statements { #[derive(Clone)] struct VisitorContext<'s> { /// the type_name of the context for a reference (e.g. `a.b` where `a`'s type is the context of `b`) - qualifier: Option<&'s str>, + qualifier: Option, /// optional context for references (e.g. `x` may mean `POU.x` if used inside `POU`'s body) pou: Option<&'s str>, /// special context of the left-hand-side of an assignment in call statements @@ -54,7 +55,7 @@ struct VisitorContext<'s> { impl<'s> VisitorContext<'s> { /// returns a copy of the current context and changes the `current_qualifier` to the given qualifier - fn with_qualifier(&self, qualifier: &'s str) -> VisitorContext<'s> { + fn with_qualifier(&self, qualifier: String) -> VisitorContext<'s> { VisitorContext { pou: self.pou, qualifier: Some(qualifier), @@ -66,7 +67,7 @@ impl<'s> VisitorContext<'s> { fn with_pou(&self, pou: &'s str) -> VisitorContext<'s> { VisitorContext { pou: Some(pou), - qualifier: self.qualifier, + qualifier: self.qualifier.clone(), call: self.call, } } @@ -75,7 +76,7 @@ impl<'s> VisitorContext<'s> { fn with_call(&self, lhs_pou: &'s str) -> VisitorContext<'s> { VisitorContext { pou: self.pou, - qualifier: self.qualifier, + qualifier: self.qualifier.clone(), call: Some(lhs_pou), } } @@ -87,9 +88,37 @@ pub struct TypeAnnotator<'i> { //context: VisitorContext<'i>, } +#[derive(Debug, Clone, PartialEq)] +pub enum StatementAnnotation { + /// an expression that resolves to a certain type (e.g. `a + b` --> `INT`) + ExpressionAnnotation { resulting_type: String }, + /// a reference that resolves to a declared variable (e.g. `a` --> `PLC_PROGRAM.a`) + VariableAnnotation { + resulting_type: String, + qualified_name: String, + }, + /// a reference to a function + FunctionAnnotation { + return_type: String, + qualified_name: String, + }, + /// a reference to a type (e.g. `INT`) + TypeAnnotation { type_name: String }, + /// a reference to a program call or reference (e.g. `PLC_PRG`) + ProgramAnnotation { qualified_name: String }, +} + +impl StatementAnnotation { + fn expression(type_name: &str) -> StatementAnnotation { + StatementAnnotation::ExpressionAnnotation { + resulting_type: type_name.to_string(), + } + } +} + pub struct AnnotationMap { - //TODO try to only borrow names? - type_map: IndexMap, // Statement -> type-name + /// maps a statement to the type it resolves to + type_map: IndexMap, } impl AnnotationMap { @@ -101,17 +130,53 @@ impl AnnotationMap { } /// annotates the given statement (using it's `get_id()`) with the given type-name - pub fn annotate_type(&mut self, s: &Statement, type_name: &str) { - self.type_map.insert(s.get_id(), type_name.to_string()); + pub fn annotate(&mut self, s: &Statement, annotation: StatementAnnotation) { + self.type_map.insert(s.get_id(), annotation); + } + + pub fn get(&self, s: &Statement) -> Option<&StatementAnnotation> { + self.type_map.get(&s.get_id()) } /// returns the annotated type or void if none was annotated - pub fn get_type<'i>(&self, s: &Statement, index: &'i Index) -> &'i typesystem::DataType { - self.type_map - .get(&s.get_id()) - .and_then(|name| index.get_type(name).ok()) + pub fn get_type_or_void<'i>( + &self, + s: &Statement, + index: &'i Index, + ) -> &'i typesystem::DataType { + self.get_type(s, index) .unwrap_or_else(|| index.get_void_type()) } + + /// returns the annotated type - for now only used by test + pub fn get_type<'i>( + &self, + s: &Statement, + index: &'i Index, + ) -> Option<&'i typesystem::DataType> { + self.get_annotation(s) + .and_then(|annotation| match annotation { + StatementAnnotation::ExpressionAnnotation { resulting_type } => { + Some(resulting_type.as_str()) + } + StatementAnnotation::VariableAnnotation { resulting_type, .. } => { + Some(resulting_type.as_str()) + } + StatementAnnotation::FunctionAnnotation { .. } => None, + StatementAnnotation::TypeAnnotation { .. } => None, + StatementAnnotation::ProgramAnnotation { .. } => None, + }) + .and_then(|type_name| index.get_type(type_name).ok()) + } + + /// returns the annotation of the given statement or none if it was not annotated + pub fn get_annotation(&self, s: &Statement) -> Option<&StatementAnnotation> { + self.type_map.get(&s.get_id()) + } + + pub fn has_type_annotation(&self, id: &usize) -> bool { + self.type_map.contains_key(id) + } } impl<'i> TypeAnnotator<'i> { @@ -172,6 +237,9 @@ impl<'i> TypeAnnotator<'i> { fn visit_variable(&mut self, ctx: &VisitorContext, variable: &Variable) { self.visit_data_type_declaration(ctx, &variable.data_type); + if let Some(initializer) = variable.initializer.as_ref() { + self.visit_statement(ctx, initializer); + } } fn visit_data_type_declaration( @@ -179,7 +247,7 @@ impl<'i> TypeAnnotator<'i> { ctx: &VisitorContext, declaration: &DataTypeDeclaration, ) { - if let DataTypeDeclaration::DataTypeDefinition { data_type } = declaration { + if let DataTypeDeclaration::DataTypeDefinition { data_type, .. } = declaration { self.visit_data_type(ctx, data_type); } } @@ -272,7 +340,7 @@ impl<'i> TypeAnnotator<'i> { visit_all_statements!(self, ctx, reference, access); let array_type = self .annotation_map - .get_type(reference, self.index) + .get_type_or_void(reference, self.index) .get_type_information(); if let DataTypeInformation::Array { inner_type_name, .. @@ -282,23 +350,26 @@ impl<'i> TypeAnnotator<'i> { .index .get_effective_type_by_name(inner_type_name) .get_name(); - self.annotation_map.annotate_type(statement, t); + + self.annotation_map + .annotate(statement, StatementAnnotation::expression(t)); } } Statement::BinaryExpression { left, right, .. } => { visit_all_statements!(self, ctx, left, right); let left = &self .annotation_map - .get_type(left, self.index) + .get_type_or_void(left, self.index) .get_type_information(); let right = &self .annotation_map - .get_type(right, self.index) + .get_type_or_void(right, self.index) .get_type_information(); if left.is_numerical() && right.is_numerical() { let bigger_name = get_bigger_type_borrow(left, right, self.index).get_name(); - self.annotation_map.annotate_type(statement, bigger_name); + self.annotation_map + .annotate(statement, StatementAnnotation::expression(bigger_name)); } } Statement::UnaryExpression { @@ -307,54 +378,97 @@ impl<'i> TypeAnnotator<'i> { self.visit_statement(ctx, value); let inner_type = self .annotation_map - .get_type(value, self.index) + .get_type_or_void(value, self.index) .get_type_information(); if operator == &Operator::Minus { //keep the same type but switch to signed if let Some(target) = typesystem::get_signed_type(inner_type, self.index) { - self.annotation_map - .annotate_type(statement, target.get_name()); + self.annotation_map.annotate( + statement, + StatementAnnotation::expression(target.get_name()), + ); } } else { - self.annotation_map - .annotate_type(statement, inner_type.get_name()); + self.annotation_map.annotate( + statement, + StatementAnnotation::expression(inner_type.get_name()), + ); } } Statement::Reference { name, .. } => { - let qualifier = ctx.qualifier.or(ctx.pou); - - let type_name = qualifier - .and_then(|pou| self.index.find_member(pou, name).map(|v| v.get_type_name())) - .or_else(|| { - self.index - .find_implementation(name) - .map(|_it| name.as_str() /* this is a pou */) - }) - .or_else(|| { - self.index - .find_global_variable(name) - .map(|v| v.get_type_name()) - }); - - let effective_type = - type_name.map(|name| self.index.get_effective_type_by_name(name)); - - if let Some(data_type) = effective_type { - self.annotation_map - .annotate_type(statement, data_type.get_name()); + let annotation = if let Some(qualifier) = ctx.qualifier.as_deref() { + // if we see a qualifier, we only consider [qualifier].[name] as candidates + self.index + .find_member(qualifier, name) + .map(|v| to_variable_annotation(v, self.index)) + } else { + // if we see no qualifier, we try some strategies ... + ctx.pou + .and_then(|qualifier| { + // ... first look at POU-local variables + self.index + .find_member(qualifier, name) + .map(|v| to_variable_annotation(v, self.index)) + }) + .or_else(|| { + // ... then try if we find a pou with that name (maybe it's a call?) + self.index.find_implementation(name).and_then(|it| { + match it.get_implementation_type() { + crate::index::ImplementationType::Program => { + Some(to_programm_annotation(it)) + } + crate::index::ImplementationType::Function => { + Some(to_function_annotation(it, self.index)) + } + crate::index::ImplementationType::FunctionBlock => { + Some(to_type_annotation(name)) + } + _ => None, + } + }) + }) + .or_else(|| { + // ... last option is a global variable, where we ignore the current pou's name as a qualifier + self.index + .find_global_variable(name) + .map(|v| to_variable_annotation(v, self.index)) + }) + }; + if let Some(annotation) = annotation { + self.annotation_map.annotate(statement, annotation) } } Statement::QualifiedReference { elements, .. } => { let mut ctx = ctx.clone(); for s in elements.iter() { self.visit_statement(&ctx, s); - ctx = - ctx.with_qualifier(self.annotation_map.get_type(s, self.index).get_name()); + + let qualifier = self + .annotation_map + .get_annotation(s) + .map(|it| match it { + StatementAnnotation::ExpressionAnnotation { resulting_type } => { + resulting_type.as_str() + } + StatementAnnotation::VariableAnnotation { resulting_type, .. } => { + resulting_type.as_str() + } + StatementAnnotation::FunctionAnnotation { .. } => VOID_TYPE, + StatementAnnotation::TypeAnnotation { type_name } => type_name.as_str(), + StatementAnnotation::ProgramAnnotation { qualified_name } => { + qualified_name.as_str() + } + }) + .unwrap_or_else(|| VOID_TYPE); + + ctx = ctx.with_qualifier(qualifier.to_string()); } //the last guy represents the type of the whole qualified expression - if let Some(t) = ctx.qualifier { - self.annotation_map.annotate_type(statement, t); + if let Some(last) = elements.last() { + if let Some(annotation) = self.annotation_map.get_annotation(last).cloned() { + self.annotation_map.annotate(statement, annotation); + } } } Statement::ExpressionList { expressions, .. } => expressions @@ -387,22 +501,53 @@ impl<'i> TypeAnnotator<'i> { .. } => { self.visit_statement(ctx, operator); - let operator_type_name = self - .annotation_map - .get_type(operator, self.index) - .get_name(); if let Some(s) = parameters.as_ref() { - let ctx = ctx.with_call(operator_type_name); + let operator_qualifier = self + .annotation_map + .get_annotation(operator) + .and_then(|it| match it { + StatementAnnotation::FunctionAnnotation { qualified_name, .. } => { + Some(qualified_name.clone()) + } + StatementAnnotation::ProgramAnnotation { qualified_name } => { + Some(qualified_name.clone()) + } + StatementAnnotation::VariableAnnotation { resulting_type, .. } => { + //lets see if this is a FB + if let Some(implementation) = + self.index.find_implementation(resulting_type.as_str()) + { + if let ImplementationType::FunctionBlock {} = + implementation.get_implementation_type() + { + return Some(resulting_type.clone()); + } + } + None + } + _ => { + println!("{:#?}", it); + None + } + }) + .unwrap_or_else(|| VOID_TYPE.to_string()); + let ctx = ctx.with_call(operator_qualifier.as_str()); + //need to clone the qualifier string because of borrow checker :-( - //todo look into this self.visit_statement(&ctx, s); } - if let Some(return_type) = self - .index - .find_return_type(operator_type_name) - .and_then(|it| self.index.find_effective_type(it)) + if let Some(StatementAnnotation::FunctionAnnotation { return_type, .. }) = + self.annotation_map.get(operator) { - self.annotation_map - .annotate_type(statement, return_type.get_name()); + if let Some(return_type) = self + .index + .find_type(return_type) + .and_then(|it| self.index.find_effective_type(it)) + .map(|it| it.get_name()) + { + self.annotation_map + .annotate(statement, StatementAnnotation::expression(return_type)); + } } } _ => { @@ -415,32 +560,41 @@ impl<'i> TypeAnnotator<'i> { fn visit_statement_literals(&mut self, ctx: &VisitorContext, statement: &Statement) { match statement { Statement::LiteralBool { .. } => { - self.annotation_map.annotate_type(statement, BOOL_TYPE) + self.annotation_map + .annotate(statement, StatementAnnotation::expression(BOOL_TYPE)); } + Statement::LiteralString { .. } => { - self.annotation_map.annotate_type(statement, STRING_TYPE); - } - Statement::LiteralInteger { value, .. } => { self.annotation_map - .annotate_type(statement, get_int_type_name_for(*value)); + .annotate(statement, StatementAnnotation::expression(STRING_TYPE)); } - Statement::LiteralTime { .. } => { - self.annotation_map.annotate_type(statement, TIME_TYPE) + Statement::LiteralInteger { value, .. } => { + self.annotation_map.annotate( + statement, + StatementAnnotation::expression(get_int_type_name_for(*value)), + ); } + Statement::LiteralTime { .. } => self + .annotation_map + .annotate(statement, StatementAnnotation::expression(TIME_TYPE)), Statement::LiteralTimeOfDay { .. } => { self.annotation_map - .annotate_type(statement, TIME_OF_DAY_TYPE); + .annotate(statement, StatementAnnotation::expression(TIME_OF_DAY_TYPE)); } Statement::LiteralDate { .. } => { - self.annotation_map.annotate_type(statement, DATE_TYPE); + self.annotation_map + .annotate(statement, StatementAnnotation::expression(DATE_TYPE)); } Statement::LiteralDateAndTime { .. } => { - self.annotation_map - .annotate_type(statement, DATE_AND_TIME_TYPE); + self.annotation_map.annotate( + statement, + StatementAnnotation::expression(DATE_AND_TIME_TYPE), + ); } Statement::LiteralReal { .. } => { //TODO when do we need a LREAL literal? - self.annotation_map.annotate_type(statement, REAL_TYPE); + self.annotation_map + .annotate(statement, StatementAnnotation::expression(REAL_TYPE)); } Statement::LiteralArray { elements: Some(elements), @@ -458,6 +612,42 @@ impl<'i> TypeAnnotator<'i> { } } +fn to_type_annotation(name: &str) -> StatementAnnotation { + StatementAnnotation::TypeAnnotation { + type_name: name.into(), + } +} + +fn to_programm_annotation(it: &ImplementationIndexEntry) -> StatementAnnotation { + StatementAnnotation::ProgramAnnotation { + qualified_name: it.get_call_name().into(), + } +} + +fn to_variable_annotation(v: &VariableIndexEntry, index: &Index) -> StatementAnnotation { + StatementAnnotation::VariableAnnotation { + qualified_name: v.get_qualified_name().into(), + resulting_type: index + .get_effective_type_by_name(v.get_type_name()) + .get_name() + .into(), + } +} + +fn to_function_annotation( + it: &crate::index::ImplementationIndexEntry, + index: &Index, +) -> StatementAnnotation { + StatementAnnotation::FunctionAnnotation { + qualified_name: it.get_call_name().into(), + return_type: index + .find_return_type(it.get_call_name()) + .map(|it| it.get_name()) + .unwrap_or(VOID_TYPE) + .into(), + } +} + fn get_int_type_name_for(value: i64) -> &'static str { if i32::MIN as i64 <= value && i32::MAX as i64 >= value { DINT_TYPE diff --git a/src/resolver/tests/resolve_expressions_tests.rs b/src/resolver/tests/resolve_expressions_tests.rs index 416635fb75..810b79c9b4 100644 --- a/src/resolver/tests/resolve_expressions_tests.rs +++ b/src/resolver/tests/resolve_expressions_tests.rs @@ -1,11 +1,13 @@ use core::panic; use crate::{ - ast::Statement, + ast::{DataType, Statement, UserTypeDeclaration}, + index::Index, resolver::{ tests::{annotate, parse}, - AnnotationMap, + AnnotationMap, StatementAnnotation, }, + typesystem::VOID_TYPE, }; #[test] @@ -22,10 +24,30 @@ fn binary_expressions_resolves_types() { let expected_types = vec!["DINT", "DINT", "LINT"]; - let none = "-".to_string(); - let types: Vec<&String> = statements + let types: Vec<&str> = statements .iter() - .map(|s| annotations.type_map.get(&s.get_id()).unwrap_or(&none)) + .map(|s| annotations.get_type_or_void(s, &index).get_name()) + .collect(); + + assert_eq!(expected_types, types); +} + +#[test] +fn unary_expressions_resolves_types() { + let (unit, index) = parse( + "PROGRAM PRG + NOT TRUE; + -(2+3); + END_PROGRAM", + ); + let annotations = annotate(&unit, &index); + let statements = &unit.implementations[0].statements; + + let expected_types = vec!["BOOL", "DINT"]; + + let types: Vec<&str> = statements + .iter() + .map(|s| annotations.get_type_or_void(s, &index).get_name()) .collect(); assert_eq!(expected_types, types); @@ -46,8 +68,8 @@ fn binary_expressions_resolves_types_with_floats() { let expected_types = vec!["REAL", "REAL", "REAL"]; for (i, s) in statements.iter().enumerate() { assert_eq!( - Some(&expected_types[i].to_string()), - annotations.type_map.get(&s.get_id()), + expected_types[i], + annotations.get_type_or_void(s, &index).get_name(), "{:#?}", s ); @@ -94,10 +116,9 @@ fn local_variables_resolves_types() { "BYTE", "WORD", "DWORD", "LWORD", "SINT", "USINT", "INT", "UINT", "DINT", "UDINT", "LINT", "ULINT", ]; - let nothing = "-".to_string(); - let type_names: Vec<&String> = statements + let type_names: Vec<&str> = statements .iter() - .map(|s| annotations.type_map.get(&s.get_id()).unwrap_or(¬hing)) + .map(|s| annotations.get_type_or_void(s, &index).get_name()) .collect(); assert_eq!(format!("{:?}", expected_types), format!("{:?}", type_names)); @@ -144,10 +165,9 @@ fn global_resolves_types() { "BYTE", "WORD", "DWORD", "LWORD", "SINT", "USINT", "INT", "UINT", "DINT", "UDINT", "LINT", "ULINT", ]; - let nothing = "-".to_string(); - let type_names: Vec<&String> = statements + let type_names: Vec<&str> = statements .iter() - .map(|s| annotations.type_map.get(&s.get_id()).unwrap_or(¬hing)) + .map(|s| annotations.get_type_or_void(s, &index).get_name()) .collect(); assert_eq!(format!("{:?}", expected_types), format!("{:?}", type_names)); @@ -194,10 +214,9 @@ fn resolve_binary_expressions() { "BYTE", "WORD", "DWORD", "LWORD", "SINT", "BYTE", "INT", "UINT", "DINT", "UDINT", "LINT", "ULINT", ]; - let nothing = "-".to_string(); - let type_names: Vec<&String> = statements + let type_names: Vec<&str> = statements .iter() - .map(|s| annotations.type_map.get(&s.get_id()).unwrap_or(¬hing)) + .map(|s| annotations.get_type_or_void(s, &index).get_name()) .collect(); assert_eq!(format!("{:?}", expected_types), format!("{:?}", type_names)); @@ -232,10 +251,9 @@ fn complex_expressions_resolve_types() { let statements = &unit.implementations[0].statements; let expected_types = vec!["LINT", "DINT", "REAL"]; - let nothing = "-".to_string(); - let type_names: Vec<&String> = statements + let type_names: Vec<&str> = statements .iter() - .map(|s| annotations.type_map.get(&s.get_id()).unwrap_or(¬hing)) + .map(|s| annotations.get_type_or_void(s, &index).get_name()) .collect(); assert_eq!(format!("{:?}", expected_types), format!("{:?}", type_names)); @@ -284,10 +302,9 @@ fn array_expressions_resolve_types() { "MyAliasArray", "INT", ]; - let nothing = "-".to_string(); - let type_names: Vec<&String> = statements + let type_names: Vec<&str> = statements .iter() - .map(|s| annotations.type_map.get(&s.get_id()).unwrap_or(¬hing)) + .map(|s| annotations.get_type_or_void(s, &index).get_name()) .collect(); assert_eq!(format!("{:?}", expected_types), format!("{:?}", type_names)); @@ -320,10 +337,9 @@ fn qualified_expressions_resolve_types() { let statements = &unit.implementations[1].statements; let expected_types = vec!["BYTE", "WORD", "DWORD", "LWORD", "WORD", "DWORD", "LWORD"]; - let nothing = "-".to_string(); - let type_names: Vec<&String> = statements + let type_names: Vec<&str> = statements .iter() - .map(|s| annotations.type_map.get(&s.get_id()).unwrap_or(¬hing)) + .map(|s| annotations.get_type_or_void(s, &index).get_name()) .collect(); assert_eq!(format!("{:?}", expected_types), format!("{:?}", type_names)); @@ -336,7 +352,7 @@ fn pou_expressions_resolve_types() { PROGRAM OtherPrg END_PROGRAM - FUNCTION OtherFunc + FUNCTION OtherFunc : INT END_FUNCTION FUNCTION_BLOCK OtherFuncBlock @@ -351,14 +367,33 @@ fn pou_expressions_resolve_types() { let annotations = annotate(&unit, &index); let statements = &unit.implementations[3].statements; - let expected_types = vec!["OtherPrg", "OtherFunc", "OtherFuncBlock"]; - let nothing = "-".to_string(); - let type_names: Vec<&String> = statements + //none of these pou's should really resolve to a type + let expected_types = vec![VOID_TYPE, VOID_TYPE, VOID_TYPE]; + let type_names: Vec<&str> = statements .iter() - .map(|s| annotations.type_map.get(&s.get_id()).unwrap_or(¬hing)) + .map(|s| annotations.get_type_or_void(s, &index).get_name()) .collect(); - assert_eq!(format!("{:?}", expected_types), format!("{:?}", type_names)); + + assert_eq!( + Some(&StatementAnnotation::ProgramAnnotation { + qualified_name: "OtherPrg".into() + }), + annotations.get_annotation(&statements[0]) + ); + assert_eq!( + Some(&StatementAnnotation::FunctionAnnotation { + qualified_name: "OtherFunc".into(), + return_type: "INT".into() + }), + annotations.get_annotation(&statements[1]) + ); + assert_eq!( + Some(&StatementAnnotation::TypeAnnotation { + type_name: "OtherFuncBlock".into() + }), + annotations.get_annotation(&statements[2]) + ); } #[test] @@ -379,35 +414,34 @@ fn assignment_expressions_resolve_types() { let annotations = annotate(&unit, &index); let statements = &unit.implementations[0].statements; - let nothing = "-".to_string(); - let expected_types = vec![¬hing, ¬hing]; - let type_names: Vec<&String> = statements + let expected_types = vec![VOID_TYPE, VOID_TYPE]; + let type_names: Vec<&str> = statements .iter() - .map(|s| annotations.type_map.get(&s.get_id()).unwrap_or(¬hing)) + .map(|s| annotations.get_type_or_void(s, &index).get_name()) .collect(); assert_eq!(format!("{:?}", expected_types), format!("{:?}", type_names)); if let Statement::Assignment { left, right, .. } = &statements[0] { assert_eq!( - annotations.type_map.get(&left.get_id()), - Some(&"INT".to_string()) + annotations.get_type_or_void(&left, &index).get_name(), + "INT" ); assert_eq!( - annotations.type_map.get(&right.get_id()), - Some(&"BYTE".to_string()) + annotations.get_type_or_void(&right, &index).get_name(), + "BYTE" ); } else { panic!("expected assignment") } if let Statement::Assignment { left, right, .. } = &statements[1] { assert_eq!( - annotations.type_map.get(&left.get_id()), - Some(&"LWORD".to_string()) + annotations.get_type_or_void(&left, &index).get_name(), + "LWORD" ); assert_eq!( - annotations.type_map.get(&right.get_id()), - Some(&"INT".to_string()) + annotations.get_type_or_void(&right, &index).get_name(), + "INT" ); } else { panic!("expected assignment") @@ -467,10 +501,9 @@ fn qualified_expressions_to_structs_resolve_types() { "DWORD", "LWORD", ]; - let nothing = "-".to_string(); - let type_names: Vec<&String> = statements + let type_names: Vec<&str> = statements .iter() - .map(|s| annotations.type_map.get(&s.get_id()).unwrap_or(¬hing)) + .map(|s| annotations.get_type_or_void(s, &index).get_name()) .collect(); assert_eq!(format!("{:?}", expected_types), format!("{:?}", type_names)); @@ -501,15 +534,79 @@ fn qualified_expressions_to_inlined_structs_resolve_types() { let statements = &unit.implementations[0].statements; let expected_types = vec!["__PRG_mys", "BYTE", "WORD", "DWORD", "LWORD"]; - let nothing = "-".to_string(); - let type_names: Vec<&String> = statements + let type_names: Vec<&str> = statements .iter() - .map(|s| annotations.type_map.get(&s.get_id()).unwrap_or(¬hing)) + .map(|s| annotations.get_type_or_void(s, &index).get_name()) .collect(); assert_eq!(format!("{:?}", expected_types), format!("{:?}", type_names)); } +#[test] +fn function_expression_resolves_to_the_function_itself_not_its_return_type() { + //GIVEN a reference to a function + let (unit, index) = parse( + " + FUNCTION foo : INT + END_FUNCTION + + PROGRAM PRG + foo; + END_PROGRAM + ", + ); + + //WHEN the AST is annotated + let annotations = annotate(&unit, &index); + let statements = &unit.implementations[1].statements; + + // THEN we expect it to be annotated with the function itself + let foo_annotation = annotations.get_annotation(&statements[0]); + assert_eq!( + Some(&StatementAnnotation::FunctionAnnotation { + qualified_name: "foo".into(), + return_type: "INT".into() + }), + foo_annotation + ); + + // AND we expect no type to be associated with the expression + let associated_type = annotations.get_type(&statements[0], &index); + assert_eq!(None, associated_type); +} + +#[test] +fn function_call_expression_resolves_to_the_function_itself_not_its_return_type() { + //GIVEN a reference to a function + let (unit, index) = parse( + " + FUNCTION foo : INT + END_FUNCTION + + PROGRAM PRG + foo(); + END_PROGRAM + ", + ); + + //WHEN the AST is annotated + let annotations = annotate(&unit, &index); + let statements = &unit.implementations[1].statements; + + // THEN we expect it to be annotated with the function itself + let foo_annotation = annotations.get_annotation(&statements[0]); + assert_eq!( + Some(&StatementAnnotation::ExpressionAnnotation { + resulting_type: "INT".into() + }), + foo_annotation + ); + + // AND we expect no type to be associated with the expression + let associated_type = annotations.get_type(&statements[0], &index); + assert_eq!(index.find_type("INT"), associated_type); +} + #[test] fn qualified_expressions_to_aliased_structs_resolve_types() { let (unit, index) = parse( @@ -566,15 +663,82 @@ fn qualified_expressions_to_aliased_structs_resolve_types() { "DWORD", "LWORD", ]; - let nothing = "-".to_string(); - let type_names: Vec<&String> = statements + let type_names: Vec<&str> = statements + .iter() + .map(|s| annotations.get_type_or_void(s, &index).get_name()) + .collect(); + + assert_eq!(format!("{:?}", expected_types), format!("{:?}", type_names)); +} + +#[test] +fn qualified_expressions_to_fbs_resolve_types() { + let (unit, index) = parse( + " + FUNCTION_BLOCK MyFb + VAR_INPUT + fb_b : SINT; + fb_i : INT; + fb_d : DINT; + END_VAR + END_FUNCTION_BLOCK + + PROGRAM PRG + VAR + fb : MyFb; + END_VAR + fb; + fb.fb_b; + fb.fb_i; + fb.fb_d; + END_PROGRAM", + ); + + let annotations = annotate(&unit, &index); + let statements = &unit.implementations[1].statements; + + let expected_types = vec!["MyFb", "SINT", "INT", "DINT"]; + let type_names: Vec<&str> = statements .iter() - .map(|s| annotations.type_map.get(&s.get_id()).unwrap_or(¬hing)) + .map(|s| annotations.get_type_or_void(s, &index).get_name()) .collect(); assert_eq!(format!("{:?}", expected_types), format!("{:?}", type_names)); } +#[test] +fn qualified_expressions_dont_fallback_to_globals() { + let (unit, index) = parse( + " + VAR_GLOBAL + x : DINT; + END_VAR + + TYPE MyStruct: STRUCT + y : INT; + END_STRUCT + END_TYPE + + PROGRAM PRG + VAR P : MyStruct; END_VAR + P.x; + P.y; + END_PROGRAM", + ); + + let annotations = annotate(&unit, &index); + let statements = &unit.implementations[0].statements; + + assert_eq!(None, annotations.get_annotation(&statements[0])); + assert_eq!( + Some(&StatementAnnotation::VariableAnnotation { + qualified_name: "MyStruct.y".into(), + resulting_type: "INT".into() + }), + annotations.get_annotation(&statements[1]) + ); +} + #[test] fn function_parameter_assignments_resolve_types() { let (unit, index) = parse( @@ -600,8 +764,14 @@ fn function_parameter_assignments_resolve_types() { let statements = &unit.implementations[1].statements; assert_eq!( - annotations.type_map.get(&statements[0].get_id()), - Some(&"INT".to_string()) + annotations + .get_type_or_void(&statements[0], &index) + .get_name(), + "INT" + ); + assert_eq!( + annotations.get_annotation(&statements[0]), + Some(&StatementAnnotation::expression("INT")) ); if let Statement::CallStatement { operator, @@ -609,32 +779,37 @@ fn function_parameter_assignments_resolve_types() { .. } = &statements[0] { + //make sure the call's operator resolved correctly assert_eq!( - annotations.type_map.get(&operator.get_id()), - Some(&"foo".to_string()) + annotations.get_type_or_void(operator, &index).get_name(), + VOID_TYPE + ); + assert_eq!( + annotations.get_annotation(operator), + Some(&StatementAnnotation::FunctionAnnotation { + qualified_name: "foo".into(), + return_type: "MyType".into() + }) ); if let Some(Statement::ExpressionList { expressions, .. }) = &**parameters { if let Statement::Assignment { left, right, .. } = &expressions[0] { + assert_eq!(annotations.get_type_or_void(left, &index).get_name(), "INT"); assert_eq!( - annotations.type_map.get(&left.get_id()), - Some(&"INT".to_string()) - ); - assert_eq!( - annotations.type_map.get(&right.get_id()), - Some(&"DINT".to_string()) + annotations.get_type_or_void(right, &index).get_name(), + "DINT" ); } else { panic!("assignment expected") } if let Statement::OutputAssignment { left, right, .. } = &expressions[1] { assert_eq!( - annotations.type_map.get(&left.get_id()), - Some(&"SINT".to_string()) + annotations.get_type_or_void(left, &index).get_name(), + "SINT" ); assert_eq!( - annotations.type_map.get(&right.get_id()), - Some(&"DINT".to_string()) + annotations.get_type_or_void(right, &index).get_name(), + "DINT" ); } else { panic!("assignment expected") @@ -676,14 +851,14 @@ fn nested_function_parameter_assignments_resolve_types() { let statements = &unit.implementations[2].statements; if let Statement::CallStatement { parameters, .. } = &statements[0] { //check the two parameters - assert_parameter_assignment(parameters, 0, "INT", "DINT", &annotations); - assert_parameter_assignment(parameters, 1, "BOOL", "REAL", &annotations); + assert_parameter_assignment(parameters, 0, "INT", "DINT", &annotations, &index); + assert_parameter_assignment(parameters, 1, "BOOL", "REAL", &annotations, &index); //check the inner call in the first parameter assignment of the outer call `x := baz(...)` if let Statement::Assignment { right, .. } = get_expression_from_list(parameters, 0) { if let Statement::CallStatement { parameters, .. } = right.as_ref() { // the left side here should be `x` - so lets see if it got mixed up with the outer call's `x` - assert_parameter_assignment(parameters, 0, "DINT", "DINT", &annotations); + assert_parameter_assignment(parameters, 0, "DINT", "DINT", &annotations, &index); } else { panic!("inner call") } @@ -695,6 +870,40 @@ fn nested_function_parameter_assignments_resolve_types() { } } +#[test] +fn type_initial_values_are_resolved() { + let (unit, index) = parse( + " + TYPE MyStruct : STRUCT + x : INT := 20; + y : BOOL := TRUE; + z : STRING := 'abc'; + END_STRUCT + END_TYPE + ", + ); + + let annotations = annotate(&unit, &index); + let UserTypeDeclaration { data_type, .. } = &unit.types[0]; + + if let DataType::StructType { variables, .. } = data_type { + assert_eq!( + Some(&StatementAnnotation::expression("DINT")), + annotations.get(variables[0].initializer.as_ref().unwrap()) + ); + assert_eq!( + Some(&StatementAnnotation::expression("BOOL")), + annotations.get(variables[1].initializer.as_ref().unwrap()) + ); + assert_eq!( + Some(&StatementAnnotation::expression("STRING")), + annotations.get(variables[2].initializer.as_ref().unwrap()) + ); + } else { + panic!("no datatype: {:#?}", data_type) + } +} + fn get_expression_from_list(stmt: &Option, index: usize) -> &Statement { if let Some(Statement::ExpressionList { expressions, .. }) = stmt { &expressions[index] @@ -709,16 +918,17 @@ fn assert_parameter_assignment( left_type: &str, right_type: &str, annotations: &AnnotationMap, + index: &Index, ) { if let Some(Statement::ExpressionList { expressions, .. }) = parameters { if let Statement::Assignment { left, right, .. } = &expressions[param_index] { assert_eq!( - annotations.type_map.get(&left.get_id()), - Some(&left_type.to_string()) + annotations.get_type_or_void(left, index).get_name(), + left_type ); assert_eq!( - annotations.type_map.get(&right.get_id()), - Some(&right_type.to_string()), + annotations.get_type_or_void(right, index).get_name(), + right_type ); } else { panic!("assignment expected") diff --git a/src/resolver/tests/resolve_literals_tests.rs b/src/resolver/tests/resolve_literals_tests.rs index c21576a485..a101f1981f 100644 --- a/src/resolver/tests/resolve_literals_tests.rs +++ b/src/resolver/tests/resolve_literals_tests.rs @@ -12,12 +12,16 @@ fn bool_literals_are_annotated() { let statements = &unit.implementations[0].statements; assert_eq!( - Some(&"BOOL".to_string()), - annotations.type_map.get(&statements[0].get_id()) + "BOOL", + annotations + .get_type_or_void(&statements[0], &index) + .get_name() ); assert_eq!( - Some(&"BOOL".to_string()), - annotations.type_map.get(&statements[1].get_id()) + "BOOL", + annotations + .get_type_or_void(&statements[1], &index) + .get_name() ); } @@ -34,8 +38,8 @@ fn string_literals_are_annotated() { for s in statements.iter() { assert_eq!( - Some(&"STRING".to_string()), - annotations.type_map.get(&s.get_id()) + "STRING", + annotations.get_type_or_void(&s, &index).get_name() ); } } @@ -58,10 +62,9 @@ fn int_literals_are_annotated() { let expected_types = vec!["DINT", "DINT", "DINT", "DINT", "DINT", "DINT", "LINT"]; - let none = "-".to_string(); - let types: Vec<&String> = statements + let types: Vec<&str> = statements .iter() - .map(|s| annotations.type_map.get(&s.get_id()).unwrap_or(&none)) + .map(|s| annotations.get_type_or_void(&s, &index).get_name()) .collect(); assert_eq!(expected_types, types); @@ -96,8 +99,8 @@ fn date_literals_are_annotated() { ]; for (i, s) in statements.iter().enumerate() { assert_eq!( - Some(&expected_types[i].to_string()), - annotations.type_map.get(&s.get_id()), + expected_types[i], + annotations.get_type_or_void(&s, &index).get_name(), "{:#?}", s ); @@ -118,8 +121,8 @@ fn real_literals_are_annotated() { let expected_types = vec!["REAL", "REAL"]; for (i, s) in statements.iter().enumerate() { assert_eq!( - Some(&expected_types[i].to_string()), - annotations.type_map.get(&s.get_id()), + expected_types[i].to_string(), + annotations.get_type_or_void(&s, &index).get_name(), "{:#?}", s ); diff --git a/src/typesystem.rs b/src/typesystem.rs index 78c7439c94..c3f720caca 100644 --- a/src/typesystem.rs +++ b/src/typesystem.rs @@ -35,6 +35,8 @@ pub const LREAL_TYPE: &str = "LREAL"; pub const STRING_TYPE: &str = "STRING"; pub const WSTRING_TYPE: &str = "WSTRING"; +pub const VOID_TYPE: &str = "VOID"; + #[derive(Debug, PartialEq)] pub struct DataType { pub name: String, diff --git a/src/validation.rs b/src/validation.rs new file mode 100644 index 0000000000..e5db412d7e --- /dev/null +++ b/src/validation.rs @@ -0,0 +1,261 @@ +use crate::{ + ast::{ + CompilationUnit, DataType, DataTypeDeclaration, Pou, SourceRange, Statement, + UserTypeDeclaration, Variable, VariableBlock, + }, + resolver::AnnotationMap, + Diagnostic, +}; + +use self::{ + pou_validator::PouValidator, stmt_validator::StatementValidator, + variable_validator::VariableValidator, +}; + +mod pou_validator; +mod stmt_validator; +mod variable_validator; + +#[cfg(test)] +mod tests; + +macro_rules! visit_all_statements { + ($self:expr, $context:expr, $last:expr ) => { + $self.visit_statement($context, $last); + }; + + ($self:expr, $context:expr, $head:expr, $($tail:expr), +) => { + $self.visit_statement($context, $head); + visit_all_statements!($self, $context, $($tail),+) + }; + } + +pub struct ValidationContext<'s> { + ast_annotation: &'s AnnotationMap, +} + +pub struct Validator { + //context: ValidationContext<'s>, + pou_validator: PouValidator, + variable_validator: VariableValidator, + stmt_validator: StatementValidator, +} + +impl Validator { + pub fn new() -> Validator { + Validator { + pou_validator: PouValidator::new(), + variable_validator: VariableValidator::new(), + stmt_validator: StatementValidator::new(), + } + } + + pub fn diagnostics(&mut self) -> Vec { + let mut all_diagnostics = Vec::new(); + all_diagnostics.append(&mut self.pou_validator.diagnostics); + all_diagnostics.append(&mut self.variable_validator.diagnostics); + all_diagnostics.append(&mut self.stmt_validator.diagnostics); + all_diagnostics + } + + pub fn visit_unit(&mut self, annotations: &AnnotationMap, unit: &CompilationUnit) { + let context = ValidationContext { + ast_annotation: annotations, + }; + + for pou in &unit.units { + self.visit_pou(&context, pou); + } + + for t in &unit.types { + self.visit_user_type_declaration(&context, t); + } + + for i in &unit.implementations { + i.statements + .iter() + .for_each(|s| self.visit_statement(&context, s)); + } + } + + pub fn visit_user_type_declaration( + &mut self, + _context: &ValidationContext, + user_data_type: &UserTypeDeclaration, + ) { + self.variable_validator + .validate_data_type(&user_data_type.data_type, &user_data_type.location); + } + + pub fn visit_pou(&mut self, context: &ValidationContext, pou: &Pou) { + self.pou_validator.validate_pou(pou); + + for block in &pou.variable_blocks { + self.visit_variable_container(context, block); + } + } + + pub fn visit_variable_container( + &mut self, + context: &ValidationContext, + container: &VariableBlock, + ) { + self.variable_validator.validate_variable_block(container); + + for variable in &container.variables { + self.visit_variable(context, variable); + } + } + + pub fn visit_variable(&mut self, context: &ValidationContext, variable: &Variable) { + self.variable_validator.validate_variable(variable); + + self.visit_data_type_declaration(context, &variable.data_type); + } + + pub fn visit_data_type_declaration( + &mut self, + context: &ValidationContext, + declaration: &DataTypeDeclaration, + ) { + self.variable_validator + .validate_data_type_declaration(declaration); + + if let DataTypeDeclaration::DataTypeDefinition { + data_type, + location, + } = declaration + { + self.visit_data_type(context, data_type, location); + } + } + + pub fn visit_data_type( + &mut self, + context: &ValidationContext, + data_type: &DataType, + location: &SourceRange, + ) { + self.variable_validator + .validate_data_type(data_type, location); + + match data_type { + DataType::StructType { variables, .. } => variables + .iter() + .for_each(|v| self.visit_variable(context, v)), + DataType::ArrayType { + referenced_type, .. + } => self.visit_data_type_declaration(context, referenced_type), + DataType::VarArgs { + referenced_type: Some(referenced_type), + } => { + self.visit_data_type_declaration(context, referenced_type.as_ref()); + } + _ => {} + } + } + + pub fn visit_statement(&mut self, context: &ValidationContext, statement: &Statement) { + match statement { + Statement::LiteralArray { + elements: Some(elements), + .. + } => self.visit_statement(context, elements.as_ref()), + Statement::MultipliedStatement { element, .. } => { + self.visit_statement(context, element) + } + Statement::QualifiedReference { elements, .. } => elements + .iter() + .for_each(|e| self.visit_statement(context, e)), + Statement::ArrayAccess { + reference, access, .. + } => { + visit_all_statements!(self, context, reference, access); + } + Statement::BinaryExpression { left, right, .. } => { + visit_all_statements!(self, context, left, right); + } + Statement::UnaryExpression { value, .. } => self.visit_statement(context, value), + Statement::ExpressionList { expressions, .. } => expressions + .iter() + .for_each(|e| self.visit_statement(context, e)), + Statement::RangeStatement { start, end, .. } => { + visit_all_statements!(self, context, start, end); + } + Statement::Assignment { left, right, .. } => { + self.visit_statement(context, left); + self.visit_statement(context, right); + } + Statement::OutputAssignment { left, right, .. } => { + self.visit_statement(context, left); + self.visit_statement(context, right); + } + Statement::CallStatement { + parameters, + operator, + .. + } => { + self.visit_statement(context, operator); + if let Some(s) = parameters.as_ref() { + self.visit_statement(context, s); + } + } + Statement::IfStatement { + blocks, else_block, .. + } => { + blocks.iter().for_each(|b| { + self.visit_statement(context, b.condition.as_ref()); + b.body.iter().for_each(|s| self.visit_statement(context, s)); + }); + else_block + .iter() + .for_each(|e| self.visit_statement(context, e)); + } + Statement::ForLoopStatement { + counter, + start, + end, + by_step, + body, + .. + } => { + visit_all_statements!(self, context, counter, start, end); + if let Some(by_step) = by_step { + self.visit_statement(context, by_step); + } + body.iter().for_each(|s| self.visit_statement(context, s)); + } + Statement::WhileLoopStatement { + condition, body, .. + } => { + self.visit_statement(context, condition); + body.iter().for_each(|s| self.visit_statement(context, s)); + } + Statement::RepeatLoopStatement { + condition, body, .. + } => { + self.visit_statement(context, condition); + body.iter().for_each(|s| self.visit_statement(context, s)); + } + Statement::CaseStatement { + selector, + case_blocks, + else_block, + .. + } => { + self.visit_statement(context, selector); + case_blocks.iter().for_each(|b| { + self.visit_statement(context, b.condition.as_ref()); + b.body.iter().for_each(|s| self.visit_statement(context, s)); + }); + else_block + .iter() + .for_each(|s| self.visit_statement(context, s)); + } + Statement::CaseCondition { condition, .. } => self.visit_statement(context, condition), + _ => {} + } + + self.stmt_validator.validate_statement(statement, context); + } +} diff --git a/src/validation/pou_validator.rs b/src/validation/pou_validator.rs new file mode 100644 index 0000000000..f97fcf3269 --- /dev/null +++ b/src/validation/pou_validator.rs @@ -0,0 +1,16 @@ +use crate::{ast::Pou, Diagnostic}; + +/// validates POUs +pub struct PouValidator { + pub diagnostics: Vec, +} + +impl PouValidator { + pub fn new() -> PouValidator { + PouValidator { + diagnostics: Vec::new(), + } + } + + pub fn validate_pou(&mut self, _pou: &Pou) {} +} diff --git a/src/validation/stmt_validator.rs b/src/validation/stmt_validator.rs new file mode 100644 index 0000000000..d2ecc8806c --- /dev/null +++ b/src/validation/stmt_validator.rs @@ -0,0 +1,41 @@ +use super::ValidationContext; +use crate::{ + ast::{SourceRange, Statement}, + Diagnostic, +}; + +/// validates control-statements, assignments + +pub struct StatementValidator { + pub diagnostics: Vec, +} + +impl StatementValidator { + pub fn new() -> StatementValidator { + StatementValidator { + diagnostics: Vec::new(), + } + } + + pub fn validate_statement(&mut self, statement: &Statement, context: &ValidationContext) { + if let Statement::Reference { + name, location, id, .. + } = statement + { + self.validate_reference(id, name, location, context); + } + } + + fn validate_reference( + &mut self, + id: &usize, + ref_name: &str, + location: &SourceRange, + context: &ValidationContext, + ) { + if !context.ast_annotation.has_type_annotation(id) { + self.diagnostics + .push(Diagnostic::unrseolved_reference(ref_name, location.clone())); + } + } +} diff --git a/src/validation/tests.rs b/src/validation/tests.rs new file mode 100644 index 0000000000..e20a30f876 --- /dev/null +++ b/src/validation/tests.rs @@ -0,0 +1,25 @@ +// Copyright (c) 2020 Ghaith Hachem and Mathias Rieder +use super::Validator; +use crate::{ + ast, + index::{self, Index}, + lexer::lex, + parser::parse, + resolver::TypeAnnotator, + Diagnostic, +}; + +mod reference_resolve_tests; + +pub fn parse_and_validate(src: &str) -> Vec { + let mut idx = Index::new(); + let (mut ast, _) = parse(lex(src)); + ast::pre_process(&mut ast); + idx.import(index::visitor::visit(&ast)); + + let annotations = TypeAnnotator::visit_unit(&idx, &ast); + + let mut validator = Validator::new(); + validator.visit_unit(&annotations, &ast); + validator.diagnostics() +} diff --git a/src/validation/tests/reference_resolve_tests.rs b/src/validation/tests/reference_resolve_tests.rs new file mode 100644 index 0000000000..aaaa83836c --- /dev/null +++ b/src/validation/tests/reference_resolve_tests.rs @@ -0,0 +1,237 @@ +use crate::{validation::tests::parse_and_validate, Diagnostic}; + +/// tests wheter simple local and global variables can be resolved and +/// errors are reported properly +#[test] +fn resolve_simple_variable_references() { + let diagnostics = parse_and_validate( + " + VAR_GLOBAL + ga : INT; + END_VAR + + PROGRAM prg + VAR a : INT; END_VAR + + a; + b; + ga; + gb; + + END_PROGRAM + ", + ); + + assert_eq!( + diagnostics, + vec![ + Diagnostic::unrseolved_reference("b", (168..169).into()), + Diagnostic::unrseolved_reference("gb", (207..209).into()), + ] + ); +} + +/// tests wheter functions and function parameters can be resolved and +/// errors are reported properly +#[test] +fn resolve_function_calls_and_parameters() { + let diagnostics = parse_and_validate( + " + PROGRAM prg + VAR a : INT; END_VAR + foo(a); + boo(c); + foo(x := a); + foo(x := c); + foo(y := a); + END_PROGRAM + + FUNCTION foo : INT + VAR_INPUT x : INT; END_VAR + END_FUNCTION + ", + ); + + assert_eq!( + diagnostics, + vec![ + Diagnostic::unrseolved_reference("boo", (101..104).into()), + Diagnostic::unrseolved_reference("c", (105..106).into()), + Diagnostic::unrseolved_reference("c", (163..164).into()), + Diagnostic::unrseolved_reference("y", (187..188).into()), + ] + ); +} + +/// tests wheter structs and struct member variables can be resolved and +/// errors are reported properly +#[test] +fn resole_struct_member_access() { + let diagnostics = parse_and_validate( + " + TYPE MySubStruct: STRUCT + subfield1: INT; + subfield2: INT; + subfield3: INT; + END_STRUCT + END_TYPE + + TYPE MyStruct: STRUCT + field1: INT; + field2: INT; + field3: INT; + sub: MySubStruct; + END_STRUCT + END_TYPE + + PROGRAM prg + VAR + a : INT; + s : MyStruct; + END_VAR + (* should be fine *) + s.field1; + s.field2; + s.field3; + + (* should not exist *) + s.field10; + s.field20; + s.field30; + + (* should be fine*) + s.sub.subfield1; + s.sub.subfield2; + s.sub.subfield3; + + (* should not exist*) + s.sub.subfield10; + s.sub.subfield20; + s.sub.subfield30; + END_PROGRAM + ", + ); + + assert_eq!( + diagnostics, + vec![ + Diagnostic::unrseolved_reference("field10", (694..701).into()), + Diagnostic::unrseolved_reference("field20", (721..728).into()), + Diagnostic::unrseolved_reference("field30", (748..755).into()), + Diagnostic::unrseolved_reference("subfield10", (955..965).into()), + Diagnostic::unrseolved_reference("subfield20", (989..999).into()), + Diagnostic::unrseolved_reference("subfield30", (1023..1033).into()), + ] + ); +} + +/// tests wheter function_block members can be resolved and +/// errors are reported properly +#[test] +fn resolve_function_block_calls_field_access() { + let diagnostics = parse_and_validate( + " + FUNCTION_BLOCK FB + VAR_INPUT + a,b,c : INT; + END_VAR + END_FUNCTION_BLOCK + + PROGRAM prg + VAR + s : FB; + END_VAR + s; + (* s.a; + s.b; + s.c; + s(a := 1, b := 2, c := 3); + s(a := s.a, b := s.b, c := s.c); + (* problem - x,y,z do not not exist *) + s(a := s.x, b := s.y, c := s.z); *) + END_PROGRAM + ", + ); + + assert_eq!(diagnostics, vec![]); +} + +/// tests wheter function_block types and member variables can be resolved and +/// errors are reported properly +#[test] +fn resolve_function_block_calls_in_structs_and_field_access() { + let diagnostics = parse_and_validate( + " + FUNCTION_BLOCK FB + VAR_INPUT + a,b,c : INT; + END_VAR + END_FUNCTION_BLOCK + + TYPE MyStruct: STRUCT + fb1: FB; + fb2: FB; + END_STRUCT + END_TYPE + + PROGRAM prg + VAR + s : MyStruct; + END_VAR + + s.fb1.a; + s.fb1.b; + s.fb1.c; + s.fb1(a := 1, b := 2, c := 3); + s.fb1(a := s.fb2.a, b := s.fb2.b, c := s.fb2.c); + (* problem - sb3 does not exist *) + s.fb1(a := s.fb3.a, b := s.fb3.b, c := s.fb3.c); + END_PROGRAM + ", + ); + + assert_eq!( + diagnostics, + vec![ + Diagnostic::unrseolved_reference("fb3", (650..653).into()), + Diagnostic::unrseolved_reference("a", (654..655).into()), + Diagnostic::unrseolved_reference("fb3", (664..667).into()), + Diagnostic::unrseolved_reference("b", (668..669).into()), + Diagnostic::unrseolved_reference("fb3", (678..681).into()), + Diagnostic::unrseolved_reference("c", (682..683).into()), + ] + ); +} + +/// tests wheter function's members cannot be access using the function's name as a qualifier +#[test] +fn resolve_function_members_via_qualifier() { + let diagnostics = parse_and_validate( + " + PROGRAM prg + VAR + s : MyStruct; + END_VAR + foo(a := 1, b := 2, c := 3); (* ok *) + foo.a; (* not ok *) + foo.b; (* not ok *) + foo.c; (* not ok *) + END_PROGRAM + + FUNCTION foo : INT + VAR_INPUT + a,b,c : INT; + END_VAR + END_FUNCTION + ", + ); + + assert_eq!( + diagnostics, + vec![ + Diagnostic::unrseolved_reference("a", (181..182).into()), + Diagnostic::unrseolved_reference("b", (217..218).into()), + Diagnostic::unrseolved_reference("c", (253..254).into()), + ] + ); +} diff --git a/src/validation/variable_validator.rs b/src/validation/variable_validator.rs new file mode 100644 index 0000000000..534dc1bb5f --- /dev/null +++ b/src/validation/variable_validator.rs @@ -0,0 +1,94 @@ +use crate::{ + ast::{DataType, DataTypeDeclaration, SourceRange, Variable, VariableBlock}, + Diagnostic, +}; + +/// validates variables & datatypes + +pub struct VariableValidator { + pub diagnostics: Vec, +} + +impl VariableValidator { + pub fn new() -> VariableValidator { + VariableValidator { + diagnostics: Vec::new(), + } + } + + pub fn validate_variable_block(&self, _block: &VariableBlock) {} + + pub fn validate_variable(&self, _variable: &Variable) {} + + pub fn validate_data_type_declaration(&self, _declaration: &DataTypeDeclaration) {} + + pub fn validate_data_type(&mut self, declaration: &DataType, location: &SourceRange) { + match declaration { + DataType::StructType { variables, .. } => { + if variables.is_empty() { + self.diagnostics + .push(Diagnostic::empty_variable_block(location.clone())); + } + } + DataType::EnumType { elements, .. } => { + if elements.is_empty() { + self.diagnostics + .push(Diagnostic::empty_variable_block(location.clone())); + } + } + _ => {} + } + } +} + +#[cfg(test)] +mod variable_validator_tests { + use crate::{validation::tests::parse_and_validate, Diagnostic}; + + #[test] + fn validate_empty_struct_declaration() { + let diagnostics = parse_and_validate( + " + TYPE the_struct : STRUCT END_STRUCT END_TYPE + + PROGRAM prg + VAR + my_struct : STRUCT + END_STRUCT + END_VAR + END_PROGRAM + ", + ); + + assert_eq!( + diagnostics, + vec![ + Diagnostic::empty_variable_block((14..44).into()), + Diagnostic::empty_variable_block((131..164).into()) + ] + ); + } + + #[test] + fn validate_empty_enum_declaration() { + let diagnostics = parse_and_validate( + " + TYPE my_enum : (); END_TYPE + + PROGRAM prg + VAR + my_enum : (); + END_VAR + END_PROGRAM + ", + ); + + assert_eq!( + diagnostics, + vec![ + Diagnostic::empty_variable_block((14..27).into()), + Diagnostic::empty_variable_block((112..114).into()) + ] + ); + } +}