feat: typecheck conditionals and for loops

This commit is contained in:
Araozu 2024-08-29 09:54:53 -05:00
parent 441db4bcad
commit d4f34b62e2
8 changed files with 135 additions and 3 deletions

View File

@ -26,6 +26,8 @@
- [x] Parse for loops - [x] Parse for loops
- [x] Parse while loops - [x] Parse while loops
- [x] Typecheck arrays - [x] Typecheck arrays
- [x] Typecheck if/else if/else
- [x] Typecheck for loops
## v0.1.1 ## v0.1.1

View File

@ -1,9 +1,19 @@
use crate::{ use crate::{
error_handling::MistiError, error_handling::MistiError,
semantic::{impls::SemanticCheck, symbol_table::SymbolTable}, semantic::{impls::SemanticCheck, symbol_table::SymbolTable},
syntax::ast::BlockMember, syntax::ast::{Block, BlockMember},
}; };
impl SemanticCheck for Block<'_> {
fn check_semantics(&self, scope: &SymbolTable) -> Result<(), MistiError> {
for member in &self.members {
member.check_semantics(scope)?;
}
Ok(())
}
}
impl<'a> SemanticCheck for BlockMember<'a> { impl<'a> SemanticCheck for BlockMember<'a> {
// TODO: A block may contain a function declaration statement, // TODO: A block may contain a function declaration statement,
// but (afaik) those are not allowed inside conditionals/loops // but (afaik) those are not allowed inside conditionals/loops

View File

@ -0,0 +1,57 @@
use crate::{
error_handling::{semantic_error::SemanticError, MistiError},
semantic::{
impls::SemanticCheck,
symbol_table::SymbolTable,
types::{Type, Typed},
},
syntax::ast::{Conditional, Positionable},
};
impl SemanticCheck for Conditional<'_> {
fn check_semantics(&self, scope: &SymbolTable) -> Result<(), MistiError> {
let bool_type = Type::Value(String::from("Bool"));
// Check if condition is a Bool
let if_condition = &self.if_member.condition;
let if_condition_type = if_condition.get_type(scope)?;
if !if_condition_type.equals(&bool_type) {
let (error_start, error_end) = if_condition.get_position();
return Err(MistiError::Semantic(SemanticError {
error_start,
error_end,
reason: format!(
"Expected a condition of type Bool, found {:?}",
if_condition_type
),
}));
}
// Check if block
let if_block = &self.if_member.body;
if_block.check_semantics(scope)?;
// Check all else if
for else_if_member in self.else_if_members.iter() {
let condition = &else_if_member.condition;
let condition_type = condition.get_type(scope)?;
if !condition_type.equals(&bool_type) {
let (error_start, error_end) = condition.get_position();
return Err(MistiError::Semantic(SemanticError {
error_start,
error_end,
reason: format!("Expected a condition of type Bool, found {:?}", condition_type),
}));
}
else_if_member.body.check_semantics(scope)?;
}
// Check else
if let Some(else_block) = &self.else_block {
else_block.check_semantics(scope)?;
}
Ok(())
}
}

View File

@ -0,0 +1,59 @@
use crate::{
error_handling::{semantic_error::SemanticError, MistiError},
semantic::{
impls::SemanticCheck,
symbol_table::SymbolTable,
types::{Type, Typed},
},
syntax::ast::{loops::ForLoop, Positionable},
};
impl SemanticCheck for ForLoop<'_> {
fn check_semantics(&self, scope: &SymbolTable) -> Result<(), MistiError> {
// TODO: Implement a generic Collection interface?
// Use Traversable from PHP?
// for now this is restricted to arrays
let collection_type = self.collection.get_type(scope)?;
// This will be a single Type,
let item_type = match collection_type {
Type::Generic(t, type_params) if t == "Array" => {
if type_params.len() != 1 {
unreachable!(
"Compiler error: found an Array[] with more than 1 type parameter: {:?}",
type_params
)
}
type_params
}
_ => {
// error, types other than an Array are not supported
let (error_start, error_end) = self.collection.get_position();
return Err(MistiError::Semantic(SemanticError {
error_start,
error_end,
reason: format!("Only Array[T] are allowed as a for-loop collection."),
}));
}
};
let item_type = &item_type[0];
let loop_scope = SymbolTable::new_from_parent(&scope);
// Create a new scope, insert key,value
if let Some(key) = self.key {
// Since for now this only supports Array[T], key
// can only be a Int
loop_scope.insert(key.value.clone(), Type::Value("Int".into()));
}
// TODO: Add lifetimes to scoping instead of cloning
loop_scope.insert(self.value.value.clone(), item_type.clone());
// Check every statement inside the block
self.body.check_semantics(&loop_scope)?;
// Ok
Ok(())
}
}

View File

@ -1,5 +1,7 @@
pub mod binding; pub mod binding;
pub mod block; pub mod block;
pub mod conditional;
pub mod expression; pub mod expression;
pub mod for_loop;
pub mod function_declaration; pub mod function_declaration;
pub mod top_level_declaration; pub mod top_level_declaration;

View File

@ -22,8 +22,8 @@ impl SemanticCheck for Statement<'_> {
match self { match self {
Statement::Binding(b) => b.check_semantics(scope), Statement::Binding(b) => b.check_semantics(scope),
Statement::FnDecl(f) => f.check_semantics(scope), Statement::FnDecl(f) => f.check_semantics(scope),
Statement::Conditional(_) => unimplemented!("check conditional"), Statement::Conditional(c) => c.check_semantics(scope),
Statement::ForLoop(_) => unimplemented!("check for for loop"), Statement::ForLoop(f) => f.check_semantics(scope),
Statement::WhileLoop(_) => unimplemented!("check for while loop"), Statement::WhileLoop(_) => unimplemented!("check for while loop"),
} }
} }

View File

@ -11,6 +11,7 @@ pub struct ForLoop<'a> {
pub loop_end: usize, pub loop_end: usize,
pub key: Option<&'a Token>, pub key: Option<&'a Token>,
pub value: &'a Token, pub value: &'a Token,
pub collection: Expression<'a>,
pub body: Block<'a>, pub body: Block<'a>,
} }

View File

@ -145,6 +145,7 @@ impl<'a> Parseable<'a> for ForLoop<'a> {
loop_end, loop_end,
key, key,
value, value,
collection: expr,
body: block, body: block,
}; };
Ok((for_loop, next)) Ok((for_loop, next))