Compare commits

...

4 Commits

14 changed files with 262 additions and 148 deletions

View File

@ -18,6 +18,13 @@
- Change REPL to execute code only after `;;` is found - Change REPL to execute code only after `;;` is found
- Forward the code generated by the REPL to the PHP repl - 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 ## v0.1.2
- [x] Parse conditionals - [x] Parse conditionals

View File

@ -10,6 +10,11 @@ impl Transpilable for PExpresssion<'_> {
Primary(p) => p.transpile(), Primary(p) => p.transpile(),
Assignment(a) => a.transpile(), Assignment(a) => a.transpile(),
FunctionCall(f) => f.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>), Primary(PPrimary<'a>),
/// This comes from a THP binding /// This comes from a THP binding
Assignment(PSimpleAssignment<'a>), Assignment(PSimpleAssignment<'a>),
BinaryOp(Box<PExpresssion<'a>>, Box<PExpresssion<'a>>, &'a String),
} }
pub struct PSimpleAssignment<'a> { pub struct PSimpleAssignment<'a> {

View File

@ -36,7 +36,14 @@ impl<'a> PHPTransformable<'a> for Expression<'_> {
PExpresssion::Primary(PPrimary::BoolLiteral(b.value == "true")) PExpresssion::Primary(PPrimary::BoolLiteral(b.value == "true"))
} }
Expression::UnaryOperator(_, _) => unimplemented!("transform unary op into php"), 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"), Expression::Array(_) => unimplemented!("transform array into php"),
} }
} }

View File

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

View File

@ -0,0 +1,171 @@
use crate::{
error_handling::{
error_messages::{SEMANTIC_MISMATCHED_ARGUMENT_COUNT, SEMANTIC_MISMATCHED_TYPES},
ErrorContainer, ErrorLabel,
},
semantic::{
impls::SemanticCheck,
symbol_table::SymbolTable,
types::{Type, Typed},
},
syntax::ast::{functions::FunctionCall, Positionable},
};
impl SemanticCheck for FunctionCall<'_> {
fn check_semantics(
&self,
scope: &SymbolTable,
) -> Result<(), crate::error_handling::MistiError> {
let fun = &*self.function;
let arguments = &*self.arguments.arguments;
let function_datatype = fun.get_type(scope)?;
let Type::Function(parameters, _) = function_datatype else {
let (error_start, error_end) = fun.get_position();
let label = ErrorLabel {
message: format!(
"Expected this expression to be a function, found a {:?}",
function_datatype
),
start: error_start,
end: error_end,
};
let econtainer = ErrorContainer {
error_code: SEMANTIC_MISMATCHED_TYPES,
error_offset: error_start,
labels: vec![label],
note: None,
help: None,
};
return Err(econtainer);
};
// Check parameters length
if parameters.len() != arguments.len() {
let (error_start, error_end) = self.arguments.get_position();
let label = ErrorLabel {
message: format!(
"Expected {} arguments, got {}",
parameters.len(),
arguments.len(),
),
start: error_start,
end: error_end,
};
let econtainer = ErrorContainer {
error_code: SEMANTIC_MISMATCHED_ARGUMENT_COUNT,
error_offset: error_start,
labels: vec![label],
note: None,
help: None,
};
return Err(econtainer);
}
// Check that each argument matches the required datatype
for i in 0..parameters.len() {
let parameter = &parameters[i];
let argument = &arguments[i];
let argument_datatype = argument.get_type(scope)?;
if !argument_datatype.is_value(parameter) {
// The argument and the parameter have diferent types
let (error_start, error_end) = argument.get_position();
let label = ErrorLabel {
message: format!("Expected a {}, got {:?}", parameter, argument_datatype),
start: error_start,
end: error_end,
};
let econtainer = ErrorContainer {
error_code: SEMANTIC_MISMATCHED_TYPES,
error_offset: error_start,
labels: vec![label],
note: None,
help: None,
};
return Err(econtainer);
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::{
error_handling::error_messages::{
SEMANTIC_INVALID_REFERENCE, SEMANTIC_MISMATCHED_TYPES, SEMANTIC_MISSING_REFERENCE,
},
lexic::{get_tokens, token::Token},
semantic::{
impls::SemanticCheck,
symbol_table::SymbolTable,
types::{global::INT, Type},
},
syntax::{
ast::{functions::FunctionCall, Expression},
parseable::Parseable,
},
};
fn t(i: &str) -> Vec<Token> {
get_tokens(&i.into()).unwrap()
}
fn exp<'a>(t: &'a Vec<Token>) -> FunctionCall<'a> {
let e = Expression::try_parse(t, 0).unwrap().0;
match e {
Expression::FunctionCall(f) => f,
_ => panic!("Expected to parse a function call"),
}
}
#[test]
fn should_fail_on_ref_not_exist() {
let b = t("my_fun()");
let expr = exp(&b);
let scope = SymbolTable::new();
let output = expr.check_semantics(&scope);
match output {
Ok(_) => panic!("Expected an error"),
Err(err) => {
assert_eq!(err.error_code, SEMANTIC_MISSING_REFERENCE);
assert_eq!(err.error_offset, 0);
let label = &err.labels[0];
assert_eq!(label.message, "Cannot find this identifier in this scope");
assert_eq!(label.start, 0);
assert_eq!(label.end, 6);
}
}
}
#[test]
fn should_fail_on_ref_not_a_function() {
let b = t("my_fun()");
let expr = exp(&b);
let scope = SymbolTable::new();
scope.insert(String::from("my_fun"), Type::Value(INT.into()));
let output = expr.check_semantics(&scope);
match output {
Ok(_) => panic!("Expected an error"),
Err(err) => {
assert_eq!(err.error_code, SEMANTIC_MISMATCHED_TYPES);
assert_eq!(err.error_offset, 0);
let label = &err.labels[0];
assert_eq!(
label.message,
"Expected this expression to be a function, found a `Int`"
);
assert_eq!(label.start, 0);
assert_eq!(label.end, 6);
}
}
}
}

