feat: semantic analysis for hello world

master v0.1.0
Araozu 2024-08-01 10:34:08 -05:00
parent c0e20ad283
commit 4c565df699
19 changed files with 263 additions and 153 deletions

View File

@ -21,17 +21,20 @@
- Not ignore comments & whitespace, for code formatting - Not ignore comments & whitespace, for code formatting
- Abstract the parsing of datatypes, such that in the future generics can be implemented in a single place - Abstract the parsing of datatypes, such that in the future generics can be implemented in a single place
- Include the original tokens in the AST - Include the original tokens in the AST
- Include comments in the AST
- Begin work on the code formatter
## v0.0.15 ## v0.1.0
- [x] Complete workflow for "Hello world"
- [x] Multiline comments - [x] Multiline comments
- [x] Nested multiline comments - [x] Nested multiline comments
- [ ] Include comments in the AST - [x] Replace all panics with actual errors
- [ ] Replace all panics with actual errors - [x] Remove all old codegen
- [ ] Remove all old codegen - [x] Test codegen
- [ ] Test codegen - [x] Reenable semantic analysis
- [ ] Begin work on the code formatter - [x] Create minimal type definitions for the stdlib
## v0.0.14 ## v0.0.14

View File

@ -1 +1 @@
mod primary_expression; mod primary_expression;

View File

@ -1,9 +1,9 @@
use super::Transpilable; use super::Transpilable;
use crate::php_ast::PhpExpression; use crate::php_ast::PhpExpression;
mod expression;
pub mod statement; pub mod statement;
pub mod statement_list; pub mod statement_list;
mod expression;
impl Transpilable for PhpExpression<'_> { impl Transpilable for PhpExpression<'_> {
fn transpile(&self) -> String { fn transpile(&self) -> String {

View File

@ -18,10 +18,9 @@ mod tests {
#[test] #[test]
fn should_transpile_empty_file() { fn should_transpile_empty_file() {
let ast = PhpAst {statements: vec![]}; let ast = PhpAst { statements: vec![] };
let output = ast.transpile(); let output = ast.transpile();
assert_eq!("<?php\n", output); assert_eq!("<?php\n", output);
} }
} }

View File

@ -51,7 +51,7 @@ pub fn scan_multiline(chars: &Vec<char>, start_pos: usize) -> LexResult {
} }
/// Implementation that scans the multiline comment. /// Implementation that scans the multiline comment.
/// ///
/// May only error if EOF is found before the comment is finished. /// May only error if EOF is found before the comment is finished.
/// If Err, returns the last position where a char was available. /// If Err, returns the last position where a char was available.
fn multiline_impl(chars: &Vec<char>, start_pos: usize) -> Result<(Vec<char>, usize), usize> { fn multiline_impl(chars: &Vec<char>, start_pos: usize) -> Result<(Vec<char>, usize), usize> {

View File

@ -45,7 +45,7 @@ impl Token {
TokenType::Comment => self.position + self.value.len() + 2, TokenType::Comment => self.position + self.value.len() + 2,
// 2 extra characters for "" // 2 extra characters for ""
TokenType::String => self.position + self.value.len() + 2, TokenType::String => self.position + self.value.len() + 2,
_ => self.position + self.value.len() _ => self.position + self.value.len(),
} }
} }
} }

View File

