From 758b3d65037d7b64eb5b05c674773b2129f0ef70 Mon Sep 17 00:00:00 2001 From: Lennart Van Hirtum Date: Thu, 9 Nov 2023 23:05:30 +0100 Subject: [PATCH] Added basic Type Checking! --- README.md | 7 + src/ast.rs | 77 +++------- src/dev_aid/lsp.rs | 5 +- src/dev_aid/syntax_highlighting.rs | 4 +- src/errors.rs | 19 ++- src/flattening.rs | 238 +++++++++++++++++++---------- src/linker.rs | 50 +----- src/main.rs | 2 + src/typing.rs | 86 +++++++++++ 9 files changed, 302 insertions(+), 186 deletions(-) create mode 100644 src/typing.rs diff --git a/README.md b/README.md index e22b0a4..042d536 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ The main goals of the language are roughly listed below: - [x] Can Parse Multiply-Add pipeline - [x] Can Parse Blur2 filter - [x] If Statements +- [ ] Structs - [ ] For Loops - [ ] Multi-Interface Syntax - [ ] Native Module integration syntax @@ -60,6 +61,12 @@ The main goals of the language are roughly listed below: - [ ] Incremental Compilation - [ ] Multi-Threaded Compilation +### Type and Bound Checking +- [x] Basic Type Checking (bools, ints, arrays, etc) +- [ ] Types for Interfaces +- [ ] Integer and Array Bounds Checking +- [ ] Latency Checking + ### LSP - [x] Basic LSP for VSCode integration - [x] Syntax Highlighting diff --git a/src/ast.rs b/src/ast.rs index ae51206..3aca758 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1,9 +1,9 @@ use num::bigint::BigUint; -use crate::{tokenizer::TokenTypeIdx, linker::{NamedUUID, FileUUID}, flattening::{FlattenedModule, WireIDMarker, WireID, OutsideWireID}, arena_alloc::ListAllocator}; +use crate::{tokenizer::{TokenTypeIdx, get_token_type_name}, linker::{NamedUUID, FileUUID}, flattening::{FlattenedModule, WireIDMarker, WireID, OutsideWireID}, arena_alloc::ListAllocator, typing::Type}; use core::ops::Range; -use std::ops::Deref; +use std::{ops::Deref, fmt::Display}; // Token span. Indices are INCLUSIVE #[derive(Clone,Copy,Debug,PartialEq,Eq)] @@ -54,37 +54,8 @@ pub enum TypeExpression { Array(Box<(TypeExpression, SpanExpression)>) } -impl TypeExpression { - pub fn get_root(&self) -> usize { - match self { - Self::Named(s) => *s, - Self::Array(b) => { - b.deref().0.get_root() - } - } - } - pub fn map_to_type NamedUUID>(&self, f : F) -> Type { - match self { - TypeExpression::Named(n) => Type::Named(f(*n)), - TypeExpression::Array(b) => { - let (sub, idx) = b.deref(); - Type::Array(Box::new(sub.map_to_type(f))) - // TODO gather bound constraints - }, - } - } -} - pub type SpanTypeExpression = (TypeExpression, Span); -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Type { - Named(NamedUUID), - Array(Box) -} - -pub type SpanType = (Type, Span); - #[derive(Debug,Clone)] pub struct SignalDeclaration { pub span : Span, @@ -98,6 +69,12 @@ pub struct Operator { pub op_typ : TokenTypeIdx } +impl Display for Operator { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(get_token_type_name(self.op_typ)) + } +} + #[derive(Debug,Clone)] pub enum Value { Bool(bool), @@ -177,31 +154,27 @@ pub struct Module { } impl Module { - pub fn get_function_sugar_inputs_outputs(&self) -> (Vec, Vec) { - let mut decl_iter = self.declarations.iter(); + pub fn get_function_sugar_inputs_outputs(&self) -> (Vec<(OutsideWireID, Type)>, Vec<(OutsideWireID, Type)>) { let mut inputs = Vec::new(); let mut outputs = Vec::new(); - let mut last = if let Some((_pos, decl)) = decl_iter.next() { + let mut last_was_output = true; + for (id, decl) in &self.declarations { + let typ = decl.typ.0.map_to_type(&self.link_info.global_references); match decl.identifier_type { - IdentifierType::Input => IdentifierType::Input, - IdentifierType::Output => IdentifierType::Output, - IdentifierType::Local | IdentifierType::State => {return (inputs, outputs)} - } - } else { - return (inputs, outputs); - }; - for (id, decl) in decl_iter { - if decl.identifier_type != last { - match decl.identifier_type { - IdentifierType::Input => { - inputs.push(OutsideWireID(id)); - } - IdentifierType::Output => { - outputs.push(OutsideWireID(id)); - } - IdentifierType::Local | IdentifierType::State => { - break; + IdentifierType::Input => { + if last_was_output { + inputs.clear(); } + inputs.push((OutsideWireID(id), typ)); + outputs.clear(); + last_was_output = false; + } + IdentifierType::Output => { + outputs.push((OutsideWireID(id), typ)); + last_was_output = true; + } + IdentifierType::Local | IdentifierType::State => { + break; } } } diff --git a/src/dev_aid/lsp.rs b/src/dev_aid/lsp.rs index 4394a66..22668e0 100644 --- a/src/dev_aid/lsp.rs +++ b/src/dev_aid/lsp.rs @@ -266,12 +266,13 @@ fn convert_diagnostic(err : ParsingError, severity : DiagnosticSeverity, token_p // Requires that token_positions.len() == tokens.len() + 1 to include EOF token fn send_errors_warnings(connection: &Connection, errors : ErrorCollector, token_positions : &[std::ops::Range], uris : &ArenaVector) -> Result<(), Box> { let mut diag_vec : Vec = Vec::new(); - for err in errors.errors { + let (err_vec, file) = errors.get(); + for err in err_vec { diag_vec.push(convert_diagnostic(err, DiagnosticSeverity::ERROR, token_positions, uris)); } let params = &PublishDiagnosticsParams{ - uri: uris[errors.file].clone(), + uri: uris[file].clone(), diagnostics: diag_vec, version: None }; diff --git a/src/dev_aid/syntax_highlighting.rs b/src/dev_aid/syntax_highlighting.rs index 21f4448..c01f932 100644 --- a/src/dev_aid/syntax_highlighting.rs +++ b/src/dev_aid/syntax_highlighting.rs @@ -244,8 +244,8 @@ pub fn syntax_highlight_file(file_paths : Vec) { linker.flatten_all_modules_in_file(file_uuid, &mut errors); - for err in errors.errors { - //err.pretty_print_error(f.parsing_errors.file, &token_offsets, &paths_arena, &mut file_cache); + for err in errors.get().0 { + err.pretty_print_error(f.parsing_errors.file, &token_offsets, &paths_arena, &mut file_cache); } } } diff --git a/src/errors.rs b/src/errors.rs index c95261f..d03b3de 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,6 +1,6 @@ -use std::{ops::Range, path::{Path, PathBuf}}; +use std::{ops::Range, path::{Path, PathBuf}, cell::RefCell}; use crate::{ast::Span, linker::{FileUUID, FileUUIDMarker}, arena_alloc::ArenaVector}; use ariadne::*; @@ -94,22 +94,27 @@ pub fn join_expected_list(expected : &[TokenTypeIdx]) -> String { } // Class that collects and manages errors and warnings +// Implemented such that it can be shared immutably. This makes many operations to do with parsing easier #[derive(Debug,Clone)] pub struct ErrorCollector { - pub errors : Vec, + errors : RefCell>, pub file : FileUUID } impl ErrorCollector { pub fn new(file : FileUUID) -> Self { - Self{errors : Vec::new(), file} + Self{errors : RefCell::new(Vec::new()), file} } - pub fn error_basic>(&mut self, position : Span, reason : S) { - self.errors.push(ParsingError{position, reason : reason.into(), infos : Vec::new()}); + pub fn error_basic>(&self, position : Span, reason : S) { + self.errors.borrow_mut().push(ParsingError{position, reason : reason.into(), infos : Vec::new()}); } - pub fn error_with_info>(&mut self, position : Span, reason : S, infos : Vec) { - self.errors.push(ParsingError{position, reason : reason.into(), infos : infos}); + pub fn error_with_info>(&self, position : Span, reason : S, infos : Vec) { + self.errors.borrow_mut().push(ParsingError{position, reason : reason.into(), infos : infos}); + } + + pub fn get(self) -> (Vec, FileUUID) { + (self.errors.into_inner(), self.file) } } diff --git a/src/flattening.rs b/src/flattening.rs index 89a3a22..f806e11 100644 --- a/src/flattening.rs +++ b/src/flattening.rs @@ -1,9 +1,9 @@ -use std::ops::Deref; +use std::{ops::Deref, iter::zip}; use crate::{ - ast::{Span, Value, Module, Expression, SpanExpression, LocalOrGlobal, Operator, AssignableExpression, SpanAssignableExpression, Statement, CodeBlock, AssignableExpressionWithModifiers, TypeExpression, Type, SpanType}, - linker::{NamedUUID, Linker, Named, Linkable}, - errors::{ErrorCollector, error_info}, arena_alloc::{ListAllocator, UUID}, tokenizer::kw + ast::{Span, Value, Module, Expression, SpanExpression, LocalOrGlobal, Operator, AssignableExpression, SpanAssignableExpression, Statement, CodeBlock, AssignableExpressionWithModifiers, TypeExpression}, + linker::{Linker, Named, Linkable, get_builtin_uuid}, + errors::{ErrorCollector, error_info}, arena_alloc::{ListAllocator, UUID}, tokenizer::kw, typing::{Type, get_unary_operator_types, get_binary_operator_types} }; #[derive(Debug,Clone,Copy,PartialEq,Eq,Hash)] @@ -19,33 +19,46 @@ pub type SpanWireID = (WireID, Span); #[derive(Debug)] pub enum ConnectionWrite { Local(WireID), - ArrayIdx(Box<(ConnectionWrite, SpanWireID)>), - StructField(Box<(ConnectionWrite, OutsideWireID)>) + ArrayIdx(Box<(SpanConnectionWrite, SpanWireID)>), + StructField(Box<(SpanConnectionWrite, OutsideWireID)>) } +pub type SpanConnectionWrite = (ConnectionWrite, Span); + #[derive(Debug)] pub enum Instantiation { - PlainWire(Option), - ExtractWire{typ : Option, extract_from : WireID, field : OutsideWireID}, - Named(NamedUUID), - UnaryOp(Operator, SpanWireID), - BinaryOp(Operator, SpanWireID, SpanWireID), - Constant(Value), - ArrayAccess(SpanWireID, SpanWireID) + Instantiation{typ : Type, typ_span : Span}, + PlainWire{typ : Type, typ_span : Span}, + ExtractWire{typ : Type, extract_from : WireID, field : OutsideWireID}, + UnaryOp{typ : Type, op : Operator, right : SpanWireID}, + BinaryOp{typ : Type, op : Operator, left : SpanWireID, right : SpanWireID}, + ArrayAccess{typ : Type, arr : SpanWireID, arr_idx : SpanWireID}, + Constant{typ : Type, value : Value}, + Error +} + +impl Instantiation { + pub fn get_type(&self) -> &Type { + match self { + Instantiation::Instantiation{ typ, typ_span : _} => typ, + Instantiation::PlainWire { typ, typ_span : _} => typ, + Instantiation::ExtractWire { typ, extract_from : _, field : _} => typ, + Instantiation::UnaryOp { typ, op : _, right : _} => typ, + Instantiation::BinaryOp { typ, op : _, left : _, right : _} => typ, + Instantiation::ArrayAccess { typ, arr : _, arr_idx : _} => typ, + Instantiation::Constant { typ, value : _} => typ, + Instantiation::Error => panic!("This was not properly resolved!") + } + } } #[derive(Debug)] pub struct Connection { pub num_regs : u32, pub from : SpanWireID, - pub to : ConnectionWrite, + pub to : SpanConnectionWrite, pub condition : WireID } -impl Connection { - fn new(to : ConnectionWrite, from : SpanWireID, condition : WireID) -> Connection { - Connection{num_regs : 0, from, to, condition} - } -} struct FlatteningContext<'l, 'm, 'e> { instantiations : ListAllocator, @@ -53,29 +66,84 @@ struct FlatteningContext<'l, 'm, 'e> { linker : &'l Linker, module : &'m Module, - errors : &'e mut ErrorCollector + errors : &'e ErrorCollector } impl<'l, 'm, 'e> FlatteningContext<'l, 'm, 'e> { - fn new(module : &'m Module, linker : &'l Linker, errors : &'e mut ErrorCollector) -> Self { - let instantiations: ListAllocator = module.declarations.map(&mut |id, decl| { - let decl_typ_root_reference = module.link_info.global_references[decl.typ.0.get_root()]; - match &linker.links.globals[decl_typ_root_reference.1] { + fn typecheck(&self, wire : SpanWireID, expected : &Type, context : &str) -> Option<()> { + let found = self.instantiations[wire.0].get_type(); + if expected != found { + let expected_name = expected.to_string(self.linker); + let found_name = found.to_string(self.linker); + self.errors.error_basic(wire.1, format!("Typing Error: {context} expects a {expected_name} but was given a {found_name}")); + None + } else { + Some(()) + } + } + fn typecheck_is_array_indexer<'a>(&self, arr_type : &'a Type, span : Span) -> Option<&'a Type> { + let Type::Array(arr_element_type) = arr_type else { + let arr_type_name = arr_type.to_string(self.linker); + self.errors.error_basic(span, format!("Typing Error: Attempting to index into this, but it is not of array type, instead found a {arr_type_name}")); + return None; + }; + Some(arr_element_type.deref()) + } + // May also error, for example when array accesses happen on non-array types + fn get_connectionwrite_type(&self, cw : &ConnectionWrite) -> Option { + match cw { + ConnectionWrite::Local(id) => Some(self.instantiations[*id].get_type().clone()), + ConnectionWrite::ArrayIdx(arr_box) => { + let (arr, arr_idx) = arr_box.deref(); + + let index_was_int = self.typecheck(*arr_idx, &Type::Named(get_builtin_uuid("int")), "array index"); + + let arr_type = self.get_connectionwrite_type(&arr.0)?; + let arr_content_type = self.typecheck_is_array_indexer(&arr_type, arr.1)?; + + index_was_int?; // Do both for better typechecking diagnostics + Some(arr_content_type.clone()) + }, + ConnectionWrite::StructField(struct_field_box) => { + let (struct_or_instance, OutsideWireID(outside_field)) = struct_field_box.deref(); + + let ConnectionWrite::Local(id) = struct_or_instance.0 else {todo!()}; + + let Instantiation::Instantiation{typ : Type::Named(instantiation), typ_span} = &self.instantiations[id] else {todo!()}; + + let Named::Module(found) = &self.linker.links[*instantiation] else {panic!("Instantiation must be module!")}; + + let found_type = found.declarations[*outside_field].typ.0.map_to_type(&found.link_info.global_references); + + Some(found_type) + }, + } + } + fn create_connection(&mut self, connection : Connection) -> Option<()> { + let expected_type = self.get_connectionwrite_type(&connection.to.0)?; + + self.typecheck(connection.from, &expected_type, "connection")?; + + self.connections.push(connection); + + Some(()) + } + fn new(module : &'m Module, linker : &'l Linker, errors : &'e ErrorCollector) -> Self { + let instantiations: ListAllocator = module.declarations.map(&mut |_id, decl| { + let typ = decl.typ.0.map_to_type(&module.link_info.global_references); + let typ_span = decl.typ.1; + + let decl_typ_root_reference = typ.get_root(); + match &linker.links.globals[decl_typ_root_reference] { Named::Constant(c) => { - errors.error_basic(decl_typ_root_reference.0, format!("This should be the type of a declaration, but it refers to the constant '{}'", c.get_full_name())); - panic!() + errors.error_basic(typ_span, format!("This should be the type of a declaration, but it refers to the constant '{}'", c.get_full_name())); + Instantiation::Error } Named::Module(_) => { - match decl.typ.0 { - TypeExpression::Named(name_ref_idx) => { - let name_ref = module.link_info.global_references[name_ref_idx].1; - Instantiation::Named(name_ref) - } - TypeExpression::Array(_) => todo!(), - } + Instantiation::Instantiation{typ, typ_span} } Named::Type(_) => { - Instantiation::PlainWire(Some((decl.typ.0.map_to_type(|n| module.link_info.global_references[n].1), decl.typ.1))) + Instantiation::PlainWire{typ, typ_span} } } }); @@ -88,23 +156,24 @@ impl<'l, 'm, 'e> FlatteningContext<'l, 'm, 'e> { errors } } - fn desugar_func_call(&mut self, func_and_args : &[SpanExpression], closing_bracket_pos : usize, condition : WireID) -> Option<(&Module, WireID, Vec)> { + fn desugar_func_call(&mut self, func_and_args : &[SpanExpression], closing_bracket_pos : usize, condition : WireID) -> Option<(&Module, WireID, Vec<(OutsideWireID, Type)>)> { let (name_expr, name_expr_span) = &func_and_args[0]; // Function name is always there let func_instantiation = match name_expr { Expression::Named(LocalOrGlobal::Local(l)) => { - todo!(); // TODO explicit interface instantiation + *l } Expression::Named(LocalOrGlobal::Global(g)) => { let module_uuid = self.module.link_info.global_references[*g]; - self.instantiations.alloc(Instantiation::Named(module_uuid.1)) + self.instantiations.alloc(Instantiation::Instantiation{typ : Type::Named(module_uuid.1), typ_span : *name_expr_span}) } _other => { self.errors.error_basic(*name_expr_span, "Function call cannot be an expression"); return None; } }; - let Instantiation::Named(module_uuid) = self.instantiations[func_instantiation] else {panic!("Instantiation is not named!");}; - let Named::Module(md) = &self.linker.links.globals[module_uuid] else {panic!("UUID Is not module!");}; + let Instantiation::Instantiation{typ : module_type, typ_span} = &self.instantiations[func_instantiation] else {panic!("Instantiation is not named!");}; + let Type::Named(module_id) = module_type else {todo!();}; + let Named::Module(md) = &self.linker.links.globals[*module_id] else {panic!("UUID Is not module!");}; let (inputs, output_range) = md.get_function_sugar_inputs_outputs(); let mut args = &func_and_args[1..]; @@ -126,9 +195,14 @@ impl<'l, 'm, 'e> FlatteningContext<'l, 'm, 'e> { } for (i, arg_expr) in args.iter().enumerate() { - let func_input_field = inputs[i]; + let func_input_field = &inputs[i]; if let Some(arg_read_side) = self.flatten_single_expr(arg_expr, condition) { - self.connections.push(Connection::new(ConnectionWrite::StructField(Box::new((ConnectionWrite::Local(func_instantiation), func_input_field))), arg_read_side, condition)); + if self.typecheck(arg_read_side, &inputs[i].1, "submodule output") == None { + continue; + } + let func_instance_connectionwrite = (ConnectionWrite::Local(func_instantiation), *name_expr_span); + let to = (ConnectionWrite::StructField(Box::new((func_instance_connectionwrite, func_input_field.0))), *name_expr_span); + self.create_connection(Connection{num_regs: 0, to, from : arg_read_side, condition}); } } @@ -142,46 +216,58 @@ impl<'l, 'm, 'e> FlatteningContext<'l, 'm, 'e> { Expression::Named(LocalOrGlobal::Global(g)) => { let r = self.module.link_info.global_references[*g]; let cst = self.linker.get_constant(r, self.errors)?; - self.instantiations.alloc(Instantiation::Constant(cst)) + self.instantiations.alloc(Instantiation::Constant{typ : cst.get_type(), value : cst}) } Expression::Constant(cst) => { - self.instantiations.alloc(Instantiation::Constant(cst.clone())) + self.instantiations.alloc(Instantiation::Constant{typ : cst.get_type(), value : cst.clone()}) } Expression::UnaryOp(op_box) => { let (op, _op_pos, operate_on) = op_box.deref(); - let flat_op_on = self.flatten_single_expr(operate_on, condition)?; - self.instantiations.alloc(Instantiation::UnaryOp(*op, flat_op_on)) + let right = self.flatten_single_expr(operate_on, condition)?; + let (input_type, output_type) = get_unary_operator_types(*op); + self.typecheck(right, &input_type, &format!("{op} input"))?; + self.instantiations.alloc(Instantiation::UnaryOp{typ : output_type, op : *op, right}) } Expression::BinOp(binop_box) => { - let (left, op, _op_pos, right) = binop_box.deref(); - let flat_left = self.flatten_single_expr(left, condition)?; - let flat_right = self.flatten_single_expr(right, condition)?; - self.instantiations.alloc(Instantiation::BinaryOp(*op, flat_left, flat_right)) + let (left_expr, op, _op_pos, right_expr) = binop_box.deref(); + let left = self.flatten_single_expr(left_expr, condition)?; + let right = self.flatten_single_expr(right_expr, condition)?; + let ((input_left_type, input_right_type), output_type) = get_binary_operator_types(*op); + self.typecheck(left, &input_left_type, &format!("{op} left"))?; + self.typecheck(right, &input_right_type, &format!("{op} right"))?; + self.instantiations.alloc(Instantiation::BinaryOp{typ : output_type, op : *op, left, right}) } Expression::Array(arr_box) => { let (left, right) = arr_box.deref(); - let flat_arr = self.flatten_single_expr(left, condition)?; - let flat_arr_idx = self.flatten_single_expr(right, condition)?; - self.instantiations.alloc(Instantiation::ArrayAccess(flat_arr, flat_arr_idx)) + let arr = self.flatten_single_expr(left, condition)?; + let arr_idx = self.flatten_single_expr(right, condition)?; + + let index_was_int = self.typecheck(arr_idx, &Type::Named(get_builtin_uuid("int")), "array index"); + let array_type = self.instantiations[arr.0].get_type(); + let typ = self.typecheck_is_array_indexer(array_type, arr.1)?.clone(); + index_was_int?; // Do both for better typechecking diagnostics + self.instantiations.alloc(Instantiation::ArrayAccess{typ, arr, arr_idx}) } Expression::FuncCall(func_and_args) => { - let (md, func_instance, output_range) = self.desugar_func_call(func_and_args, expr_span.1, condition)?; + let (md, func_instance, outputs) = self.desugar_func_call(func_and_args, expr_span.1, condition)?; - if output_range.len() != 1 { + if outputs.len() != 1 { let info = error_info(md.link_info.span, md.link_info.file, "Module Defined here"); self.errors.error_with_info(*expr_span, "A function called in this context may only return one result. Split this function call into a separate line instead.", vec![info]); return None; } - self.instantiations.alloc(Instantiation::ExtractWire{typ: None, extract_from: func_instance, field: output_range[0]}) + let only_output = outputs.into_iter().next().unwrap(); + + self.instantiations.alloc(Instantiation::ExtractWire{typ: only_output.1, extract_from: func_instance, field: only_output.0}) } }; Some((single_connection_side, *expr_span)) } - fn flatten_assignable_expr(&mut self, (expr, _span) : &SpanAssignableExpression, condition : WireID) -> Option { - match expr { + fn flatten_assignable_expr(&mut self, (expr, span) : &SpanAssignableExpression, condition : WireID) -> Option { + Some((match expr { AssignableExpression::Named{local_idx} => { - Some(ConnectionWrite::Local(*local_idx)) + ConnectionWrite::Local(*local_idx) } AssignableExpression::ArrayIndex(arr_box) => { let (arr, idx) = arr_box.deref(); @@ -189,15 +275,17 @@ impl<'l, 'm, 'e> FlatteningContext<'l, 'm, 'e> { let flattened_arr_expr = self.flatten_assignable_expr(arr, condition)?; - Some(ConnectionWrite::ArrayIdx(Box::new((flattened_arr_expr, idx_local)))) + ConnectionWrite::ArrayIdx(Box::new((flattened_arr_expr, idx_local))) } - } + }, *span)) } fn extend_condition(&mut self, condition : WireID, additional_condition : SpanWireID) -> WireID { if condition == WireID::INVALID { additional_condition.0 } else { - self.instantiations.alloc(Instantiation::BinaryOp(Operator{op_typ : kw("&")}, (condition, additional_condition.1), additional_condition)) + let bool_typ = Type::Named(get_builtin_uuid("bool")); + assert!(*self.instantiations[condition].get_type() == bool_typ); + self.instantiations.alloc(Instantiation::BinaryOp{typ : bool_typ, op: Operator{op_typ : kw("&")}, left : (condition, additional_condition.1), right : additional_condition}) } } fn flatten_code(&mut self, code : &CodeBlock, condition : WireID) { @@ -210,11 +298,13 @@ impl<'l, 'm, 'e> FlatteningContext<'l, 'm, 'e> { // TODO } Statement::If{condition : condition_expr, then, els} => { - let Some(then_condition_bool) = self.flatten_single_expr(condition_expr, condition) else {continue;}; - let then_condition = self.extend_condition(condition, then_condition_bool); + let Some(if_statement_condition) = self.flatten_single_expr(condition_expr, condition) else {continue;}; + let bool_typ = Type::Named(get_builtin_uuid("bool")); + if self.typecheck(if_statement_condition, &bool_typ, "if statement condition") == None {continue;} + let then_condition = self.extend_condition(condition, if_statement_condition); self.flatten_code(then, then_condition); if let Some(e) = els { - let else_condition_bool = (self.instantiations.alloc(Instantiation::UnaryOp(Operator{op_typ : kw("!")}, then_condition_bool)), condition_expr.1); + let else_condition_bool = (self.instantiations.alloc(Instantiation::UnaryOp{typ : bool_typ, op : Operator{op_typ : kw("!")}, right : if_statement_condition}), condition_expr.1); let else_condition = self.extend_condition(condition, else_condition_bool); self.flatten_code(e, else_condition); } @@ -240,11 +330,10 @@ impl<'l, 'm, 'e> FlatteningContext<'l, 'm, 'e> { &to }; - for (i, to_i) in assign_list.iter().enumerate() { + for (field, to_i) in zip(outputs, assign_list) { let Some(write_side) = self.flatten_assignable_expr(&to_i.expr, condition) else {return;}; - let field = outputs[i]; - let w = self.instantiations.alloc(Instantiation::ExtractWire{typ: None, extract_from: instantiation_idx, field}); - self.connections.push(Connection{num_regs : to_i.num_regs, from: (w, func_name_span), to: write_side, condition}); + let w = self.instantiations.alloc(Instantiation::ExtractWire{typ: field.1, extract_from: instantiation_idx, field : field.0}); + self.create_connection(Connection{num_regs : to_i.num_regs, from: (w, func_name_span), to: write_side, condition}); } }, Statement::Assign{to, expr : non_func_expr, eq_sign_position : _} => { @@ -252,7 +341,7 @@ impl<'l, 'm, 'e> FlatteningContext<'l, 'm, 'e> { let Some(read_side) = self.flatten_single_expr(non_func_expr, condition) else {return;}; let t = &to[0]; let Some(write_side) = self.flatten_assignable_expr(&t.expr, condition) else {return;}; - self.connections.push(Connection{num_regs : t.num_regs, from: read_side, to: write_side, condition}); + self.create_connection(Connection{num_regs : t.num_regs, from: read_side, to: write_side, condition}); } else { self.errors.error_basic(*stmt_span, format!("Non-function assignments must only output exactly 1 instead of {}", to.len())); } @@ -264,18 +353,9 @@ impl<'l, 'm, 'e> FlatteningContext<'l, 'm, 'e> { } } } - - pub fn get_type(&self, ) { - - } - pub fn type_check(&mut self) { - for c in &self.connections { - - } - } } -pub fn flatten(module : &Module, linker : &Linker, errors : &mut ErrorCollector) -> FlattenedModule { +pub fn flatten(module : &Module, linker : &Linker, errors : &ErrorCollector) -> FlattenedModule { let mut result = FlatteningContext::new(module, linker, errors); result.flatten_code(&module.code, WireID::INVALID); diff --git a/src/linker.rs b/src/linker.rs index c72b9f7..d3e798a 100644 --- a/src/linker.rs +++ b/src/linker.rs @@ -1,6 +1,6 @@ use std::{collections::{HashMap, HashSet}, ops::{IndexMut, Index}}; -use crate::{ast::{Module, LinkInfo, Span, Value, GlobalReference, Operator, Type}, arena_alloc::{ArenaAllocator, UUID}, parser::{FullParseResult, TokenTreeNode}, tokenizer::{Token, kw}, errors::{ErrorCollector, error_info}, flattening::flatten, util::{const_str_position, const_str_position_in_tuples}}; +use crate::{ast::{Module, LinkInfo, Span, Value, GlobalReference}, arena_alloc::{ArenaAllocator, UUID}, parser::{FullParseResult, TokenTreeNode}, tokenizer::Token, errors::{ErrorCollector, error_info}, flattening::flatten, util::{const_str_position, const_str_position_in_tuples}}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct NamedUUIDMarker; @@ -279,7 +279,7 @@ impl PreLinker { } } -fn add_error(info_a: &LinkInfo, info_b: &LinkInfo, errors: &mut ErrorCollector) { +fn add_error(info_a: &LinkInfo, info_b: &LinkInfo, errors: &ErrorCollector) { let this_object_name = &info_a.name; errors.error_with_info(info_a.name_span, format!("Conflicting Declaration for the name '{this_object_name}'"), vec![ error_info(info_b.name_span, info_b.file, "Conflicting Declaration") @@ -287,7 +287,7 @@ fn add_error(info_a: &LinkInfo, info_b: &LinkInfo, errors: &mut ErrorCollector) } impl Linker { - pub fn get_linking_errors(&self, file_uuid : FileUUID, errors : &mut ErrorCollector) { + pub fn get_linking_errors(&self, file_uuid : FileUUID, errors : &ErrorCollector) { let file = &self.files[file_uuid]; // Conflicting Declarations @@ -431,7 +431,7 @@ impl Linker { self.add_reserved_file(file, parse_result, parsing_errors); } - pub fn get_constant(&self, GlobalReference(identifier_span, uuid) : GlobalReference, errors : &mut ErrorCollector) -> Option { + pub fn get_constant(&self, GlobalReference(identifier_span, uuid) : GlobalReference, errors : &ErrorCollector) -> Option { match &self.links.globals[uuid] { Named::Constant(NamedConstant::Builtin(_name, v)) => { Some(v.clone()) @@ -451,7 +451,7 @@ impl Linker { } } - pub fn get_module(&self, GlobalReference(identifier_span, uuid) : GlobalReference, errors : &mut ErrorCollector) -> Option<&Module> { + pub fn get_module(&self, GlobalReference(identifier_span, uuid) : GlobalReference, errors : &ErrorCollector) -> Option<&Module> { match &self.links.globals[uuid] { Named::Module(md) => { Some(md) @@ -471,45 +471,7 @@ impl Linker { } } - pub fn get_unary_operator_types(&self, op : Operator) -> (Type, Type) { - let bool = Type::Named(get_builtin_uuid("bool")); - let int = Type::Named(get_builtin_uuid("int")); - - match op.op_typ { - x if x == kw("!") => (bool.clone(), bool), - x if x == kw("&") => (Type::Array(Box::new(bool.clone())), bool), - x if x == kw("|") => (Type::Array(Box::new(bool.clone())), bool), - x if x == kw("^") => (Type::Array(Box::new(bool.clone())), bool), - x if x == kw("+") => (Type::Array(Box::new(int.clone())), int), - x if x == kw("*") => (Type::Array(Box::new(int.clone())), int), - _ => unreachable!() - } - } - pub fn get_binary_operator_types(&self, op : Operator) -> ((Type, Type), Type) { - let bool = get_builtin_uuid("bool"); - let int = get_builtin_uuid("int"); - - let (a, b, o) = match op.op_typ { - x if x == kw("&") => (bool, bool, bool), - x if x == kw("|") => (bool, bool, bool), - x if x == kw("^") => (bool, bool, bool), - x if x == kw("+") => (int, int, int), - x if x == kw("-") => (int, int, int), - x if x == kw("*") => (int, int, int), - x if x == kw("/") => (int, int, int), - x if x == kw("%") => (int, int, int), - x if x == kw("==") => (int, int, bool), - x if x == kw("!=") => (int, int, bool), - x if x == kw(">=") => (int, int, bool), - x if x == kw("<=") => (int, int, bool), - x if x == kw(">") => (int, int, bool), - x if x == kw("<") => (int, int, bool), - _ => unreachable!() - }; - ((Type::Named(a), Type::Named(b)), Type::Named(o)) - } - - pub fn flatten_all_modules_in_file(&self, file : FileUUID, errors : &mut ErrorCollector) { + pub fn flatten_all_modules_in_file(&self, file : FileUUID, errors : &ErrorCollector) { for md_uuid in &self.files[file].associated_values { let named = &self.links.globals[*md_uuid]; println!("Flattening {}", named.get_name()); diff --git a/src/main.rs b/src/main.rs index 62b9002..4ade563 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,8 @@ mod flattening; #[cfg(feature = "codegen")] mod codegen; +mod typing; + mod dev_aid; mod linker; diff --git a/src/typing.rs b/src/typing.rs new file mode 100644 index 0000000..6b99eca --- /dev/null +++ b/src/typing.rs @@ -0,0 +1,86 @@ +use std::ops::Deref; + +use crate::{ast::{Value, Operator, TypeExpression, GlobalReference}, linker::{get_builtin_uuid, NamedUUID, Linker, Linkable}, tokenizer::kw}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Type { + Named(NamedUUID), + Array(Box) +} + +impl Type { + pub fn to_string(&self, linker : &Linker) -> String { + match self { + Type::Named(n) => linker.links[*n].get_full_name(), + Type::Array(sub) => sub.deref().to_string(linker) + "[]", + } + } + pub fn get_root(&self) -> NamedUUID { + match self { + Type::Named(name) => *name, + Type::Array(sub) => sub.get_root(), + } + } +} + +impl Value { + pub fn get_type(&self) -> Type { + match self { + Value::Bool(_) => Type::Named(get_builtin_uuid("bool")), + Value::Integer(_) => Type::Named(get_builtin_uuid("int")), + } + } +} + +impl TypeExpression { + pub fn map_to_type(&self, global_references : &[GlobalReference]) -> Type { + match self { + TypeExpression::Named(n) => Type::Named(global_references[*n].1), + TypeExpression::Array(b) => { + let (sub, _idx) = b.deref(); + Type::Array(Box::new(sub.map_to_type(global_references))) + // TODO gather bound constraints + }, + } + } +} + + +pub fn get_unary_operator_types(op : Operator) -> (Type, Type) { + let bool = Type::Named(get_builtin_uuid("bool")); + let int = Type::Named(get_builtin_uuid("int")); + + match op.op_typ { + x if x == kw("!") => (bool.clone(), bool), + x if x == kw("&") => (Type::Array(Box::new(bool.clone())), bool), + x if x == kw("|") => (Type::Array(Box::new(bool.clone())), bool), + x if x == kw("^") => (Type::Array(Box::new(bool.clone())), bool), + x if x == kw("+") => (Type::Array(Box::new(int.clone())), int), + x if x == kw("*") => (Type::Array(Box::new(int.clone())), int), + _ => unreachable!() + } +} +pub fn get_binary_operator_types(op : Operator) -> ((Type, Type), Type) { + let bool = get_builtin_uuid("bool"); + let int = get_builtin_uuid("int"); + + let (a, b, o) = match op.op_typ { + x if x == kw("&") => (bool, bool, bool), + x if x == kw("|") => (bool, bool, bool), + x if x == kw("^") => (bool, bool, bool), + x if x == kw("+") => (int, int, int), + x if x == kw("-") => (int, int, int), + x if x == kw("*") => (int, int, int), + x if x == kw("/") => (int, int, int), + x if x == kw("%") => (int, int, int), + x if x == kw("==") => (int, int, bool), + x if x == kw("!=") => (int, int, bool), + x if x == kw(">=") => (int, int, bool), + x if x == kw("<=") => (int, int, bool), + x if x == kw(">") => (int, int, bool), + x if x == kw("<") => (int, int, bool), + _ => unreachable!() + }; + ((Type::Named(a), Type::Named(b)), Type::Named(o)) +} +