refactor: improve typechecking of binary operator

This commit is contained in:
Fernando Araoz 2024-10-02 12:29:52 -05:00
parent b7d7244cfa
commit 71095deaa0
11 changed files with 70 additions and 31 deletions

View File

@ -18,6 +18,13 @@
- Change REPL to execute code only after `;;` is found
- Forward the code generated by the REPL to the PHP repl
## v0.1.3
- [ ] Test semantic analysis
- [ ] Generate php code from current AST
## v0.1.2
- [x] Parse conditionals

View File

@ -10,6 +10,11 @@ impl Transpilable for PExpresssion<'_> {
Primary(p) => p.transpile(),
Assignment(a) => a.transpile(),
FunctionCall(f) => f.transpile(),
BinaryOp(left, right, op) => {
let left_str = left.transpile();
let right_str = right.transpile();
format!("{} {} {}", left_str, op, right_str)
}
}
}
}

View File

@ -32,6 +32,7 @@ pub enum PExpresssion<'a> {
Primary(PPrimary<'a>),
/// This comes from a THP binding
Assignment(PSimpleAssignment<'a>),
BinaryOp(Box<PExpresssion<'a>>, Box<PExpresssion<'a>>, &'a String),
}
pub struct PSimpleAssignment<'a> {

View File

@ -36,7 +36,14 @@ impl<'a> PHPTransformable<'a> for Expression<'_> {
PExpresssion::Primary(PPrimary::BoolLiteral(b.value == "true"))
}
Expression::UnaryOperator(_, _) => unimplemented!("transform unary op into php"),
Expression::BinaryOperator(_, _, _) => unimplemented!("transform binary op into php"),
Expression::BinaryOperator(left_expr, right_expr, op) => {
// For now assume that any THP operator directly maps to a PHP operator...
let left_value = left_expr.into_php_ast();
let right_value = right_expr.into_php_ast();
PExpresssion::BinaryOp(Box::new(left_value), Box::new(right_value), &op.value)
}
Expression::Array(_) => unimplemented!("transform array into php"),
}
}

View File

@ -32,6 +32,8 @@ impl SemanticCheck for VariableBinding<'_> {
return Err(econtainer);
}
self.expression.check_semantics(scope)?;
// This gets the datatype of the assigned expression,
// to compare it later with the declared datatype.
let expression_datatype = self.expression.get_type(scope)?;

View File

@ -261,7 +261,7 @@ impl SemanticCheck for Expression<'_> {
let label = ErrorLabel {
message: format!(
"Expected a {}, got a {:?} on the right side of the {} operator",
op_params[1], left_expr_type, op.value
op_params[1], right_expr_type, op.value
),
start: error_start,
end: error_end,

View File

@ -7,7 +7,7 @@ use crate::{
impl SemanticCheck for FunctionDeclaration<'_> {
fn check_semantics(
&self,
scope: &crate::semantic::symbol_table::SymbolTable,
scope: &SymbolTable,
) -> Result<(), crate::error_handling::MistiError> {
let function_name = self.identifier.value.clone();

View File

@ -2,11 +2,21 @@
//! by directly inserting the definitions into the
//! Symbol Table
use super::{symbol_table::SymbolTable, types::Type};
use super::{
symbol_table::SymbolTable,
types::{
global::{INT, STRING, VOID},
Type,
},
};
/// Populates the symbol table with the stdlib
pub fn populate(table: &mut SymbolTable) {
// print: (String) -> (Void)
let print_fn = Type::Function(vec!["String".into()], "Void".into());
let print_fn = Type::Function(vec![STRING.into()], VOID.into());
table.insert("print".into(), print_fn);
// + operator (Int, Int) -> Int
let plus_op = Type::Function(vec![INT.into(), INT.into()], INT.into());
table.insert("+".into(), plus_op);
}

View File

@ -13,7 +13,6 @@ use crate::{
use super::{Type, Typed};
impl Typed for Expression<'_> {
/// Attempts to get the datatype for an expression.
fn get_type(&self, scope: &SymbolTable) -> Result<Type, MistiError> {
match self {
Expression::Int(_) => Ok(Type::Value("Int".into())),
@ -162,32 +161,32 @@ impl Typed for Expression<'_> {
unreachable!("Illegal state: Found an unexpected unary operator during semantic analysis: {}", op.value);
}
Expression::BinaryOperator(exp1, exp2, operator) => {
let t1 = exp1.get_type(scope)?;
let t2 = exp2.get_type(scope)?;
// TODO: There's definitely a better way to do this
// maybe store operators as functions?
if operator.value == "+" && t1.is_value("Int") && t2.is_value("Int") {
return Ok(Type::Value("Int".into()));
} else if operator.value == "-" && t1.is_value("Int") && t2.is_value("Int") {
return Ok(Type::Value("Int".into()));
Expression::BinaryOperator(_, _, operator) => {
match scope.get_type(&operator.value) {
Some(Type::Function(_, return_type)) => Ok(Type::Value(return_type)),
Some(_) => {
unreachable!(
"Compiler error: The operator {} was defined but it wasn't a function",
operator.value
)
}
None => {
let label = ErrorLabel {
message: format!("Unsupported binary operator"),
// TODO: Fix positioning
start: 0,
end: 1,
};
let econtainer = ErrorContainer {
error_code: SEMANTIC_MISMATCHED_TYPES,
error_offset: 0,
labels: vec![label],
note: None,
help: None,
};
return Err(econtainer);
}
}
let label = ErrorLabel {
message: format!("Unsupported binary operator"),
// TODO: Fix positioning
start: 0,
end: 1,
};
let econtainer = ErrorContainer {
error_code: SEMANTIC_MISMATCHED_TYPES,
error_offset: 0,
labels: vec![label],
note: None,
help: None,
};
return Err(econtainer);
}
Expression::Array(arr) => {
// The first expression found determines the

View File

@ -0,0 +1,3 @@
pub const STRING: &str = "String";
pub const INT: &str = "Int";
pub const VOID: &str = "Void";

View File

@ -6,6 +6,7 @@ use crate::error_handling::MistiError;
use super::symbol_table::SymbolTable;
mod expression;
pub mod global;
#[derive(Debug, Clone, PartialEq)]
pub enum Type {
@ -45,5 +46,9 @@ impl Type {
}
pub trait Typed {
/// Returns the datatype of this value.
///
/// This function does not perform typechecking, it only returns a type.
/// Typeckecking is done by the trait SemanticCheck
fn get_type(&self, scope: &SymbolTable) -> Result<Type, MistiError>;
}