@ -1,9 +1,8 @@
/// This AST implements a subset of the PHP AST as defined /// This AST implements a subset of the PHP AST as defined
/// by https://phplang.org/spec/19-grammar.html#syntactic-grammar /// by https://phplang.org/spec/19-grammar.html#syntactic-grammar
/// ///
/// This subset only includes nodes that can be generated by /// This subset only includes nodes that can be generated by
/// THP /// THP
pub mod transformers; pub mod transformers;
/// Represents `statement-list` on the grammar, /// Represents `statement-list` on the grammar,
@ -13,9 +12,9 @@ pub struct PhpAst<'a> {
} }
/// https://phplang.org/spec/19-grammar.html#grammar-statement /// https://phplang.org/spec/19-grammar.html#grammar-statement
/// ///
/// Not fully implemented /// Not fully implemented
/// ///
/// statement: /// statement:
/// echo-statement /// echo-statement
pub enum PhpStatement<'a> { pub enum PhpStatement<'a> {
@ -31,7 +30,7 @@ pub enum PhpExpression<'a> {
} }
/// https://phplang.org/spec/19-grammar.html#grammar-primary-expression /// https://phplang.org/spec/19-grammar.html#grammar-primary-expression
/// ///
/// primary-expression: /// primary-expression:
/// literal /// literal
pub enum PhpPrimaryExpression<'a> { pub enum PhpPrimaryExpression<'a> {
@ -39,4 +38,3 @@ pub enum PhpPrimaryExpression<'a> {
FloatingLiteral(&'a String), FloatingLiteral(&'a String),
StringLiteral(&'a String), StringLiteral(&'a String),
} }

View File

@ -12,7 +12,7 @@ impl<'a> PHPTransformable<'a> for Expression<'_> {
Expression::String(value) => { Expression::String(value) => {
let expr = PhpPrimaryExpression::StringLiteral(value); let expr = PhpPrimaryExpression::StringLiteral(value);
PhpExpression::PrimaryExpression(expr) PhpExpression::PrimaryExpression(expr)
}, }
_ => todo!("transformation for expression: {:?}", self), _ => todo!("transformation for expression: {:?}", self),
} }
} }

View File

@ -34,10 +34,10 @@ impl<'a> PHPTransformable<'a> for ModuleAST<'_> {
match e { match e {
Expression::String(v) => { Expression::String(v) => {
expressions.push( expressions.push(
PhpExpression::PrimaryExpression(PhpPrimaryExpression::StringLiteral(v.clone())) PhpExpression::PrimaryExpression(PhpPrimaryExpression::StringLiteral(v))
) )
}, },
_ => panic!("Non string expressions not supported") _ => todo!("Non string expressions not supported")
} }
} }

View File

@ -4,62 +4,12 @@ use colored::Colorize;
use crate::codegen::Transpilable; use crate::codegen::Transpilable;
use crate::error_handling::PrintableError; use crate::error_handling::PrintableError;
use crate::lexic::token::Token;
use super::codegen;
use super::lexic; use super::lexic;
use super::syntax; use super::syntax;
use crate::php_ast::transformers::PHPTransformable; use crate::php_ast::transformers::PHPTransformable;
/// Executes Lexical analysis, handles errors and calls build_ast for the next phase
fn compile(input: &String) {
let tokens = lexic::get_tokens(input);
match tokens {
Ok(tokens) => {
build_ast(input, tokens);
}
Err(error) => {
let chars: Vec<char> = input.chars().into_iter().collect();
eprintln!("{}", error.get_error_str(&chars))
}
}
}
/// Executes Syntax analysis, and for now, Semantic analysis and Code generation.
///
/// Prints the generated code in stdin
fn build_ast(input: &String, tokens: Vec<Token>) {
let ast = syntax::build_ast(&tokens);
match ast {
Ok(ast) => {
/*
let res1 = crate::semantic::check_semantics(&ast);
TODO: Disabled to test the PHP codegen. Reenable
match res1 {
Ok(_) => {}
Err(reason) => {
let chars: Vec<char> = input.chars().into_iter().collect();
let error = format!("{}: {}", "error".on_red(), reason.get_error_str(&chars));
eprintln!("{}", error);
return;
}
}
*/
let php_ast = ast.into_php_ast();
let js_code = php_ast.transpile();
println!("{}", js_code)
}
Err(reason) => {
let chars: Vec<char> = input.chars().into_iter().collect();
eprintln!("{}", reason.get_error_str(&chars))
}
}
}
/// Executes the REPL, reading from stdin, compiling and emitting PHP to stdout /// Executes the REPL, reading from stdin, compiling and emitting PHP to stdout
pub fn run() -> io::Result<()> { pub fn run() -> io::Result<()> {
let stdin = io::stdin(); let stdin = io::stdin();
@ -87,3 +37,54 @@ pub fn run() -> io::Result<()> {
}; };
} }
} }
/// Full pipeline from THP source code to PHP output
fn compile(input: &String) {
//
// Lexical analysis
//
let tokens = match lexic::get_tokens(input) {
Ok(t) => t,
Err(error) => {
let chars: Vec<char> = input.chars().into_iter().collect();
eprintln!("{}", error.get_error_str(&chars));
return;
}
};
//
// Syntax analysis
//
let ast = match syntax::build_ast(&tokens) {
Ok(ast) => ast,
Err(reason) => {
let chars: Vec<char> = input.chars().into_iter().collect();
eprintln!("{}", reason.get_error_str(&chars));
return;
}
};
//
// Semantic analysis
//
let res1 = crate::semantic::check_semantics(&ast);
match res1 {
Ok(_) => {}
Err(reason) => {
let chars: Vec<char> = input.chars().into_iter().collect();
let error = format!("{}: {}", "error".on_red(), reason.get_error_str(&chars));
eprintln!("{}", error);
return;
}
}
//
// Intermediate representation (THP -> PHP ast)
//
let php_ast = ast.into_php_ast();
//
// Codegen
//
println!("{}", php_ast.transpile());
}