View File

@ -1,9 +1,6 @@
use crate::{ use crate::{
error_handling::{ error_handling::{
error_messages::{ error_messages::{COMPILER_TODO, SEMANTIC_INVALID_REFERENCE, SEMANTIC_MISMATCHED_TYPES},
COMPILER_TODO, SEMANTIC_INVALID_REFERENCE, SEMANTIC_MISMATCHED_ARGUMENT_COUNT,
SEMANTIC_MISMATCHED_TYPES,
},
ErrorContainer, ErrorLabel, MistiError, ErrorContainer, ErrorLabel, MistiError,
}, },
semantic::{ semantic::{
@ -14,90 +11,12 @@ use crate::{
syntax::ast::{Expression, Positionable}, syntax::ast::{Expression, Positionable},
}; };
mod funtion_call;
impl SemanticCheck for Expression<'_> { impl SemanticCheck for Expression<'_> {
fn check_semantics(&self, scope: &SymbolTable) -> Result<(), MistiError> { fn check_semantics(&self, scope: &SymbolTable) -> Result<(), MistiError> {
match self { match self {
Expression::FunctionCall(f) => { Expression::FunctionCall(f) => f.check_semantics(scope),
let fun = &*f.function;
let arguments = &*f.arguments.arguments;
let function_datatype = fun.get_type(scope)?;
match function_datatype {
Type::Function(parameters, _return_type) => {
// Check parameters length
if parameters.len() != arguments.len() {
let (error_start, error_end) = f.arguments.get_position();
let label = ErrorLabel {
message: format!(
"Expected {} arguments, got {}",
parameters.len(),
arguments.len(),
),
start: error_start,
end: error_end,
};
let econtainer = ErrorContainer {
error_code: SEMANTIC_MISMATCHED_ARGUMENT_COUNT,
error_offset: error_start,
labels: vec![label],
note: None,
help: None,
};
return Err(econtainer);
}
// Check that each argument matches the required datatype
for i in 0..parameters.len() {
let parameter = &parameters[i];
let argument = &arguments[i];
let argument_datatype = argument.get_type(scope)?;
if !argument_datatype.is_value(parameter) {
// The argument and the parameter have diferent types
let (error_start, error_end) = argument.get_position();
let label = ErrorLabel {
message: format!(
"Expected a {}, got {:?}",
parameter, argument_datatype
),
start: error_start,
end: error_end,
};
let econtainer = ErrorContainer {
error_code: SEMANTIC_MISMATCHED_TYPES,
error_offset: error_start,
labels: vec![label],
note: None,
help: None,
};
return Err(econtainer);
}
}
Ok(())
}
_ => {
let (error_start, error_end) = fun.get_position();
let label = ErrorLabel {
message: format!(
"Expected a function type, got {:?}",
function_datatype
),
start: error_start,
end: error_end,
};
let econtainer = ErrorContainer {
error_code: SEMANTIC_MISMATCHED_TYPES,
error_offset: error_start,
labels: vec![label],
note: None,
help: None,
};
return Err(econtainer);
}
}
}
// These are empty because they have nothing to check, // These are empty because they have nothing to check,
// their existance alone is correct // their existance alone is correct
Expression::Int(_) => Ok(()), Expression::Int(_) => Ok(()),
@ -261,7 +180,7 @@ impl SemanticCheck for Expression<'_> {
let label = ErrorLabel { let label = ErrorLabel {
message: format!( message: format!(
"Expected a {}, got a {:?} on the right side of the {} operator", "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, start: error_start,
end: error_end, end: error_end,
@ -350,41 +269,22 @@ impl SemanticCheck for Expression<'_> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{ use crate::{
lexic::token::Token, lexic::{get_tokens, token::Token},
semantic::{impls::SemanticCheck, std::populate, symbol_table::SymbolTable}, semantic::{impls::SemanticCheck, std::populate, symbol_table::SymbolTable},
syntax::ast::{ syntax::{
ast::{
functions::{ArgumentsList, FunctionCall}, functions::{ArgumentsList, FunctionCall},
Expression, Expression,
}, },
parseable::Parseable,
},
}; };
#[test] fn t(i: &str) -> Vec<Token> {
fn should_error_on_undefined_symbol() { get_tokens(&i.into()).unwrap()
// source code: `print()`
let expr_token = Token::new_identifier("print".into(), 0);
let expr_function = Expression::Identifier(&expr_token);
let arguments = ArgumentsList {
arguments: vec![],
paren_open_pos: 5,
paren_close_pos: 7,
};
let expr = Expression::FunctionCall(FunctionCall {
function: Box::new(expr_function),
arguments: Box::new(arguments),
});
let scope = SymbolTable::new();
let output = expr.check_semantics(&scope);
match output {
Ok(_) => panic!("Expected an error"),
Err(err) => {
//assert_eq!(err.reason, "Cannot find `print` in this scope.");
assert_eq!(err.error_offset, 0);
//assert_eq!(err.error_end, 5);
}
} }
fn exp<'a>(t: &'a Vec<Token>) -> Expression<'a> {
Expression::try_parse(t, 0).unwrap().0
} }
#[test] #[test]

View File

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

View File

@ -4,6 +4,10 @@ use super::symbol_table::SymbolTable;
/// Allows this type to have it's semantics checked. /// Allows this type to have it's semantics checked.
pub trait SemanticCheck { pub trait SemanticCheck {
/// Checks the semantics of this AST node and performs typechecking
///
/// Types are provided by the Typed trait, because not every AST node
/// will have a defined type
fn check_semantics(&self, scope: &SymbolTable) -> Result<(), MistiError>; fn check_semantics(&self, scope: &SymbolTable) -> Result<(), MistiError>;
} }

View File

@ -2,11 +2,21 @@
//! by directly inserting the definitions into the //! by directly inserting the definitions into the
//! Symbol Table //! 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 /// Populates the symbol table with the stdlib
pub fn populate(table: &mut SymbolTable) { pub fn populate(table: &mut SymbolTable) {
// print: (String) -> (Void) // 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); 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}; use super::{Type, Typed};
impl Typed for Expression<'_> { impl Typed for Expression<'_> {
/// Attempts to get the datatype for an expression.
fn get_type(&self, scope: &SymbolTable) -> Result<Type, MistiError> { fn get_type(&self, scope: &SymbolTable) -> Result<Type, MistiError> {
match self { match self {
Expression::Int(_) => Ok(Type::Value("Int".into())), Expression::Int(_) => Ok(Type::Value("Int".into())),
@ -162,18 +161,16 @@ impl Typed for Expression<'_> {
unreachable!("Illegal state: Found an unexpected unary operator during semantic analysis: {}", op.value); unreachable!("Illegal state: Found an unexpected unary operator during semantic analysis: {}", op.value);
} }
Expression::BinaryOperator(exp1, exp2, operator) => { Expression::BinaryOperator(_, _, operator) => {
let t1 = exp1.get_type(scope)?; match scope.get_type(&operator.value) {
let t2 = exp2.get_type(scope)?; Some(Type::Function(_, return_type)) => Ok(Type::Value(return_type)),
Some(_) => {
// TODO: There's definitely a better way to do this unreachable!(
// maybe store operators as functions? "Compiler error: The operator {} was defined but it wasn't a function",
if operator.value == "+" && t1.is_value("Int") && t2.is_value("Int") { operator.value
return Ok(Type::Value("Int".into())); )
} else if operator.value == "-" && t1.is_value("Int") && t2.is_value("Int") {
return Ok(Type::Value("Int".into()));
} }
None => {
let label = ErrorLabel { let label = ErrorLabel {
message: format!("Unsupported binary operator"), message: format!("Unsupported binary operator"),
// TODO: Fix positioning // TODO: Fix positioning
@ -189,6 +186,8 @@ impl Typed for Expression<'_> {
}; };
return Err(econtainer); return Err(econtainer);
} }
}
}
Expression::Array(arr) => { Expression::Array(arr) => {
// The first expression found determines the // The first expression found determines the
// type of the array // type of the array

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; use super::symbol_table::SymbolTable;
mod expression; mod expression;
pub mod global;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Type { pub enum Type {
@ -45,5 +46,9 @@ impl Type {
} }
pub trait Typed { 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>; fn get_type(&self, scope: &SymbolTable) -> Result<Type, MistiError>;
} }

View File

@ -1,7 +1,7 @@
use crate::error_handling::MistiError; use crate::error_handling::MistiError;
mod functions; mod functions;
mod parseable; pub mod parseable;
mod parsers; mod parsers;
mod utils; mod utils;