Compare commits

...

4 Commits

Author SHA1 Message Date
59894a1b64 Add even more tests 2024-05-04 19:08:22 -05:00
6f47dc69b6 More tests 2024-05-04 18:34:44 -05:00
231187d1f1 Add tests 2024-05-04 18:22:36 -05:00
527b1b4af6 Organize files for semantic analysis 2024-05-04 12:28:15 -05:00
13 changed files with 279 additions and 90 deletions

View File

@ -43,7 +43,7 @@
- [x] Parse function declaration arguments (`Type id`) - [x] Parse function declaration arguments (`Type id`)
- [x] Parse function return datatype (`fun f() -> Type`) - [x] Parse function return datatype (`fun f() -> Type`)
- [x] Return parsing to variables to var/val - [x] Return parsing to variables to var/val
- [ ] Write tests - [x] Write tests
## v0.0.10 ## v0.0.10

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
error_handling::{semantic_error::SemanticError, MistiError}, error_handling::{semantic_error::SemanticError, MistiError},
semantic::{impls::SemanticCheck, symbol_table::SymbolEntry}, semantic::{impls::SemanticCheck, symbol_table::SymbolEntry, types::Typed},
syntax::ast::var_binding::Binding, syntax::ast::var_binding::Binding,
}; };
@ -26,8 +26,8 @@ impl SemanticCheck for Binding<'_> {
return Err(MistiError::Semantic(error)); return Err(MistiError::Semantic(error));
} }
todo!(""); // This gets the datatype of the assigned expression,
/* // to compare it later with the declared datatype.
let expression_datatype = self.expression.get_type(); let expression_datatype = self.expression.get_type();
let datatype = match self.datatype { let datatype = match self.datatype {
@ -53,6 +53,5 @@ impl SemanticCheck for Binding<'_> {
scope.insert(binding_name.clone(), SymbolEntry::new_variable(datatype)); scope.insert(binding_name.clone(), SymbolEntry::new_variable(datatype));
Ok(()) Ok(())
*/
} }
} }

View File

@ -1,2 +1,3 @@
pub mod binding; pub mod binding;
pub mod function_declaration; pub mod function_declaration;
pub mod top_level_declaration;

View File

@ -0,0 +1,12 @@
use crate::{semantic::impls::SemanticCheck, syntax::ast::TopLevelDeclaration};
impl SemanticCheck for TopLevelDeclaration<'_> {
fn check_semantics(&self, scope: &crate::semantic::symbol_table::SymbolTable) -> Result<(), crate::error_handling::MistiError> {
match self {
TopLevelDeclaration::Binding(binding) => binding.check_semantics(scope),
TopLevelDeclaration::FunctionDeclaration(function) => function.check_semantics(scope),
_ => panic!("Not implemented"),
}
}
}

View File

@ -1,11 +1,8 @@
use crate::{ use crate::{error_handling::MistiError, syntax::ast::ModuleAST};
error_handling::semantic_error::SemanticError,
error_handling::MistiError,
syntax::ast::{ModuleAST, TopLevelDeclaration},
};
use super::symbol_table::{SymbolEntry, SymbolTable}; use super::symbol_table::SymbolTable;
/// Allows this type to have it's semantics checked.
pub trait SemanticCheck { pub trait SemanticCheck {
fn check_semantics(&self, scope: &SymbolTable) -> Result<(), MistiError>; fn check_semantics(&self, scope: &SymbolTable) -> Result<(), MistiError>;
} }
@ -20,74 +17,3 @@ impl SemanticCheck for ModuleAST<'_> {
Ok(()) Ok(())
} }
} }
impl SemanticCheck for TopLevelDeclaration<'_> {
fn check_semantics(&self, scope: &SymbolTable) -> Result<(), MistiError> {
match self {
TopLevelDeclaration::Binding(binding) => {
let binding_name = &binding.identifier.value;
if scope.test(binding_name) {
let error = SemanticError {
error_start: binding.identifier.position,
error_end: binding.identifier.get_end_position(),
reason: format!(
"Duplicated: A symbol with name {} was already defined",
binding_name
),
};
return Err(MistiError::Semantic(error));
}
let datatype = match binding.datatype {
Some(t) => t,
None => {
let error = SemanticError {
error_start: binding.identifier.position,
error_end: binding.identifier.get_end_position(),
reason: format!(
"The variable `{}` didn't define a datatype. Datatype inference is not implemented.",
binding_name
),
};
return Err(MistiError::Semantic(error));
}
};
scope.insert(
binding_name.clone(),
SymbolEntry::new_variable(datatype.value.clone()),
);
Ok(())
}
TopLevelDeclaration::FunctionDeclaration(function) => {
let function_name = function.identifier.value.clone();
// Check that the function is not already defined
if scope.test(&function_name) {
let error = SemanticError {
error_start: function.identifier.position,
error_end: function.identifier.get_end_position(),
reason: format!(
"Duplicated: A symbol with name {} was already defined",
function_name
),
};
return Err(MistiError::Semantic(error));
}
scope.insert(
function_name,
SymbolEntry::new_function(vec![], "Unit".into()),
);
Ok(())
}
_ => panic!("Not implemented"),
}
}
}