View File

@ -1,6 +1,9 @@
use crate::{ use crate::{
error_handling::{semantic_error::SemanticError, MistiError}, error_handling::{semantic_error::SemanticError, MistiError},
semantic::{impls::SemanticCheck, symbol_table::SymbolEntry, types::Typed}, semantic::{
impls::SemanticCheck,
types::{Type, Typed},
},
syntax::ast::var_binding::VariableBinding, syntax::ast::var_binding::VariableBinding,
}; };
@ -31,7 +34,7 @@ impl SemanticCheck for VariableBinding<'_> {
let expression_datatype = self.expression.get_type(scope)?; let expression_datatype = self.expression.get_type(scope)?;
let datatype = match self.datatype { let datatype = match self.datatype {
Some(t) => t.value.clone(), Some(t) => Type::Value(t.value.clone()),
// If the datatype is not defined, we use the expression datatype // If the datatype is not defined, we use the expression datatype
None => expression_datatype.clone(), None => expression_datatype.clone(),
}; };
@ -42,7 +45,7 @@ impl SemanticCheck for VariableBinding<'_> {
error_start: self.identifier.position, error_start: self.identifier.position,
error_end: self.identifier.get_end_position(), error_end: self.identifier.get_end_position(),
reason: format!( reason: format!(
"The variable `{}` was declared as `{}` but its expression has type `{}`", "The variable `{}` was declared as `{:?}` but its expression has type `{:?}`",
binding_name, datatype, expression_datatype binding_name, datatype, expression_datatype
), ),
}; };
@ -50,7 +53,7 @@ impl SemanticCheck for VariableBinding<'_> {
return Err(MistiError::Semantic(error)); return Err(MistiError::Semantic(error));
} }
scope.insert(binding_name.clone(), SymbolEntry::new_variable(datatype)); scope.insert(binding_name.clone(), datatype);
Ok(()) Ok(())
} }

View File

@ -1,9 +1,6 @@
use crate::{ use crate::{
error_handling::{semantic_error::SemanticError, MistiError}, error_handling::{semantic_error::SemanticError, MistiError},
semantic::{ semantic::{impls::SemanticCheck, symbol_table::SymbolTable, types::Type},
impls::SemanticCheck,
symbol_table::{SymbolEntry, SymbolTable},
},
syntax::ast::{BlockMember, FunctionDeclaration, Statement}, syntax::ast::{BlockMember, FunctionDeclaration, Statement},
}; };
@ -49,10 +46,7 @@ impl SemanticCheck for FunctionDeclaration<'_> {
// TODO: Check the return type of the function // TODO: Check the return type of the function
scope.insert( scope.insert(function_name, Type::Function(vec![], "Unit".into()));
function_name,
SymbolEntry::new_function(vec![], "Unit".into()),
);
Ok(()) Ok(())
} }

View File