View File

@ -3,6 +3,7 @@ use crate::{error_handling::MistiError, syntax::ast::ModuleAST};
mod impls; mod impls;
mod symbol_table; mod symbol_table;
mod checks; mod checks;
mod types;
use impls::SemanticCheck; use impls::SemanticCheck;

View File

@ -0,0 +1,10 @@
use crate::syntax::ast::Expression;
use super::Typed;
impl Typed for Expression<'_> {
fn get_type(&self) -> String {
todo!()
}
}

11
src/semantic/types/mod.rs Normal file
View File

@ -0,0 +1,11 @@
// This crate provides an interface and implementations
// for determining the datatypes of the language constructs.
use crate::lexic::token::Token;
mod expression;
pub trait Typed {
fn get_type(&self) -> String;
}

View File

@ -60,26 +60,23 @@ pub fn try_parse<'a>(tokens: &'a Vec<Token>, pos: usize) -> ParsingResult<Bindin
return Err(ParsingError::Err(SyntaxError { return Err(ParsingError::Err(SyntaxError {
error_start: token.position, error_start: token.position,
error_end: token.get_end_position(), error_end: token.get_end_position(),
reason: "??".into(), reason: "There should be an identifier after a binding".into(),
})); }));
} }
Err(ParsingError::Err(error)) => {
return Err(ParsingError::Err(error));
}
_ => { _ => {
// The parser didn't find an Identifier after VAL/VAR or the Datatype // The parser didn't find an Identifier after VAL/VAR or the Datatype
match (binding_token, datatype) { match (binding_token, datatype) {
(Some(binding_token), _) => { (Some(binding_token), None) => {
return Err(ParsingError::Err(SyntaxError { return Err(ParsingError::Err(SyntaxError {
reason: format!( reason: format!(
"There should be an identifier after a `{}` token", "There should be an identifier after a `{}` token",
if is_var { "val" } else { "var" } if is_var { "var" } else { "val" }
), ),
error_start: binding_token.position, error_start: binding_token.position,
error_end: binding_token.get_end_position(), error_end: binding_token.get_end_position(),
})); }));
} }
(None, Some(datatype_token)) => { (_, Some(datatype_token)) => {
return Err(ParsingError::Err(SyntaxError { return Err(ParsingError::Err(SyntaxError {
reason: "There should be an identifier after the datatype".into(), reason: "There should be an identifier after the datatype".into(),
error_start: datatype_token.position, error_start: datatype_token.position,
@ -87,7 +84,7 @@ pub fn try_parse<'a>(tokens: &'a Vec<Token>, pos: usize) -> ParsingResult<Bindin
})); }));
} }
_ => { _ => {
panic!("Illegal parser state: binding_token and datatype are both None") unreachable!("Illegal parser state: binding_token and datatype are both None")
} }
}; };
} }
@ -267,6 +264,7 @@ mod tests {
Err(ParsingError::Err(error)) => { Err(ParsingError::Err(error)) => {
assert_eq!(4, error.error_start); assert_eq!(4, error.error_start);
assert_eq!(11, error.error_end); assert_eq!(11, error.error_end);
assert_eq!("There should be an identifier after a binding", error.reason);
} }
_ => panic!("Error expected"), _ => panic!("Error expected"),
} }
@ -285,4 +283,65 @@ mod tests {
_ => panic!("Error expected"), _ => panic!("Error expected"),
} }
} }
#[test]
fn should_return_error_when_identifier_is_empty() {
let tokens = get_tokens(&String::from("val String ")).unwrap();
let binding = try_parse(&tokens, 0);
match binding {
Err(ParsingError::Err(error)) => {
assert_eq!(4, error.error_start);
assert_eq!(10, error.error_end);
assert_eq!("There should be an identifier after the datatype", error.reason);
}
_ => panic!("Error expected"),
}
}
#[test]
fn should_return_error_when_identifier_is_empty_2() {
let tokens = get_tokens(&String::from("val ")).unwrap();
let binding = try_parse(&tokens, 0);
match binding {
Err(ParsingError::Err(error)) => {
assert_eq!(0, error.error_start);
assert_eq!(3, error.error_end);
assert_eq!("There should be an identifier after a `val` token", error.reason);
}
_ => panic!("Error expected"),
}
}
#[test]
fn should_error_when_equal_op_is_missing() {
let tokens = get_tokens(&String::from("val identifier ")).unwrap();
let binding = try_parse(&tokens, 0);
match binding {
Err(ParsingError::Err(error)) => {
assert_eq!(4, error.error_start);
assert_eq!(14, error.error_end);
assert_eq!("There should be an equal sign `=` after the identifier", error.reason);
}
_ => panic!("Error expected"),
}
}
#[test]
fn should_error_when_exp_is_empty() {
let tokens = get_tokens(&String::from("val identifier = ")).unwrap();
let binding = try_parse(&tokens, 0);
match binding {
Err(ParsingError::Err(error)) => {
assert_eq!(15, error.error_start);
assert_eq!(16, error.error_end);
assert_eq!("Expected an expression after the equal `=` operator", error.reason);
}
_ => panic!("Error expected"),
}
}
} }