@ -1,6 +1,10 @@
use crate::{ use crate::{
error_handling::MistiError, error_handling::{semantic_error::SemanticError, MistiError},
semantic::{impls::SemanticCheck, symbol_table::SymbolTable}, semantic::{
impls::SemanticCheck,
symbol_table::SymbolTable,
types::{Type, Typed},
},
syntax::ast::{Expression, ModuleMembers, Statement}, syntax::ast::{Expression, ModuleMembers, Statement},
}; };
@ -16,7 +20,7 @@ impl SemanticCheck for ModuleMembers<'_> {
} }
} }
// TODO: Move to its own file // TODO: Move to its own file when it grows
impl SemanticCheck for Statement<'_> { impl SemanticCheck for Statement<'_> {
fn check_semantics(&self, scope: &SymbolTable) -> Result<(), MistiError> { fn check_semantics(&self, scope: &SymbolTable) -> Result<(), MistiError> {
match self { match self {
@ -26,9 +30,83 @@ impl SemanticCheck for Statement<'_> {
} }
} }
// TODO: Move to its own file // TODO: Move to its own file when it grows
impl SemanticCheck for Expression<'_> { impl SemanticCheck for Expression<'_> {
fn check_semantics(&self, scope: &SymbolTable) -> Result<(), MistiError> { fn check_semantics(&self, scope: &SymbolTable) -> Result<(), MistiError> {
todo!("Check semantics for expression") // How to get the global definition into the symbol table?
// maybe just when creating the symbol table inject all
// the global elements at once?
// Store the global elements as binary/JSON
// and load them along with the symbol table
// then for efficiency they could be grouped by module?
// and stored as binary files?
// then the binary files are searched for and loaded when
// requested?
// For a function call:
// check that the function exists
// check its signature
// check parameters
match self {
Expression::FunctionCall(f) => {
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() {
return Err(MistiError::Semantic(SemanticError {
// TODO: fix
error_start: 0,
error_end: 1,
reason: format!(
"Expected {} arguments, found {}",
parameters.len(),
arguments.len(),
),
}));
}
// 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
return Err(MistiError::Semantic(SemanticError {
// TODO: fix
error_start: 0,
error_end: 1,
reason: format!(
"Expected datatype {}, got {:?}",
parameter, argument
),
}));
}
}
}
_ => {
return Err(MistiError::Semantic(SemanticError {
// TODO: fix
error_start: 0,
error_end: 1,
reason: format!(
"Expected a function type, got {:?}",
function_datatype
),
}));
}
}
}
_ => todo!("Check semantics for expression other than function call"),
}
Ok(())
} }
} }

View File