View File

@ -47,3 +47,45 @@ fn parse_many<'a>(
_ => Ok((prev_expr, pos)), _ => Ok((prev_expr, pos)),
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::lexic::get_tokens;
#[test]
fn should_parse_comparison() {
let tokens = get_tokens(&String::from("a >= b")).unwrap();
let result = try_parse(&tokens, 0);
match result {
Ok((expr, _)) => match expr {
Expression::BinaryOperator(exp1, exp2, op) => {
match (*exp1, *exp2) {
(Expression::Identifier(id1), Expression::Identifier(id2)) => {
assert_eq!("a", id1);
assert_eq!("b", id2);
}
_ => panic!("Expected 2 identifiers"),
}
assert_eq!(">=", op)
}
_ => panic!("Expected a binary expression with 2 identifiers"),
},
Err(err) => {
panic!("{:?}", err)
}
}
}
#[test]
fn should_not_parse_unfinished_comparison() {
let tokens = get_tokens(&String::from("a >=")).unwrap();
let result = try_parse(&tokens, 0);
match result {
Err(ParsingError::Unmatched) => assert!(true),
_ => panic!("Expected an Unmatched error"),
}
}
}

View File

@ -42,3 +42,45 @@ fn parse_many<'a>(
_ => Ok((prev_expr, pos)), _ => Ok((prev_expr, pos)),
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::lexic::get_tokens;
#[test]
fn should_parse_comparison() {
let tokens = get_tokens(&String::from("a == b")).unwrap();
let result = try_parse(&tokens, 0);
match result {
Ok((expr, _)) => match expr {
Expression::BinaryOperator(exp1, exp2, op) => {
match (*exp1, *exp2) {
(Expression::Identifier(id1), Expression::Identifier(id2)) => {
assert_eq!("a", id1);
assert_eq!("b", id2);
}
_ => panic!("Expected 2 identifiers"),
}
assert_eq!("==", op)
}
_ => panic!("Expected a binary expression with 2 identifiers"),
},
Err(err) => {
panic!("{:?}", err)
}
}
}
#[test]
fn should_not_parse_unfinished_comparison() {
let tokens = get_tokens(&String::from("a ==")).unwrap();
let result = try_parse(&tokens, 0);
match result {
Err(ParsingError::Unmatched) => assert!(true),
_ => panic!("Expected an Unmatched error")
}
}
}

View File

@ -42,3 +42,47 @@ fn parse_many<'a>(
_ => Ok((prev_expr, pos)), _ => Ok((prev_expr, pos)),
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::lexic::get_tokens;
#[test]
fn should_parse_comparison() {
let tokens = get_tokens(&String::from("a * b")).unwrap();
let result = try_parse(&tokens, 0);
match result {
Ok((expr, _)) => match expr {
Expression::BinaryOperator(exp1, exp2, op) => {
match (*exp1, *exp2) {
(Expression::Identifier(id1), Expression::Identifier(id2)) => {
assert_eq!("a", id1);
assert_eq!("b", id2);
}
_ => panic!("Expected 2 identifiers"),
}
assert_eq!("*", op)
}
_ => panic!("Expected a binary expression with 2 identifiers"),
},
Err(err) => {
panic!("{:?}", err)
}
}
}
#[test]
fn should_not_parse_unfinished_comparison() {
let tokens = get_tokens(&String::from("a /")).unwrap();
let result = try_parse(&tokens, 0);
match result {
Err(ParsingError::Unmatched) => assert!(true),
_ => panic!("Expected an Unmatched error"),
}
}
}

View File

@ -42,3 +42,45 @@ fn parse_many<'a>(
_ => Ok((prev_expr, pos)), _ => Ok((prev_expr, pos)),
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::lexic::get_tokens;
#[test]
fn should_parse_comparison() {
let tokens = get_tokens(&String::from("a + b")).unwrap();
let result = try_parse(&tokens, 0);
match result {
Ok((expr, _)) => match expr {
Expression::BinaryOperator(exp1, exp2, op) => {
match (*exp1, *exp2) {
(Expression::Identifier(id1), Expression::Identifier(id2)) => {
assert_eq!("a", id1);
assert_eq!("b", id2);
}
_ => panic!("Expected 2 identifiers"),
}
assert_eq!("+", op)
}
_ => panic!("Expected a binary expression with 2 identifiers"),
},
Err(err) => {
panic!("{:?}", err)
}
}
}
#[test]
fn should_not_parse_unfinished_comparison() {
let tokens = get_tokens(&String::from("a -")).unwrap();
let result = try_parse(&tokens, 0);
match result {
Err(ParsingError::Unmatched) => assert!(true),
_ => panic!("Expected an Unmatched error"),
}
}
}