@ -2,6 +2,7 @@ use crate::{error_handling::MistiError, syntax::ast::ModuleAST};
mod checks; mod checks;
mod impls; mod impls;
mod std;
mod symbol_table; mod symbol_table;
mod types; mod types;
@ -18,19 +19,22 @@ pub fn check_semantics(ast: &ModuleAST) -> Result<(), MistiError> {
// For now there's only support for a single file // For now there's only support for a single file
// TODO: Receive a symbol table as a reference and work on it. // TODO: Receive a symbol table as a reference and work on it.
// this way we can implement a unique symbol table for REPL session // this way we can implement a unique symbol table for REPL session
let global_scope = symbol_table::SymbolTable::new(); let mut global_scope = symbol_table::SymbolTable::new();
std::populate(&mut global_scope);
ast.check_semantics(&global_scope) ast.check_semantics(&global_scope)
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::symbol_table::{SymbolEntry, SymbolTable}; use crate::semantic::types::Type;
use super::symbol_table::SymbolTable;
#[test] #[test]
fn test_1() { fn test_1() {
let global_scope = SymbolTable::new(); let global_scope = SymbolTable::new();
let main_function = SymbolEntry::new_function(vec![], String::from("Unit")); let main_function = Type::Function(vec![], String::from("Unit"));
global_scope.insert("main".into(), main_function); global_scope.insert("main".into(), main_function);
@ -41,17 +45,16 @@ mod tests {
fn test_2() { fn test_2() {
let global_scope = SymbolTable::new(); let global_scope = SymbolTable::new();
let main_function = SymbolEntry::new_function(vec![], String::from("Unit")); let main_function = Type::Function(vec![], String::from("Unit"));
global_scope.insert("main".into(), main_function); global_scope.insert("main".into(), main_function);
global_scope.insert("db_url".into(), SymbolEntry::Variable("String".into())); global_scope.insert("db_url".into(), Type::Value("String".into()));
let add_function = let add_function = Type::Function(vec!["Int".into(), "Int".into()], "Int".into());
SymbolEntry::new_function(vec!["Int".into(), "Int".into()], "Int".into());
global_scope.insert("add".into(), add_function); global_scope.insert("add".into(), add_function);
let main_function_scope = SymbolTable::new_from_parent(&global_scope); let main_function_scope = SymbolTable::new_from_parent(&global_scope);
main_function_scope.insert("message".into(), SymbolEntry::Variable("String".into())); main_function_scope.insert("message".into(), Type::Value("String".into()));
assert!(main_function_scope.test(&"message".into())); assert!(main_function_scope.test(&"message".into()));
assert!(main_function_scope.test(&"db_url".into())); assert!(main_function_scope.test(&"db_url".into()));
@ -59,10 +62,10 @@ mod tests {
let add_function_scope = SymbolTable::new_from_parent(&global_scope); let add_function_scope = SymbolTable::new_from_parent(&global_scope);
add_function_scope.insert("a".into(), SymbolEntry::Variable("Int".into())); add_function_scope.insert("a".into(), Type::Value("Int".into()));
add_function_scope.insert("b".into(), SymbolEntry::Variable("Int".into())); add_function_scope.insert("b".into(), Type::Value("Int".into()));
assert!(add_function_scope.test(&"a".into())); assert!(add_function_scope.test(&"a".into()));
global_scope.insert("test".into(), SymbolEntry::Variable("Int".into())); global_scope.insert("test".into(), Type::Value("Int".into()));
} }
} }

12
src/semantic/std.rs Normal file
View File

@ -0,0 +1,12 @@
//! Naively provides the standard library for THP
//! by directly inserting the definitions into the
//! Symbol Table
use super::{symbol_table::SymbolTable, types::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());
table.insert("print".into(), print_fn);
}

View File

@ -1,5 +1,7 @@
use std::{cell::RefCell, collections::HashMap, rc::Rc}; use std::{cell::RefCell, collections::HashMap, rc::Rc};
use super::types::Type;
/// Public interface for the symbol table /// Public interface for the symbol table
pub struct SymbolTable { pub struct SymbolTable {
node: Rc<RefCell<SymbolTableNode>>, node: Rc<RefCell<SymbolTableNode>>,
@ -10,14 +12,7 @@ struct SymbolTableNode {
// the parent scope // the parent scope
parent: Option<Rc<RefCell<SymbolTableNode>>>, parent: Option<Rc<RefCell<SymbolTableNode>>>,
// the current scope // the current scope
scope: HashMap<String, SymbolEntry>, scope: HashMap<String, Type>,
}
pub enum SymbolEntry {
// Just a Datatype
Variable(String),
// Contains: parameters, return type
Function(Vec<String>, String),
} }
impl SymbolTable { impl SymbolTable {
@ -37,7 +32,7 @@ impl SymbolTable {
} }
/// Inserts a new symbol into the current table scope /// Inserts a new symbol into the current table scope
pub fn insert(&self, key: String, value: SymbolEntry) { pub fn insert(&self, key: String, value: Type) {
self.node.borrow_mut().insert(key, value); self.node.borrow_mut().insert(key, value);
} }
@ -47,7 +42,7 @@ impl SymbolTable {
} }
/// Gets the datatype of a symbol, if it exists /// Gets the datatype of a symbol, if it exists
pub fn get_type(&self, key: &String) -> Option<String> { pub fn get_type<'a>(&'a self, key: &String) -> Option<Type> {
self.node.borrow_mut().get_type(key) self.node.borrow_mut().get_type(key)
} }
} }
@ -62,7 +57,7 @@ impl SymbolTableNode {
} }
/// Creates a new symbol table with a parent /// Creates a new symbol table with a parent
pub fn new_from_parent<'a>(parent: &Rc<RefCell<SymbolTableNode>>) -> SymbolTableNode { pub fn new_from_parent(parent: &Rc<RefCell<SymbolTableNode>>) -> SymbolTableNode {
SymbolTableNode { SymbolTableNode {
parent: Some(Rc::clone(&parent)), parent: Some(Rc::clone(&parent)),
scope: HashMap::new(), scope: HashMap::new(),
@ -70,7 +65,7 @@ impl SymbolTableNode {
} }
/// Inserts a new symbol into the current scope /// Inserts a new symbol into the current scope
pub fn insert(&mut self, key: String, value: SymbolEntry) { pub fn insert(&mut self, key: String, value: Type) {
self.scope.insert(key, value); self.scope.insert(key, value);
} }
@ -90,33 +85,20 @@ impl SymbolTableNode {
} }
/// Returns the symbol's datatype /// Returns the symbol's datatype
pub fn get_type(&mut self, key: &String) -> Option<String> { pub fn get_type<'a>(&'a mut self, key: &String) -> Option<Type> {
// Try to get the type in the current scope // Try to get the type in the current scope
if let Some(entry) = self.scope.get(key) { if let Some(entry) = self.scope.get(key) {
// TODO: Change to allow other types of datatypes: functions, classes, maps // TODO: Change to allow other types of datatypes: functions, classes, maps
return match entry { return Some(entry.clone());
SymbolEntry::Variable(t) => Some(t.clone()),
SymbolEntry::Function(_, _) => None,
};
} }
// Try to get the type in the parent scope // Try to get the type in the parent scope
match &self.parent { match &self.parent {
Some(parent) => { Some(parent) => {
let mut parent = parent.as_ref().borrow_mut(); parent.as_ref().borrow_mut().get_type(key)
parent.get_type(key) // parent.get_type(key)
} }
None => None, None => None,
} }
} }
} }
impl SymbolEntry {
pub fn new_variable(datatype: String) -> SymbolEntry {
SymbolEntry::Variable(datatype)
}
pub fn new_function(parameters: Vec<String>, return_type: String) -> SymbolEntry {
SymbolEntry::Function(parameters, return_type)
}
}

View File

@ -4,16 +4,16 @@ use crate::{
syntax::ast::Expression, syntax::ast::Expression,
}; };
use super::Typed; use super::{Type, Typed};
impl Typed for Expression<'_> { impl Typed for Expression<'_> {
/// Attempts to get the datatype for an expression. /// Attempts to get the datatype for an expression.
fn get_type(&self, scope: &SymbolTable) -> Result<String, MistiError> { fn get_type(&self, scope: &SymbolTable) -> Result<Type, MistiError> {
match self { match self {
Expression::Int(_) => Ok("Int".into()), Expression::Int(_) => Ok(Type::Value("Int".into())),
Expression::Float(_) => Ok("Float".into()), Expression::Float(_) => Ok(Type::Value("Float".into())),
Expression::String(_) => Ok("String".into()), Expression::String(_) => Ok(Type::Value("String".into())),
Expression::Boolean(_) => Ok("Bool".into()), Expression::Boolean(_) => Ok(Type::Value("Bool".into())),
Expression::Identifier(identifier) => { Expression::Identifier(identifier) => {
// Attempt to get the datatype of the identifier in the current scope // Attempt to get the datatype of the identifier in the current scope
let datatype = match scope.get_type(identifier) { let datatype = match scope.get_type(identifier) {
@ -27,14 +27,30 @@ impl Typed for Expression<'_> {
} }
}; };
// TODO: use lifetimes
Ok(datatype) Ok(datatype)
} }
Expression::FunctionCall(_f) => { Expression::FunctionCall(f) => {
// TODO: Must implement functions as first class citizens // TODO: Must implement functions as first class citizens
// for this to work // for this to work with any arbitrary expression.
// for now it justs expects an identifier
// TODO: check the parameter types match &*f.function {
panic!("Not implemented: Get datatype of function call") Expression::Identifier(id) => {
match scope.get_type(id) {
Some(t) => Ok(t),
None => Err(MistiError::Semantic(SemanticError {
// TODO: Actually find the start and end position
// this requires the token to be stored, rather than
// just the string value
error_start: 0,
error_end: 1,
reason: format!("Type not found for symbol {}", id),
})),
}
}
_ => todo!("Get datatype of an expression that resolves into a function call"),
}
} }
Expression::UnaryOperator(op, exp) => { Expression::UnaryOperator(op, exp) => {
let expr_type = match exp.get_type(scope) { let expr_type = match exp.get_type(scope) {
@ -50,41 +66,41 @@ impl Typed for Expression<'_> {
// Only supported unary operator: - & ! // Only supported unary operator: - & !
if *op == "-" { if *op == "-" {
if expr_type != "Int" && expr_type != "Float" { if !expr_type.is_value("Int") && !expr_type.is_value("Float") {
return Err(MistiError::Semantic(SemanticError { return Err(MistiError::Semantic(SemanticError {
error_start: 0, error_start: 0,
error_end: 1, error_end: 1,
reason: format!( reason: format!(
"Expected a Int or Float after unary `-`, got {}", "Expected a Int or Float after unary `-`, got {:?}",
expr_type expr_type
), ),
})); }));
} else { } else {
return Ok("Int".into()); return Ok(Type::Value("Int".into()));
} }
} else if *op == "!" { } else if *op == "!" {
if expr_type != "Bool" { if !expr_type.is_value("Bool") {
return Err(MistiError::Semantic(SemanticError { return Err(MistiError::Semantic(SemanticError {
error_start: 0, error_start: 0,
error_end: 1, error_end: 1,
reason: format!("Expected a Bool after unary `!`, got {}", expr_type), reason: format!("Expected a Bool after unary `!`, got {:?}", expr_type),
})); }));
} else { } else {
return Ok("Bool".into()); return Ok(Type::Value("Bool".into()));
} }
} }
panic!("Illegal state: Found an unexpected unary operator during semantic analysis: {}", *op); unreachable!("Illegal state: Found an unexpected unary operator during semantic analysis: {}", *op);
} }
Expression::BinaryOperator(exp1, exp2, operator) => { Expression::BinaryOperator(exp1, exp2, operator) => {
let t1 = exp1.get_type(scope)?; let t1 = exp1.get_type(scope)?;
let t2 = exp2.get_type(scope)?; let t2 = exp2.get_type(scope)?;
// TODO: There's definitely a better way to do this // TODO: There's definitely a better way to do this
if *operator == "+" && t1 == "Int" && t2 == "Int" { if *operator == "+" && t1.is_value("Int") && t2.is_value("Int") {
return Ok("Int".into()); return Ok(Type::Value("Int".into()));
} else if *operator == "-" && t1 == "Int" && t2 == "Int" { } else if *operator == "-" && t1.is_value("Int") && t2.is_value("Int") {
return Ok("Int".into()); return Ok(Type::Value("Int".into()));
} }
return Err(MistiError::Semantic(SemanticError { return Err(MistiError::Semantic(SemanticError {

View File

@ -1,5 +1,5 @@
// This crate provides an interface and implementations //! This crate provides an interface and implementations
// for determining the datatypes of the language constructs. //! for determining the datatypes of the language constructs.
use crate::error_handling::MistiError; use crate::error_handling::MistiError;
@ -7,6 +7,26 @@ use super::symbol_table::SymbolTable;
mod expression; mod expression;
pub trait Typed { #[derive(Debug, Clone, PartialEq)]
fn get_type(&self, scope: &SymbolTable) -> Result<String, MistiError>; pub enum Type {
Value(String),
// TODO: Use Type instead of String to allow
// arbitrary types
Function(Vec<String>, String),
// TODO: tuple, union types
// TODO: generics
}
impl Type {
/// Checks if this type is a value and has the specified type
pub fn is_value(&self, datatype: impl Into<String>) -> bool {
match self {
Type::Value(v) if *v == datatype.into() => true,
_ => false,
}
}
}
pub trait Typed {
fn get_type(&self, scope: &SymbolTable) -> Result<Type, MistiError>;
} }