From 69c254382d129bf892f22966bba4fb6bab0675ca Mon Sep 17 00:00:00 2001 From: Toni Date: Tue, 17 Mar 2026 14:04:14 +0100 Subject: [PATCH 01/12] TypeChecker --- src/codegen_x86_64.rs | 28 +++++++++++++++++----------- src/main.rs | 34 ++++++++++++++++++++++++++++------ src/typechecker.rs | 31 +++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 17 deletions(-) create mode 100644 src/typechecker.rs diff --git a/src/codegen_x86_64.rs b/src/codegen_x86_64.rs index 94f4998..6439105 100644 --- a/src/codegen_x86_64.rs +++ b/src/codegen_x86_64.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, fmt::Write}; +use std::{cell::RefCell, collections::HashMap, fmt::Write, rc::Rc}; use crate::{ analyzer::Analyzer, @@ -77,17 +77,17 @@ pub struct CodegenX86_64 { data_section: String, label_counter: usize, data_counter: usize, - pub analyzer: Analyzer, + pub analyzer: Rc>, } impl CodegenX86_64 { - pub fn new() -> CodegenX86_64 { + pub fn new(analyzer: Rc>) -> CodegenX86_64 { CodegenX86_64 { output: String::new(), data_section: String::new(), label_counter: 0, data_counter: 1, - analyzer: Analyzer::new(), + analyzer, } } @@ -571,11 +571,11 @@ _builtin_environ: } } Expr::Variable(name) => { - if self.analyzer.constants.contains_key(&name.lexeme) { + if self.analyzer.borrow().constants.contains_key(&name.lexeme) { emit!( &mut self.output, " mov rax, {}", - self.analyzer.constants[&name.lexeme] + self.analyzer.borrow().constants[&name.lexeme] ); } else { // TODO: move to analyzer @@ -677,7 +677,12 @@ _builtin_environ: } if let Expr::Variable(callee_name) = &**callee { - if self.analyzer.functions.contains_key(&callee_name.lexeme) { + if self + .analyzer + .borrow() + .functions + .contains_key(&callee_name.lexeme) + { // its a function (defined/builtin/extern) emit!(&mut self.output, " call {}", callee_name.lexeme); } else { @@ -720,7 +725,7 @@ _builtin_environ: } Expr::AddrOf { op, expr } => match *expr.clone() { Expr::Variable(name) => { - if self.analyzer.functions.contains_key(&name.lexeme) { + if self.analyzer.borrow().functions.contains_key(&name.lexeme) { emit!(&mut self.output, " mov rax, {}", name.lexeme); } else { let var = match env.get_var(&name.lexeme) { @@ -744,7 +749,7 @@ _builtin_environ: } }, Expr::New(struct_name) => { - let struct_fields = &self.analyzer.structs[&struct_name.lexeme]; + let struct_fields = &self.analyzer.borrow().structs[&struct_name.lexeme]; // TODO: panic on mem.alloc error let memory_size = struct_fields.len() * 8; @@ -769,7 +774,7 @@ _builtin_environ: if BUILTIN_TYPES.contains(&name) { return true; } - if self.analyzer.structs.contains_key(name) { + if self.analyzer.borrow().structs.contains_key(name) { return true; } false @@ -796,7 +801,8 @@ _builtin_environ: } }; - let fields = match self.analyzer.structs.get(&struct_name) { + let analyzer = self.analyzer.borrow(); + let fields = match analyzer.structs.get(&struct_name) { Some(f) => f, None => { return error!(&field.loc, format!("unknown struct type: {}", struct_name)); diff --git a/src/main.rs b/src/main.rs index 5220485..f7f39de 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,11 +2,14 @@ mod analyzer; mod codegen_x86_64; mod parser; mod tokenizer; +mod typechecker; use std::{ + cell::RefCell, fs, path::Path, process::{self, Command}, + rc::Rc, }; use tokenizer::ZernError; @@ -14,10 +17,12 @@ use tokenizer::ZernError; use clap::Parser; macro_rules! compile_std { - ($codegen:expr, $( $name:literal ),* $(,)? ) => { + ($codegen:expr, $typechecker:expr, $analyzer:expr, $( $name:literal ),* $(,)? ) => { $( compile_file_to( $codegen, + $typechecker, + $analyzer, $name, include_str!(concat!("std/", $name)).into() )?; @@ -27,6 +32,8 @@ macro_rules! compile_std { fn compile_file_to( codegen: &mut codegen_x86_64::CodegenX86_64, + typechecker: &mut typechecker::TypeChecker, + analyzer: Rc>, filename: &str, source: String, ) -> Result<(), ZernError> { @@ -37,11 +44,15 @@ fn compile_file_to( let statements = parser.parse()?; for stmt in &statements { - codegen.analyzer.register_function(stmt)?; + analyzer.borrow_mut().register_function(stmt)?; } for stmt in &statements { - codegen.analyzer.analyze_stmt(stmt)?; + analyzer.borrow_mut().analyze_stmt(stmt)?; + } + + for stmt in &statements { + typechecker.typecheck_stmt(stmt)?; } for stmt in statements { @@ -73,10 +84,21 @@ fn compile_file(args: Args) -> Result<(), ZernError> { let filename = Path::new(&args.path).file_name().unwrap().to_str().unwrap(); - let mut codegen = codegen_x86_64::CodegenX86_64::new(); + let analyzer = Rc::new(RefCell::new(analyzer::Analyzer::new())); + + let mut typechecker = typechecker::TypeChecker::new(analyzer.clone()); + + let mut codegen = codegen_x86_64::CodegenX86_64::new(analyzer.clone()); codegen.emit_prologue(args.use_gcc)?; - compile_std!(&mut codegen, "syscalls.zr", "std.zr", "net.zr"); - compile_file_to(&mut codegen, filename, source)?; + compile_std!( + &mut codegen, + &mut typechecker, + analyzer.clone(), + "syscalls.zr", + "std.zr", + "net.zr" + ); + compile_file_to(&mut codegen, &mut typechecker, analyzer, filename, source)?; if !args.output_asm { let out = args.out.unwrap_or_else(|| "out".into()); diff --git a/src/typechecker.rs b/src/typechecker.rs new file mode 100644 index 0000000..3999d21 --- /dev/null +++ b/src/typechecker.rs @@ -0,0 +1,31 @@ +use std::{cell::RefCell, rc::Rc}; + +use crate::{ + analyzer::Analyzer, + parser::{Expr, Stmt}, + tokenizer::ZernError, +}; + +pub struct TypeChecker { + analyzer: Rc>, +} + +type Type = String; + +impl TypeChecker { + pub fn new(analyzer: Rc>) -> TypeChecker { + TypeChecker { analyzer } + } + + pub fn typecheck_stmt(&mut self, stmt: &Stmt) -> Result<(), ZernError> { + match stmt { + _ => todo!(), + } + } + + pub fn typecheck_expr(&mut self, expr: &Expr) -> Result { + match expr { + _ => todo!(), + } + } +} From 13fa93c6116a76700e5e556f7fd64ec6a23a5602 Mon Sep 17 00:00:00 2001 From: Toni Date: Tue, 17 Mar 2026 14:55:07 +0100 Subject: [PATCH 02/12] start implementing typechecking --- src/codegen_x86_64.rs | 41 +------- src/main.rs | 2 +- src/typechecker.rs | 219 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 213 insertions(+), 49 deletions(-) diff --git a/src/codegen_x86_64.rs b/src/codegen_x86_64.rs index 6439105..94c0b11 100644 --- a/src/codegen_x86_64.rs +++ b/src/codegen_x86_64.rs @@ -69,9 +69,6 @@ macro_rules! emit { static REGISTERS: [&str; 6] = ["rdi", "rsi", "rdx", "rcx", "r8", "r9"]; -// TODO: currently they are all just 64 bit values -static BUILTIN_TYPES: [&str; 7] = ["void", "u8", "i64", "str", "bool", "ptr", "any"]; - pub struct CodegenX86_64 { output: String, data_section: String, @@ -248,10 +245,6 @@ _builtin_environ: }, }; - if !self.is_valid_type_name(&var_type) { - return error!(&name.loc, "unrecognized type: ".to_owned() + &var_type); - } - self.compile_expr(env, initializer)?; let offset = env.define_var(name.lexeme.clone(), var_type); emit!(&mut self.output, " mov QWORD [rbp-{}], rax", offset); @@ -306,7 +299,7 @@ _builtin_environ: Stmt::Function { name, params, - return_type, + return_type: _, body, exported, } => { @@ -319,21 +312,7 @@ _builtin_environ: emit!(&mut self.output, " mov rbp, rsp"); emit!(&mut self.output, " sub rsp, 256"); // TODO - if !self.is_valid_type_name(&return_type.lexeme) { - return error!( - &return_type.loc, - "unrecognized type: ".to_owned() + &return_type.lexeme - ); - } - for (i, param) in params.iter().enumerate() { - if !self.is_valid_type_name(¶m.var_type.lexeme) { - return error!( - ¶m.var_name.loc, - "unrecognized type: ".to_owned() + ¶m.var_type.lexeme - ); - } - let offset = env .define_var(param.var_name.lexeme.clone(), param.var_type.lexeme.clone()); if let Some(reg) = REGISTERS.get(i) { @@ -578,15 +557,9 @@ _builtin_environ: self.analyzer.borrow().constants[&name.lexeme] ); } else { - // TODO: move to analyzer let var = match env.get_var(&name.lexeme) { Some(x) => x, - None => { - return error!( - name.loc, - format!("undefined variable: {}", &name.lexeme) - ); - } + None => unreachable!(), }; emit!( &mut self.output, @@ -770,16 +743,6 @@ _builtin_environ: Ok(()) } - fn is_valid_type_name(&self, name: &str) -> bool { - if BUILTIN_TYPES.contains(&name) { - return true; - } - if self.analyzer.borrow().structs.contains_key(name) { - return true; - } - false - } - fn get_field_offset( &self, env: &mut Env, diff --git a/src/main.rs b/src/main.rs index f7f39de..df66e36 100644 --- a/src/main.rs +++ b/src/main.rs @@ -52,7 +52,7 @@ fn compile_file_to( } for stmt in &statements { - typechecker.typecheck_stmt(stmt)?; + typechecker.typecheck_stmt(&mut typechecker::Env::new(), stmt)?; } for stmt in statements { diff --git a/src/typechecker.rs b/src/typechecker.rs index 3999d21..ac87ea3 100644 --- a/src/typechecker.rs +++ b/src/typechecker.rs @@ -1,31 +1,232 @@ -use std::{cell::RefCell, rc::Rc}; +#![allow(dead_code)] +#![allow(unused_variables)] + +use std::{cell::RefCell, collections::HashMap, rc::Rc}; use crate::{ analyzer::Analyzer, parser::{Expr, Stmt}, - tokenizer::ZernError, + tokenizer::{TokenType, ZernError, error}, }; +type Type = String; + +macro_rules! expect_type { + ($expr_type:expr, $expected:expr, $loc:expr) => { + if $expr_type != $expected { + return error!( + $loc, + format!("expected type '{}', got '{}'", $expected, $expr_type) + ); + } + }; +} + +// TODO: currently they are all just 64 bit values +static BUILTIN_TYPES: [&str; 8] = ["void", "u8", "i64", "str", "bool", "ptr", "fnptr", "any"]; + +pub struct Env { + scopes: Vec>, +} + +impl Env { + pub fn new() -> Env { + Env { + scopes: vec![HashMap::new()], + } + } + + pub fn push_scope(&mut self) { + self.scopes.push(HashMap::new()); + } + + pub fn pop_scope(&mut self) { + self.scopes.pop(); + } + + pub fn define_var(&mut self, name: String, var_type: String) { + self.scopes.last_mut().unwrap().insert(name, var_type); + } + + fn get_var_type(&self, name: &str) -> Option<&Type> { + for scope in self.scopes.iter().rev() { + if let Some(var) = scope.get(name) { + return Some(var); + } + } + None + } +} + pub struct TypeChecker { analyzer: Rc>, } -type Type = String; - impl TypeChecker { pub fn new(analyzer: Rc>) -> TypeChecker { TypeChecker { analyzer } } - pub fn typecheck_stmt(&mut self, stmt: &Stmt) -> Result<(), ZernError> { + pub fn typecheck_stmt(&mut self, env: &mut Env, stmt: &Stmt) -> Result<(), ZernError> { match stmt { - _ => todo!(), + Stmt::Expression(expr) => { + self.typecheck_expr(env, expr)?; + } + Stmt::Let { + name, + var_type, + initializer, + } => { + let inferred = self.typecheck_expr(env, initializer)?; + if let Some(var_type) = var_type { + if !self.is_valid_type_name(&var_type.lexeme) { + return error!( + &name.loc, + "unrecognized type: ".to_owned() + &var_type.lexeme + ); + } + expect_type!(inferred, var_type.lexeme, var_type.loc); + } + + env.define_var(name.lexeme.clone(), inferred); + } + Stmt::Const { name, value } => {} + Stmt::Block(stmts) => { + for stmt in stmts { + self.typecheck_stmt(env, stmt)?; + } + } + Stmt::If { + condition, + then_branch, + else_branch, + } => { + self.typecheck_expr(env, condition)?; + self.typecheck_stmt(env, then_branch)?; + self.typecheck_stmt(env, else_branch)?; + } + Stmt::While { condition, body } => todo!(), + Stmt::For { + var, + start, + end, + body, + } => todo!(), + Stmt::Function { + name, + params, + return_type, + body, + exported, + } => { + if !self.is_valid_type_name(&return_type.lexeme) { + return error!( + &return_type.loc, + "unrecognized type: ".to_owned() + &return_type.lexeme + ); + } + + for (i, param) in params.iter().enumerate() { + if !self.is_valid_type_name(¶m.var_type.lexeme) { + return error!( + ¶m.var_name.loc, + "unrecognized type: ".to_owned() + ¶m.var_type.lexeme + ); + } + + env.define_var(param.var_name.lexeme.clone(), param.var_type.lexeme.clone()); + } + + self.typecheck_stmt(env, body)?; + } + Stmt::Return(expr) => { + // TODO + } + Stmt::Break => todo!(), + Stmt::Continue => todo!(), + Stmt::Extern(token) => todo!(), + Stmt::Struct { name, fields } => {} + } + Ok(()) + } + + pub fn typecheck_expr(&mut self, env: &mut Env, expr: &Expr) -> Result { + match expr { + Expr::Binary { left, op, right } => { + expect_type!(self.typecheck_expr(env, left)?, "i64", op.loc); + Ok("i64".into()) + } + Expr::Logical { left, op, right } => todo!(), + Expr::Grouping(expr) => todo!(), + Expr::Literal(token) => match token.token_type { + TokenType::Number => Ok("i64".into()), + TokenType::Char => Ok("u8".into()), + TokenType::String => Ok("str".into()), + TokenType::True => Ok("bool".into()), + TokenType::False => Ok("bool".into()), + _ => unreachable!(), + }, + Expr::Unary { op, right } => todo!(), + Expr::Variable(name) => { + if self.analyzer.borrow().constants.contains_key(&name.lexeme) { + Ok("fnptr".into()) + } else { + match env.get_var_type(&name.lexeme) { + Some(x) => Ok(x.clone()), + None => error!(name.loc, format!("undefined variable: {}", &name.lexeme)), + } + } + } + Expr::Assign { left, op, value } => todo!(), + Expr::Call { + callee, + paren, + args, + } => { + if let Expr::Variable(callee_name) = &**callee { + if self + .analyzer + .borrow() + .functions + .contains_key(&callee_name.lexeme) + { + // its a function (defined/builtin/extern) + } else { + // its a variable containing function address + expect_type!(self.typecheck_expr(env, callee)?, "fnptr", paren.loc); + } + } else { + // its an expression that evalutes to function address + expect_type!(self.typecheck_expr(env, callee)?, "fnptr", paren.loc); + } + + for arg in args { + self.typecheck_expr(env, arg)?; + } + + // TODO: actually look up return type + Ok("any".into()) + } + Expr::ArrayLiteral(exprs) => todo!(), + Expr::Index { expr, index } => todo!(), + Expr::AddrOf { op, expr } => todo!(), + Expr::New(token) => todo!(), + Expr::MemberAccess { left, field } => { + let left_type = self.typecheck_expr(env, left)?; + + // TODO: actually look up left_type->field type + Ok("any".into()) + } } } - pub fn typecheck_expr(&mut self, expr: &Expr) -> Result { - match expr { - _ => todo!(), + fn is_valid_type_name(&self, name: &str) -> bool { + if BUILTIN_TYPES.contains(&name) { + return true; } + if self.analyzer.borrow().structs.contains_key(name) { + return true; + } + false } } From 5846f7e7305089146d4ec2b41d3eb58664287c25 Mon Sep 17 00:00:00 2001 From: Toni Date: Tue, 17 Mar 2026 15:25:28 +0100 Subject: [PATCH 03/12] casts, typecheck struct field access --- src/codegen_x86_64.rs | 29 +++++---- src/parser.rs | 24 ++++++- src/std/std.zr | 14 ++-- src/tokenizer.rs | 2 + src/typechecker.rs | 144 +++++++++++++++++++++++++++++++++++++++--- 5 files changed, 184 insertions(+), 29 deletions(-) diff --git a/src/codegen_x86_64.rs b/src/codegen_x86_64.rs index 94c0b11..05650a5 100644 --- a/src/codegen_x86_64.rs +++ b/src/codegen_x86_64.rs @@ -573,15 +573,9 @@ _builtin_environ: match left.as_ref() { Expr::Variable(name) => { - // TODO: move to analyzer let var = match env.get_var(&name.lexeme) { Some(x) => x, - None => { - return error!( - name.loc, - format!("undefined variable: {}", &name.lexeme) - ); - } + None => unreachable!(), }; emit!( &mut self.output, @@ -589,7 +583,11 @@ _builtin_environ: var.stack_offset, ); } - Expr::Index { expr, index } => { + Expr::Index { + expr, + bracket: _, + index, + } => { emit!(&mut self.output, " push rax"); self.compile_expr(env, expr)?; emit!(&mut self.output, " push rax"); @@ -688,7 +686,11 @@ _builtin_environ: } emit!(&mut self.output, " pop rax"); } - Expr::Index { expr, index } => { + Expr::Index { + expr, + bracket: _, + index, + } => { self.compile_expr(env, expr)?; emit!(&mut self.output, " push rax"); self.compile_expr(env, index)?; @@ -739,6 +741,9 @@ _builtin_environ: self.compile_expr(env, left)?; emit!(&mut self.output, " mov rax, QWORD [rax+{}]", offset); } + Expr::Cast { expr, type_name: _ } => { + self.compile_expr(env, expr)?; + } } Ok(()) } @@ -772,11 +777,11 @@ _builtin_environ: } }; - let offset = match fields.get(&field.lexeme) { - Some(o) => *o, + let field = match fields.get(&field.lexeme) { + Some(o) => o, None => return error!(&field.loc, format!("unknown field: {}", &field.lexeme)), }; - Ok(offset) + Ok(field.offset) } } diff --git a/src/parser.rs b/src/parser.rs index da88eaf..857fffb 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -83,6 +83,7 @@ pub enum Expr { ArrayLiteral(Vec), Index { expr: Box, + bracket: Token, index: Box, }, AddrOf { @@ -94,6 +95,10 @@ pub enum Expr { left: Box, field: Token, }, + Cast { + expr: Box, + type_name: Token, + }, } pub struct Parser { @@ -438,7 +443,7 @@ impl Parser { } fn factor(&mut self) -> Result { - let mut expr = self.unary()?; + let mut expr = self.cast()?; while self.match_token(&[ TokenType::Star, @@ -459,6 +464,20 @@ impl Parser { Ok(expr) } + fn cast(&mut self) -> Result { + let mut expr = self.unary()?; + + while self.match_token(&[TokenType::KeywordAs]) { + let type_name = self.consume(TokenType::Identifier, "expected type after 'as'")?; + expr = Expr::Cast { + expr: Box::new(expr), + type_name, + }; + } + + Ok(expr) + } + fn unary(&mut self) -> Result { if self.match_token(&[TokenType::Xor]) { let op = self.previous().clone(); @@ -504,9 +523,10 @@ impl Parser { }; } else if self.match_token(&[TokenType::LeftBracket]) { let index = self.expression()?; - self.consume(TokenType::RightBracket, "expected ']' after index")?; + let bracket = self.consume(TokenType::RightBracket, "expected ']' after index")?; expr = Expr::Index { expr: Box::new(expr), + bracket, index: Box::new(index), } } else if self.match_token(&[TokenType::Arrow]) { diff --git a/src/std/std.zr b/src/std/std.zr index be155bf..9d54377 100644 --- a/src/std/std.zr +++ b/src/std/std.zr @@ -47,7 +47,7 @@ func mem._align[x: i64] : i64 func mem._split_block[blk: mem.Block, needed: i64] : void if blk->size >= needed + MEM_BLOCK_SIZE + 8 - let new_blk: mem.Block = blk + MEM_BLOCK_SIZE + needed + let new_blk: mem.Block = (blk as ptr + MEM_BLOCK_SIZE + needed) as mem.Block new_blk->size = blk->size - needed - MEM_BLOCK_SIZE new_blk->free = true new_blk->next = blk->next @@ -70,17 +70,19 @@ func mem._request_space?[size: i64] : mem.Block // PROT_READ | PROT_WRITE = 3 // MAP_PRIVATE | MAP_ANONYMOUS = 34 // fd = -1, offset = 0 - let blk: mem.Block = _builtin_syscall(SYS_mmap, 0, alloc_size, 3, 34, -1, 0) - if blk == -1 + let blk_ptr: ptr = _builtin_syscall(SYS_mmap, 0, alloc_size, 3, 34, -1, 0) as ptr + if blk_ptr == -1 err.set(ERR_ALLOC_FAILED, "mem._request_space: mmap failed") return 0 + + let blk: mem.Block = blk_ptr as mem.Block blk->size = alloc_size - MEM_BLOCK_SIZE blk->free = false - blk->next = 0 - blk->prev = 0 + blk->next = 0 as mem.Block + blk->prev = 0 as mem.Block - let tail: mem.Block = mem.read64(_builtin_heap_tail()) + let tail: mem.Block = mem.read64(_builtin_heap_tail()) as mem.Block if tail tail->next = blk blk->prev = tail diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 3056a7a..445c93d 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -55,6 +55,7 @@ pub enum TokenType { KeywordExport, KeywordStruct, KeywordNew, + KeywordAs, Indent, Dedent, @@ -371,6 +372,7 @@ impl Tokenizer { "export" => TokenType::KeywordExport, "struct" => TokenType::KeywordStruct, "new" => TokenType::KeywordNew, + "as" => TokenType::KeywordAs, "true" => TokenType::True, "false" => TokenType::False, _ => TokenType::Identifier, diff --git a/src/typechecker.rs b/src/typechecker.rs index ac87ea3..06b5398 100644 --- a/src/typechecker.rs +++ b/src/typechecker.rs @@ -22,6 +22,21 @@ macro_rules! expect_type { }; } +macro_rules! expect_types { + ($expr_type:expr, [$( $expected:expr ),+], $loc:expr) => { + if $( $expr_type != $expected )&&+ { + return error!( + $loc, + format!( + "expected one of [{}], got '{}'", + [$( $expected ),+].join(", "), + $expr_type + ) + ); + } + }; +} + // TODO: currently they are all just 64 bit values static BUILTIN_TYPES: [&str; 8] = ["void", "u8", "i64", "str", "bool", "ptr", "fnptr", "any"]; @@ -119,6 +134,8 @@ impl TypeChecker { body, exported, } => { + env.push_scope(); + if !self.is_valid_type_name(&return_type.lexeme) { return error!( &return_type.loc, @@ -138,6 +155,8 @@ impl TypeChecker { } self.typecheck_stmt(env, body)?; + + env.pop_scope(); } Stmt::Return(expr) => { // TODO @@ -153,11 +172,31 @@ impl TypeChecker { pub fn typecheck_expr(&mut self, env: &mut Env, expr: &Expr) -> Result { match expr { Expr::Binary { left, op, right } => { - expect_type!(self.typecheck_expr(env, left)?, "i64", op.loc); - Ok("i64".into()) + expect_types!(self.typecheck_expr(env, left)?, ["i64", "ptr"], op.loc); + expect_types!(self.typecheck_expr(env, right)?, ["i64", "ptr"], op.loc); + + match op.token_type { + TokenType::Plus + | TokenType::Minus + | TokenType::Star + | TokenType::Slash + | TokenType::Mod + | TokenType::Xor + | TokenType::BitAnd + | TokenType::BitOr + | TokenType::ShiftLeft + | TokenType::ShiftRight => Ok("i64".into()), + TokenType::DoubleEqual + | TokenType::NotEqual + | TokenType::Greater + | TokenType::GreaterEqual + | TokenType::Less + | TokenType::LessEqual => Ok("bool".into()), + _ => unreachable!(), + } } Expr::Logical { left, op, right } => todo!(), - Expr::Grouping(expr) => todo!(), + Expr::Grouping(expr) => self.typecheck_expr(env, expr), Expr::Literal(token) => match token.token_type { TokenType::Number => Ok("i64".into()), TokenType::Char => Ok("u8".into()), @@ -166,10 +205,23 @@ impl TypeChecker { TokenType::False => Ok("bool".into()), _ => unreachable!(), }, - Expr::Unary { op, right } => todo!(), + Expr::Unary { op, right } => { + let right_type = self.typecheck_expr(env, right)?; + match op.token_type { + TokenType::Minus => { + expect_type!(right_type, "i64", op.loc); + Ok("i64".into()) + } + TokenType::Bang => { + expect_type!(right_type, "bool", op.loc); + Ok("bool".into()) + } + _ => unreachable!(), + } + } Expr::Variable(name) => { if self.analyzer.borrow().constants.contains_key(&name.lexeme) { - Ok("fnptr".into()) + Ok("i64".into()) } else { match env.get_var_type(&name.lexeme) { Some(x) => Ok(x.clone()), @@ -177,7 +229,60 @@ impl TypeChecker { } } } - Expr::Assign { left, op, value } => todo!(), + Expr::Assign { left, op, value } => { + let value_type = self.typecheck_expr(env, value)?; + + match left.as_ref() { + Expr::Variable(name) => { + let var_type = match env.get_var_type(&name.lexeme) { + Some(x) => x, + None => { + return error!( + name.loc, + format!("undefined variable: {}", &name.lexeme) + ); + } + }; + expect_type!(*var_type, value_type, name.loc); + } + Expr::Index { + expr, + bracket, + index, + } => { + expect_type!(self.typecheck_expr(env, expr)?, "ptr", bracket.loc); + expect_type!(self.typecheck_expr(env, index)?, "u8", bracket.loc); + } + Expr::MemberAccess { left, field } => { + let left_type = self.typecheck_expr(env, left)?; + + let analyzer = self.analyzer.borrow(); + let fields = match analyzer.structs.get(&left_type) { + Some(f) => f, + None => { + return error!( + &field.loc, + format!("unknown struct type: {}", left_type) + ); + } + }; + + let f = match fields.get(&field.lexeme) { + Some(o) => o, + None => { + return error!( + &field.loc, + format!("unknown field: {}", &field.lexeme) + ); + } + }; + + expect_type!(value_type, f.field_type, field.loc); + } + _ => return error!(&op.loc, "invalid assignment target"), + } + Ok(value_type) + } Expr::Call { callee, paren, @@ -201,6 +306,7 @@ impl TypeChecker { } for arg in args { + // TODO: actually check against the function we're calling self.typecheck_expr(env, arg)?; } @@ -208,14 +314,34 @@ impl TypeChecker { Ok("any".into()) } Expr::ArrayLiteral(exprs) => todo!(), - Expr::Index { expr, index } => todo!(), + Expr::Index { + expr, + bracket, + index, + } => todo!(), Expr::AddrOf { op, expr } => todo!(), Expr::New(token) => todo!(), Expr::MemberAccess { left, field } => { let left_type = self.typecheck_expr(env, left)?; - // TODO: actually look up left_type->field type - Ok("any".into()) + let analyzer = self.analyzer.borrow(); + let fields = match analyzer.structs.get(&left_type) { + Some(f) => f, + None => { + return error!(&field.loc, format!("unknown struct type: {}", left_type)); + } + }; + + let field = match fields.get(&field.lexeme) { + Some(o) => o, + None => return error!(&field.loc, format!("unknown field: {}", &field.lexeme)), + }; + + Ok(field.field_type.clone()) + } + Expr::Cast { expr, type_name } => { + self.typecheck_expr(env, expr)?; + Ok(type_name.lexeme.clone()) } } } From a3f480dc1d086d0652e09748c7949be1b94ceb31 Mon Sep 17 00:00:00 2001 From: Toni Date: Tue, 17 Mar 2026 16:10:28 +0100 Subject: [PATCH 04/12] typecheck function calls --- src/analyzer.rs | 79 +++++++++++++++++++++-------- src/std/net.zr | 6 +-- src/std/std.zr | 120 ++++++++++++++++++++++----------------------- src/typechecker.rs | 114 +++++++++++++++++++++++++++++++----------- 4 files changed, 206 insertions(+), 113 deletions(-) diff --git a/src/analyzer.rs b/src/analyzer.rs index 2abb0e7..c123a38 100644 --- a/src/analyzer.rs +++ b/src/analyzer.rs @@ -5,25 +5,36 @@ use crate::{ tokenizer::{ZernError, error}, }; +pub struct StructField { + pub offset: usize, + pub field_type: String, +} + pub struct Analyzer { - pub functions: HashMap, + pub functions: HashMap>)>, pub constants: HashMap, - pub structs: HashMap>, + pub structs: HashMap>, } impl Analyzer { pub fn new() -> Analyzer { Analyzer { functions: HashMap::from([ - ("_builtin_heap_head".into(), 0), - ("_builtin_heap_tail".into(), 0), - ("_builtin_err_code".into(), 0), - ("_builtin_err_msg".into(), 0), - ("_builtin_read64".into(), 1), - ("_builtin_set64".into(), 2), - ("_builtin_syscall".into(), -1), - ("io.printf".into(), -1), - ("_builtin_environ".into(), 0), + ("_builtin_heap_head".into(), ("ptr".into(), Some(vec![]))), + ("_builtin_heap_tail".into(), ("ptr".into(), Some(vec![]))), + ("_builtin_err_code".into(), ("ptr".into(), Some(vec![]))), + ("_builtin_err_msg".into(), ("ptr".into(), Some(vec![]))), + ( + "_builtin_read64".into(), + ("i64".into(), Some(vec!["ptr".into()])), + ), + ( + "_builtin_set64".into(), + ("void".into(), Some(vec!["ptr".into(), "i64".into()])), + ), + ("_builtin_syscall".into(), ("any".into(), None)), + ("io.printf".into(), ("void".into(), None)), + ("_builtin_environ".into(), ("ptr".into(), Some(vec![]))), ]), constants: HashMap::new(), structs: HashMap::new(), @@ -34,7 +45,7 @@ impl Analyzer { if let Stmt::Function { name, params, - return_type: _, + return_type, body: _, exported: _, } = stmt @@ -42,8 +53,13 @@ impl Analyzer { if self.functions.contains_key(&name.lexeme) { return error!(name.loc, format!("tried to redefine '{}'", name.lexeme)); } - self.functions - .insert(name.lexeme.clone(), params.len() as i32); + self.functions.insert( + name.lexeme.clone(), + ( + return_type.lexeme.clone(), + Some(params.iter().map(|x| x.var_type.lexeme.clone()).collect()), + ), + ); } Ok(()) } @@ -127,14 +143,21 @@ impl Analyzer { if self.functions.contains_key(&name.lexeme) { return error!(name.loc, format!("tried to redefine '{}'", name.lexeme)); } - self.functions.insert(name.lexeme.clone(), -1); + self.functions + .insert(name.lexeme.clone(), ("any".into(), None)); } Stmt::Struct { name, fields } => { - let mut fields_map: HashMap = HashMap::new(); + let mut fields_map: HashMap = HashMap::new(); let mut offset: usize = 0; for field in fields { - fields_map.insert(field.var_name.lexeme.clone(), offset); + fields_map.insert( + field.var_name.lexeme.clone(), + StructField { + offset, + field_type: field.var_type.lexeme.clone(), + }, + ); offset += 8; } @@ -172,11 +195,18 @@ impl Analyzer { if let Expr::Variable(callee_name) = *callee.clone() { if self.functions.contains_key(&callee_name.lexeme) { // its a function (defined/builtin/extern) - if let Some(arity) = self.functions.get(&callee_name.lexeme) { - if *arity >= 0 && *arity != args.len() as i32 { + if let Some(params) = self.functions.get(&callee_name.lexeme) { + // if its None, its variadic + if let (_, Some(params)) = params + && params.len() != args.len() + { return error!( &paren.loc, - format!("expected {} arguments, got {}", arity, args.len()) + format!( + "expected {} arguments, got {}", + params.len(), + args.len() + ) ); } } else { @@ -203,7 +233,11 @@ impl Analyzer { self.analyze_expr(expr)?; } } - Expr::Index { expr, index } => { + Expr::Index { + expr, + bracket: _, + index, + } => { self.analyze_expr(expr)?; self.analyze_expr(index)?; } @@ -214,6 +248,9 @@ impl Analyzer { Expr::MemberAccess { left, field: _ } => { self.analyze_expr(left)?; } + Expr::Cast { expr, type_name: _ } => { + self.analyze_expr(expr)?; + } } Ok(()) } diff --git a/src/std/net.zr b/src/std/net.zr index f47df90..1a6bbca 100644 --- a/src/std/net.zr +++ b/src/std/net.zr @@ -81,7 +81,7 @@ func net.read[s: i64, buffer: ptr, size: i64] : i64 func net.close[s: i64] : void _builtin_syscall(SYS_close, s) -func net.pack_addr[a: i64, b: i64, c: i64, d: i64] : i64 +func net.pack_addr[a: u8, b: u8, c: u8, d: u8] : i64 return (a << 24) | (b << 16) | (c << 8) | d struct net.UDPSocket @@ -166,7 +166,7 @@ func net.encode_dns_name[domain: str] : io.Buffer let part_len: i64 = i - part_start buf->data[out_pos] = part_len & 0xff out_pos = out_pos + 1 - mem.copy(domain + part_start, buf->data + out_pos, part_len) + mem.copy(domain as ptr + part_start, buf->data + out_pos, part_len) out_pos = out_pos + part_len part_start = i + 1 i = i + 1 @@ -201,7 +201,7 @@ func net.resolve?[domain: str] : i64 let query: io.Buffer = net.build_dns_query(domain, DNS_TYPE_A) // TODO: this probably shouldnt be hardcoded - let s: net.UDPSocket = net.create_udp_client?(net.pack_addr(8, 8, 8, 8), 53) + let s: net.UDPSocket = net.create_udp_client?(net.pack_addr(8 as u8, 8 as u8, 8 as u8, 8 as u8), 53) if err.check() return -1 diff --git a/src/std/std.zr b/src/std/std.zr index 9d54377..9906c88 100644 --- a/src/std/std.zr +++ b/src/std/std.zr @@ -16,11 +16,11 @@ func err.check[] : bool return err.code() != 0 func err.clear[] : void - err.set(0, 0) + err.set(0, 0 as str) func err.set[code: i64, msg: str] : void mem.write64(_builtin_err_code(), code) - mem.write64(_builtin_err_msg(), msg) + mem.write64(_builtin_err_msg(), msg as ptr as i64) func must[x: any] : any if err.check() @@ -57,7 +57,7 @@ func mem._split_block[blk: mem.Block, needed: i64] : void let next: mem.Block = blk->next next->prev = new_blk else - mem.write64(_builtin_heap_tail(), new_blk) + mem.write64(_builtin_heap_tail(), new_blk as ptr as i64) blk->size = needed blk->next = new_blk @@ -87,7 +87,7 @@ func mem._request_space?[size: i64] : mem.Block tail->next = blk blk->prev = tail - mem.write64(_builtin_heap_tail(), blk) + mem.write64(_builtin_heap_tail(), blk as ptr as i64) return blk func mem.alloc?[size: i64] : ptr @@ -98,7 +98,7 @@ func mem.alloc?[size: i64] : ptr size = mem._align(size) - let cur: mem.Block = mem.read64(_builtin_heap_head()) + let cur: mem.Block = mem.read64(_builtin_heap_head()) as mem.Block while cur if cur->free && cur->size >= size mem._split_block(cur, size) @@ -111,7 +111,7 @@ func mem.alloc?[size: i64] : ptr return 0 if !mem.read64(_builtin_heap_head()) - mem.write64(_builtin_heap_head(), blk) + mem.write64(_builtin_heap_head(), blk as ptr as i64) mem._split_block(blk, size) @@ -120,50 +120,50 @@ func mem.alloc?[size: i64] : ptr func mem.alloc[size: i64] : ptr return must(mem.alloc?(size)) -func mem.free[x: ptr] : void - if !x +func mem.free[x: any] : void + if !(x as ptr) return 0 - let blk: mem.Block = x - MEM_BLOCK_SIZE + let blk: mem.Block = (x as ptr - MEM_BLOCK_SIZE) as mem.Block blk->free = true let next: mem.Block = blk->next - if next && next->free - let expected_next: mem.Block = blk + MEM_BLOCK_SIZE + blk->size - if expected_next == next + if next as ptr && next->free + let expected_next: mem.Block = (blk as ptr + MEM_BLOCK_SIZE + blk->size) as mem.Block + if expected_next as ptr == next as ptr blk->size = blk->size + MEM_BLOCK_SIZE + next->size blk->next = next->next if next->next let b: mem.Block = next->next b->prev = blk - if mem.read64(_builtin_heap_tail()) == next - mem.write64(_builtin_heap_tail(), blk) + if mem.read64(_builtin_heap_tail()) == next as ptr + mem.write64(_builtin_heap_tail(), blk as ptr as i64) let prev: mem.Block = blk->prev - if prev && prev->free - let expected_blk: mem.Block = prev + MEM_BLOCK_SIZE + prev->size - if expected_blk == blk + if prev as ptr && prev->free + let expected_blk: mem.Block = (prev as ptr + MEM_BLOCK_SIZE + prev->size) as mem.Block + if expected_blk as ptr == blk as ptr prev->size = prev->size + MEM_BLOCK_SIZE + blk->size prev->next = blk->next if blk->next let b: mem.Block = blk->next b->prev = prev - if mem.read64(_builtin_heap_tail()) == blk - mem.write64(_builtin_heap_tail(), prev) + if mem.read64(_builtin_heap_tail()) == blk as ptr + mem.write64(_builtin_heap_tail(), prev as ptr as i64) blk = prev let block_total: i64 = blk->size + MEM_BLOCK_SIZE - if (blk & 4095) == 0 && (block_total & 4095) == 0 + if (blk as i64 & 4095) == 0 && (block_total & 4095) == 0 if blk->prev let b: mem.Block = blk->prev b->next = blk->next else - mem.write64(_builtin_heap_head(), blk->next) + mem.write64(_builtin_heap_head(), blk->next as ptr as i64) if blk->next let b: mem.Block = blk->next b->prev = blk->prev else - mem.write64(_builtin_heap_tail(), blk->prev) + mem.write64(_builtin_heap_tail(), blk->prev as ptr as i64) _builtin_syscall(SYS_munmap, blk, block_total) func mem.realloc?[x: ptr, new_size: i64] : ptr @@ -181,15 +181,15 @@ func mem.realloc?[x: ptr, new_size: i64] : ptr new_size = mem._align(new_size) - let blk: mem.Block = x - MEM_BLOCK_SIZE + let blk: mem.Block = (x - MEM_BLOCK_SIZE) as mem.Block if blk->size >= new_size mem._split_block(blk, new_size) return x let next: mem.Block = blk->next - let expected_next: mem.Block = blk + MEM_BLOCK_SIZE + blk->size + let expected_next: mem.Block = (blk as ptr + MEM_BLOCK_SIZE + blk->size) as mem.Block - if next && next->free && expected_next == next + if next as ptr && next->free && expected_next as ptr == next as ptr let combined: i64 = blk->size + MEM_BLOCK_SIZE + next->size if combined >= new_size blk->size = combined @@ -197,8 +197,8 @@ func mem.realloc?[x: ptr, new_size: i64] : ptr if next->next let b: mem.Block = next->next b->prev = blk - if mem.read64(_builtin_heap_tail()) == next - mem.write64(_builtin_heap_tail(), blk) + if mem.read64(_builtin_heap_tail()) == next as ptr + mem.write64(_builtin_heap_tail(), blk as ptr as i64) mem._split_block(blk, new_size) return x @@ -216,7 +216,7 @@ func mem.zero_and_free[x: ptr, size: i64] : void func mem.zero[x: ptr, size: i64] : void for i in 0..size - x[i] = 0 + x[i] = 0 as u8 func mem.copy[src: ptr, dst: ptr, n: i64] : void if dst > src @@ -264,7 +264,7 @@ func mem.write64[x: ptr, d: i64] : void _builtin_set64(x, d) // see emit_prologue for the io.printf wrapper -func io._printf_impl[s: str, args: ptr] : void +func io._printf_impl[s: ptr, args: ptr] : void while s[0] if s[0] == '%' s = s + 1 @@ -276,10 +276,10 @@ func io._printf_impl[s: str, args: ptr] : void io.print_i64_hex(mem.read64(args)) args = args + 8 else if s[0] == 's' - io.print(mem.read64(args)) + io.print(mem.read64(args) as str) args = args + 8 else if s[0] == 'c' - io.print_char(mem.read64(args)) + io.print_char(mem.read64(args) as u8) args = args + 8 else if s[0] == '%' io.print_char('%') @@ -300,7 +300,7 @@ func io.println[x: str] : void io.print("\n") func io.print_char[x: u8] : void - io.print_sized(^x, 1) + io.print_sized(^x as str, 1) func io.print_bool[x: bool] : void if x @@ -324,18 +324,18 @@ func io.println_i64[x: i64] : void mem.free(s) func io.read_char[] : u8 - let c: u8 = 0 + let c: u8 = 0 as u8 _builtin_syscall(SYS_read, 0, ^c, 1) return c func io.read_line[]: str let MAX_SIZE = 60000 - let buffer: str = mem.alloc(MAX_SIZE + 1) + let buffer: str = mem.alloc(MAX_SIZE + 1) as str let n: i64 = _builtin_syscall(SYS_read, 0, buffer, MAX_SIZE) if n < 0 n = 0 buffer[n] = 0 - buffer = must(mem.realloc?(buffer, n + 1)) + buffer = must(mem.realloc?(buffer, n + 1) as str) return buffer struct io.Buffer @@ -369,7 +369,7 @@ func io.read_text_file?[path: str] : str let size: i64 = _builtin_syscall(SYS_lseek, fd, 0, 2) _builtin_syscall(SYS_lseek, fd, 0, 0) - let buffer: str = mem.alloc?(size + 1) + let buffer: str = mem.alloc?(size + 1) as str if err.check() _builtin_syscall(SYS_close, fd) return 0 @@ -413,7 +413,7 @@ func io.read_binary_file?[path: str] : io.Buffer return buf func io.write_file?[path: str, content: str] : void - io.write_binary_file?(path, content, str.len(content)) + io.write_binary_file?(path, content as ptr, str.len(content)) func io.write_binary_file?[path: str, content: ptr, size: i64] : void err.clear() @@ -436,8 +436,8 @@ func str.len[s: str] : i64 func str.make_copy[s: str] : str let size: i64 = str.len(s) + 1 - let dup: str = mem.alloc(size) - mem.copy(s, dup, size) + let dup: str = mem.alloc(size) as str + mem.copy(s as ptr, dup as ptr, size) return dup func str.equal[a: str, b: str] : bool @@ -472,9 +472,9 @@ func str.is_alphanumeric[x: u8] : bool func str.concat[a: str, b: str] : str let a_len: i64 = str.len(a) let b_len: i64 = str.len(b) - let out: str = mem.alloc(a_len + b_len + 1) - mem.copy(a, out, a_len) - mem.copy(b, out + a_len, b_len) + let out: str = mem.alloc(a_len + b_len + 1) as str + mem.copy(a as ptr, out as ptr, a_len) + mem.copy(b as ptr, out as ptr + a_len, b_len) out[a_len + b_len] = 0 return out @@ -505,15 +505,15 @@ func str.substr[s: str, start: i64, length: i64] : str if start < 0 || length < 0 || start + length > str.len(s) panic("str.substr out of bounds") - let out: str = mem.alloc(length + 1) - mem.copy(s + start, out, length) + let out: str = mem.alloc(length + 1) as str + mem.copy(s as ptr + start, out as ptr, length) out[length] = 0 return out func str.trim[s: str] : str let len: i64 = str.len(s) if len == 0 - let out: str = mem.alloc(1) + let out: str = mem.alloc(1) as str out[0] = 0 return out @@ -562,7 +562,7 @@ func str.split[haystack: str, needle: str]: Array func str.reverse[s: str] : str let len: i64 = str.len(s) - let out: str = mem.alloc(len + 1) + let out: str = mem.alloc(len + 1) as str for i in 0..len out[i] = s[len - i - 1] @@ -571,7 +571,7 @@ func str.reverse[s: str] : str func str.from_i64[n: i64] : str if n == 0 - let out: str = mem.alloc(2) + let out: str = mem.alloc(2) as str out[0] = '0' out[1] = 0 return out @@ -582,7 +582,7 @@ func str.from_i64[n: i64] : str if n == -9223372036854775808 return str.make_copy("-9223372036854775808") n = -n - let buf: str = mem.alloc(21) // enough to fit -MAX_I64 + let buf: str = mem.alloc(21) as str // enough to fit -MAX_I64 let end = 20 buf[end] = 0 end = end - 1 @@ -593,7 +593,7 @@ func str.from_i64[n: i64] : str if neg buf[end] = '-' end = end - 1 - let s: str = str.make_copy(buf + end + 1) + let s: str = str.make_copy((buf as ptr + end + 1) as str) mem.free(buf) return s @@ -601,13 +601,13 @@ func str.hex_from_i64[n: i64] : str let hex_chars: str = "0123456789abcdef" if n == 0 - let out: str = mem.alloc(2) + let out: str = mem.alloc(2) as str out[0] = '0' out[1] = 0 return out let mask: i64 = (1 << 60) - 1 - let buf: str = mem.alloc(17) + let buf: str = mem.alloc(17) as str let len = 0 while n != 0 @@ -615,7 +615,7 @@ func str.hex_from_i64[n: i64] : str n = (n >> 4) & mask len = len + 1 - let out: str = mem.alloc(len + 1) + let out: str = mem.alloc(len + 1) as str let j = 0 while j < len out[j] = buf[len - 1 - j] @@ -626,7 +626,7 @@ func str.hex_from_i64[n: i64] : str return out func str.from_char[c: u8] : str - let s: str = mem.alloc(2) + let s: str = mem.alloc(2) as str s[0] = c s[1] = 0 return s @@ -652,7 +652,7 @@ func str.parse_i64[s: str] : i64 func str.hex_encode[s: str, s_len: i64] : str let hex_chars: str = "0123456789abcdef" let j = 0 - let out: str = mem.alloc(s_len * 2 + 1) + let out: str = mem.alloc(s_len * 2 + 1) as str for i in 0..s_len let high: u8 = (s[i] >> 4) & 15 @@ -678,7 +678,7 @@ func str.hex_decode[s: str] : str let i = 0 let j = 0 - let out: str = mem.alloc(s_len / 2 + 1) + let out: str = mem.alloc(s_len / 2 + 1) as str while i < s_len out[j] = str._hex_digit_to_int(s[i]) * 16 + str._hex_digit_to_int(s[i + 1]) @@ -774,7 +774,7 @@ func array.new[] : Array func array.new_with_size_zeroed[size: i64] : Array let xs: Array = new Array if size > 0 - xs->data = must(mem.realloc?(xs->data, size * 8)) + xs->data = must(mem.realloc?(xs->data, size * 8)) as ptr mem.zero(xs->data, size * 8) xs->size = size xs->capacity = size @@ -843,7 +843,7 @@ func alg._partition[arr: Array, low: i64, high: i64] : i64 let pivot: i64 = array.nth(arr, high) let i: i64 = low - 1 for j in (low)..high - if array.nth(arr, j) <= pivot + if array.nth(arr, j) as i64 <= pivot i = i + 1 let temp: i64 = array.nth(arr ,i) array.set(arr, i, array.nth(arr, j)) @@ -866,14 +866,14 @@ func alg.map[arr: Array, fn: ptr] : Array array.set(out, i, fn(array.nth(arr, i))) return out -func alg.filter[arr: Array, fn: ptr] : Array +func alg.filter[arr: Array, fn: fnptr] : Array let out: Array = [] for i in 0..arr->size if fn(array.nth(arr, i)) array.push(out, array.nth(arr, i)) return out -func alg.reduce[arr: Array, fn: ptr, acc: any] : any +func alg.reduce[arr: Array, fn: fnptr, acc: any] : any for i in 0..arr->size acc = fn(acc, array.nth(arr, i)) return acc @@ -990,7 +990,7 @@ func os.list_directory?[path: str] : Array let pos = 0 while pos < n let len: i64 = mem.read16(buf + pos + 16) - let name: str = buf + pos + 19 + let name: ptr = buf + pos + 19 if name[0] let skip: bool = false // skip if name is exactly '.' or '..' diff --git a/src/typechecker.rs b/src/typechecker.rs index 06b5398..d1fb0dc 100644 --- a/src/typechecker.rs +++ b/src/typechecker.rs @@ -13,7 +13,7 @@ type Type = String; macro_rules! expect_type { ($expr_type:expr, $expected:expr, $loc:expr) => { - if $expr_type != $expected { + if $expected != "any" && $expr_type != "any" && $expr_type != $expected { return error!( $loc, format!("expected type '{}', got '{}'", $expected, $expr_type) @@ -24,7 +24,7 @@ macro_rules! expect_type { macro_rules! expect_types { ($expr_type:expr, [$( $expected:expr ),+], $loc:expr) => { - if $( $expr_type != $expected )&&+ { + if $expr_type != "any" && $( $expr_type != $expected )&&+ { return error!( $loc, format!( @@ -92,7 +92,7 @@ impl TypeChecker { var_type, initializer, } => { - let inferred = self.typecheck_expr(env, initializer)?; + let mut actual_type = self.typecheck_expr(env, initializer)?; if let Some(var_type) = var_type { if !self.is_valid_type_name(&var_type.lexeme) { return error!( @@ -100,10 +100,14 @@ impl TypeChecker { "unrecognized type: ".to_owned() + &var_type.lexeme ); } - expect_type!(inferred, var_type.lexeme, var_type.loc); + expect_type!(actual_type, var_type.lexeme, var_type.loc); + + if actual_type == "any" { + actual_type = var_type.lexeme.clone(); + } } - env.define_var(name.lexeme.clone(), inferred); + env.define_var(name.lexeme.clone(), actual_type); } Stmt::Const { name, value } => {} Stmt::Block(stmts) => { @@ -120,13 +124,24 @@ impl TypeChecker { self.typecheck_stmt(env, then_branch)?; self.typecheck_stmt(env, else_branch)?; } - Stmt::While { condition, body } => todo!(), + Stmt::While { condition, body } => { + self.typecheck_expr(env, condition)?; + self.typecheck_stmt(env, body)?; + } Stmt::For { var, start, end, body, - } => todo!(), + } => { + expect_type!(self.typecheck_expr(env, start)?, "i64", var.loc); + expect_type!(self.typecheck_expr(env, end)?, "i64", var.loc); + + env.push_scope(); + env.define_var(var.lexeme.clone(), "i64".into()); + self.typecheck_stmt(env, body)?; + env.pop_scope(); + } Stmt::Function { name, params, @@ -161,9 +176,11 @@ impl TypeChecker { Stmt::Return(expr) => { // TODO } - Stmt::Break => todo!(), - Stmt::Continue => todo!(), - Stmt::Extern(token) => todo!(), + Stmt::Break => {} + Stmt::Continue => {} + Stmt::Extern(_) => { + // handled in the analyzer + } Stmt::Struct { name, fields } => {} } Ok(()) @@ -172,8 +189,13 @@ impl TypeChecker { pub fn typecheck_expr(&mut self, env: &mut Env, expr: &Expr) -> Result { match expr { Expr::Binary { left, op, right } => { - expect_types!(self.typecheck_expr(env, left)?, ["i64", "ptr"], op.loc); - expect_types!(self.typecheck_expr(env, right)?, ["i64", "ptr"], op.loc); + let left_type = self.typecheck_expr(env, left)?; + expect_types!(left_type, ["i64", "ptr", "u8"], op.loc); + expect_types!( + self.typecheck_expr(env, right)?, + ["i64", "ptr", "u8"], + op.loc + ); match op.token_type { TokenType::Plus @@ -185,7 +207,7 @@ impl TypeChecker { | TokenType::BitAnd | TokenType::BitOr | TokenType::ShiftLeft - | TokenType::ShiftRight => Ok("i64".into()), + | TokenType::ShiftRight => Ok(left_type), TokenType::DoubleEqual | TokenType::NotEqual | TokenType::Greater @@ -195,7 +217,19 @@ impl TypeChecker { _ => unreachable!(), } } - Expr::Logical { left, op, right } => todo!(), + Expr::Logical { left, op, right } => { + expect_types!( + self.typecheck_expr(env, left)?, + ["bool", "i64", "ptr"], + op.loc + ); + expect_types!( + self.typecheck_expr(env, right)?, + ["bool", "i64", "ptr"], + op.loc + ); + Ok("bool".into()) + } Expr::Grouping(expr) => self.typecheck_expr(env, expr), Expr::Literal(token) => match token.token_type { TokenType::Number => Ok("i64".into()), @@ -213,7 +247,7 @@ impl TypeChecker { Ok("i64".into()) } TokenType::Bang => { - expect_type!(right_type, "bool", op.loc); + expect_types!(right_type, ["bool", "i64", "ptr"], op.loc); Ok("bool".into()) } _ => unreachable!(), @@ -250,8 +284,9 @@ impl TypeChecker { bracket, index, } => { - expect_type!(self.typecheck_expr(env, expr)?, "ptr", bracket.loc); - expect_type!(self.typecheck_expr(env, index)?, "u8", bracket.loc); + expect_types!(self.typecheck_expr(env, expr)?, ["ptr", "str"], bracket.loc); + expect_types!(self.typecheck_expr(env, index)?, ["i64", "u8"], bracket.loc); + expect_types!(value_type, ["u8", "i64"], bracket.loc); } Expr::MemberAccess { left, field } => { let left_type = self.typecheck_expr(env, left)?; @@ -295,32 +330,53 @@ impl TypeChecker { .functions .contains_key(&callee_name.lexeme) { - // its a function (defined/builtin/extern) + let params = self.analyzer.borrow().functions[&callee_name.lexeme].clone(); + if let (_, Some(params)) = params { + // its a function (defined/builtin/extern) + for (i, arg) in args.iter().enumerate() { + expect_type!(self.typecheck_expr(env, arg)?, params[i], paren.loc); + } + } + Ok(params.0) } else { // its a variable containing function address expect_type!(self.typecheck_expr(env, callee)?, "fnptr", paren.loc); + + for arg in args { + self.typecheck_expr(env, arg)?; + } + Ok("any".into()) } } else { // its an expression that evalutes to function address expect_type!(self.typecheck_expr(env, callee)?, "fnptr", paren.loc); - } - for arg in args { - // TODO: actually check against the function we're calling - self.typecheck_expr(env, arg)?; + for arg in args { + self.typecheck_expr(env, arg)?; + } + Ok("any".into()) } - - // TODO: actually look up return type - Ok("any".into()) } - Expr::ArrayLiteral(exprs) => todo!(), + Expr::ArrayLiteral(exprs) => { + for expr in exprs { + self.typecheck_expr(env, expr)?; + } + Ok("Array".into()) + } Expr::Index { expr, bracket, index, - } => todo!(), - Expr::AddrOf { op, expr } => todo!(), - Expr::New(token) => todo!(), + } => { + expect_types!(self.typecheck_expr(env, expr)?, ["ptr", "str"], bracket.loc); + expect_types!(self.typecheck_expr(env, index)?, ["i64", "u8"], bracket.loc); + Ok("u8".into()) + } + Expr::AddrOf { op, expr } => { + self.typecheck_expr(env, expr)?; + Ok("ptr".into()) + } + Expr::New(struct_name) => Ok(struct_name.lexeme.clone()), Expr::MemberAccess { left, field } => { let left_type = self.typecheck_expr(env, left)?; From 15a68a41d1367ada1085d9fd3e3b0b9ff9430638 Mon Sep 17 00:00:00 2001 From: Toni Date: Tue, 17 Mar 2026 16:27:39 +0100 Subject: [PATCH 05/12] use FnType instead of the tuple mess --- src/analyzer.rs | 63 ++++++++++++++++++++++++++++++---------------- src/std/std.zr | 54 +++++++++++++++++++-------------------- src/typechecker.rs | 11 ++++---- 3 files changed, 74 insertions(+), 54 deletions(-) diff --git a/src/analyzer.rs b/src/analyzer.rs index c123a38..6e4c84c 100644 --- a/src/analyzer.rs +++ b/src/analyzer.rs @@ -5,13 +5,37 @@ use crate::{ tokenizer::{ZernError, error}, }; +pub type Type = String; + pub struct StructField { pub offset: usize, - pub field_type: String, + pub field_type: Type, +} + +#[derive(Clone)] +pub struct FnType { + pub return_type: Type, + pub params: Option>, +} + +impl FnType { + fn new(return_type: &str, params: Vec<&str>) -> FnType { + FnType { + return_type: return_type.to_string(), + params: Some(params.iter().map(|x| x.to_string()).collect()), + } + } + + fn new_variadic(return_type: &str) -> FnType { + FnType { + return_type: return_type.to_string(), + params: None, + } + } } pub struct Analyzer { - pub functions: HashMap>)>, + pub functions: HashMap, pub constants: HashMap, pub structs: HashMap>, } @@ -20,21 +44,18 @@ impl Analyzer { pub fn new() -> Analyzer { Analyzer { functions: HashMap::from([ - ("_builtin_heap_head".into(), ("ptr".into(), Some(vec![]))), - ("_builtin_heap_tail".into(), ("ptr".into(), Some(vec![]))), - ("_builtin_err_code".into(), ("ptr".into(), Some(vec![]))), - ("_builtin_err_msg".into(), ("ptr".into(), Some(vec![]))), - ( - "_builtin_read64".into(), - ("i64".into(), Some(vec!["ptr".into()])), - ), + ("_builtin_heap_head".into(), FnType::new("ptr", vec![])), + ("_builtin_heap_tail".into(), FnType::new("ptr", vec![])), + ("_builtin_err_code".into(), FnType::new("ptr", vec![])), + ("_builtin_err_msg".into(), FnType::new("ptr", vec![])), + ("_builtin_read64".into(), FnType::new("i64", vec!["ptr"])), ( "_builtin_set64".into(), - ("void".into(), Some(vec!["ptr".into(), "i64".into()])), + FnType::new("void", vec!["ptr", "any"]), ), - ("_builtin_syscall".into(), ("any".into(), None)), - ("io.printf".into(), ("void".into(), None)), - ("_builtin_environ".into(), ("ptr".into(), Some(vec![]))), + ("_builtin_syscall".into(), FnType::new_variadic("any")), + ("io.printf".into(), FnType::new_variadic("void")), + ("_builtin_environ".into(), FnType::new("ptr", vec![])), ]), constants: HashMap::new(), structs: HashMap::new(), @@ -55,10 +76,10 @@ impl Analyzer { } self.functions.insert( name.lexeme.clone(), - ( - return_type.lexeme.clone(), - Some(params.iter().map(|x| x.var_type.lexeme.clone()).collect()), - ), + FnType { + return_type: return_type.lexeme.clone(), + params: Some(params.iter().map(|x| x.var_type.lexeme.clone()).collect()), + }, ); } Ok(()) @@ -144,7 +165,7 @@ impl Analyzer { return error!(name.loc, format!("tried to redefine '{}'", name.lexeme)); } self.functions - .insert(name.lexeme.clone(), ("any".into(), None)); + .insert(name.lexeme.clone(), FnType::new_variadic("any")); } Stmt::Struct { name, fields } => { let mut fields_map: HashMap = HashMap::new(); @@ -195,9 +216,9 @@ impl Analyzer { if let Expr::Variable(callee_name) = *callee.clone() { if self.functions.contains_key(&callee_name.lexeme) { // its a function (defined/builtin/extern) - if let Some(params) = self.functions.get(&callee_name.lexeme) { + if let Some(fn_type) = self.functions.get(&callee_name.lexeme) { // if its None, its variadic - if let (_, Some(params)) = params + if let Some(params) = &fn_type.params && params.len() != args.len() { return error!( diff --git a/src/std/std.zr b/src/std/std.zr index 9906c88..a1d7ce6 100644 --- a/src/std/std.zr +++ b/src/std/std.zr @@ -20,7 +20,7 @@ func err.clear[] : void func err.set[code: i64, msg: str] : void mem.write64(_builtin_err_code(), code) - mem.write64(_builtin_err_msg(), msg as ptr as i64) + mem.write64(_builtin_err_msg(), msg) func must[x: any] : any if err.check() @@ -57,7 +57,7 @@ func mem._split_block[blk: mem.Block, needed: i64] : void let next: mem.Block = blk->next next->prev = new_blk else - mem.write64(_builtin_heap_tail(), new_blk as ptr as i64) + mem.write64(_builtin_heap_tail(), new_blk) blk->size = needed blk->next = new_blk @@ -87,10 +87,10 @@ func mem._request_space?[size: i64] : mem.Block tail->next = blk blk->prev = tail - mem.write64(_builtin_heap_tail(), blk as ptr as i64) + mem.write64(_builtin_heap_tail(), blk) return blk -func mem.alloc?[size: i64] : ptr +func mem.alloc?[size: i64] : any err.clear() if size <= 0 err.set(ERR_ALLOC_FAILED, "mem.alloc? called with non-positive size") @@ -111,13 +111,13 @@ func mem.alloc?[size: i64] : ptr return 0 if !mem.read64(_builtin_heap_head()) - mem.write64(_builtin_heap_head(), blk as ptr as i64) + mem.write64(_builtin_heap_head(), blk) mem._split_block(blk, size) return blk + MEM_BLOCK_SIZE -func mem.alloc[size: i64] : ptr +func mem.alloc[size: i64] : any return must(mem.alloc?(size)) func mem.free[x: any] : void @@ -137,7 +137,7 @@ func mem.free[x: any] : void let b: mem.Block = next->next b->prev = blk if mem.read64(_builtin_heap_tail()) == next as ptr - mem.write64(_builtin_heap_tail(), blk as ptr as i64) + mem.write64(_builtin_heap_tail(), blk) let prev: mem.Block = blk->prev if prev as ptr && prev->free @@ -149,7 +149,7 @@ func mem.free[x: any] : void let b: mem.Block = blk->next b->prev = prev if mem.read64(_builtin_heap_tail()) == blk as ptr - mem.write64(_builtin_heap_tail(), prev as ptr as i64) + mem.write64(_builtin_heap_tail(), prev) blk = prev let block_total: i64 = blk->size + MEM_BLOCK_SIZE @@ -158,12 +158,12 @@ func mem.free[x: any] : void let b: mem.Block = blk->prev b->next = blk->next else - mem.write64(_builtin_heap_head(), blk->next as ptr as i64) + mem.write64(_builtin_heap_head(), blk->next) if blk->next let b: mem.Block = blk->next b->prev = blk->prev else - mem.write64(_builtin_heap_tail(), blk->prev as ptr as i64) + mem.write64(_builtin_heap_tail(), blk->prev) _builtin_syscall(SYS_munmap, blk, block_total) func mem.realloc?[x: ptr, new_size: i64] : ptr @@ -198,7 +198,7 @@ func mem.realloc?[x: ptr, new_size: i64] : ptr let b: mem.Block = next->next b->prev = blk if mem.read64(_builtin_heap_tail()) == next as ptr - mem.write64(_builtin_heap_tail(), blk as ptr as i64) + mem.write64(_builtin_heap_tail(), blk) mem._split_block(blk, new_size) return x @@ -260,7 +260,7 @@ func mem.write16be[x: ptr, d: i64] : void x[0] = (d >> 8) & 0xff x[1] = d & 0xff -func mem.write64[x: ptr, d: i64] : void +func mem.write64[x: ptr, d: any] : void _builtin_set64(x, d) // see emit_prologue for the io.printf wrapper @@ -330,12 +330,12 @@ func io.read_char[] : u8 func io.read_line[]: str let MAX_SIZE = 60000 - let buffer: str = mem.alloc(MAX_SIZE + 1) as str + let buffer: str = mem.alloc(MAX_SIZE + 1) let n: i64 = _builtin_syscall(SYS_read, 0, buffer, MAX_SIZE) if n < 0 n = 0 buffer[n] = 0 - buffer = must(mem.realloc?(buffer, n + 1) as str) + buffer = must(mem.realloc?(buffer, n + 1)) return buffer struct io.Buffer @@ -436,7 +436,7 @@ func str.len[s: str] : i64 func str.make_copy[s: str] : str let size: i64 = str.len(s) + 1 - let dup: str = mem.alloc(size) as str + let dup: str = mem.alloc(size) mem.copy(s as ptr, dup as ptr, size) return dup @@ -472,7 +472,7 @@ func str.is_alphanumeric[x: u8] : bool func str.concat[a: str, b: str] : str let a_len: i64 = str.len(a) let b_len: i64 = str.len(b) - let out: str = mem.alloc(a_len + b_len + 1) as str + let out: str = mem.alloc(a_len + b_len + 1) mem.copy(a as ptr, out as ptr, a_len) mem.copy(b as ptr, out as ptr + a_len, b_len) out[a_len + b_len] = 0 @@ -505,7 +505,7 @@ func str.substr[s: str, start: i64, length: i64] : str if start < 0 || length < 0 || start + length > str.len(s) panic("str.substr out of bounds") - let out: str = mem.alloc(length + 1) as str + let out: str = mem.alloc(length + 1) mem.copy(s as ptr + start, out as ptr, length) out[length] = 0 return out @@ -513,7 +513,7 @@ func str.substr[s: str, start: i64, length: i64] : str func str.trim[s: str] : str let len: i64 = str.len(s) if len == 0 - let out: str = mem.alloc(1) as str + let out: str = mem.alloc(1) out[0] = 0 return out @@ -562,7 +562,7 @@ func str.split[haystack: str, needle: str]: Array func str.reverse[s: str] : str let len: i64 = str.len(s) - let out: str = mem.alloc(len + 1) as str + let out: str = mem.alloc(len + 1) for i in 0..len out[i] = s[len - i - 1] @@ -571,7 +571,7 @@ func str.reverse[s: str] : str func str.from_i64[n: i64] : str if n == 0 - let out: str = mem.alloc(2) as str + let out: str = mem.alloc(2) out[0] = '0' out[1] = 0 return out @@ -582,7 +582,7 @@ func str.from_i64[n: i64] : str if n == -9223372036854775808 return str.make_copy("-9223372036854775808") n = -n - let buf: str = mem.alloc(21) as str // enough to fit -MAX_I64 + let buf: str = mem.alloc(21) // enough to fit -MAX_I64 let end = 20 buf[end] = 0 end = end - 1 @@ -601,13 +601,13 @@ func str.hex_from_i64[n: i64] : str let hex_chars: str = "0123456789abcdef" if n == 0 - let out: str = mem.alloc(2) as str + let out: str = mem.alloc(2) out[0] = '0' out[1] = 0 return out let mask: i64 = (1 << 60) - 1 - let buf: str = mem.alloc(17) as str + let buf: str = mem.alloc(17) let len = 0 while n != 0 @@ -615,7 +615,7 @@ func str.hex_from_i64[n: i64] : str n = (n >> 4) & mask len = len + 1 - let out: str = mem.alloc(len + 1) as str + let out: str = mem.alloc(len + 1) let j = 0 while j < len out[j] = buf[len - 1 - j] @@ -626,7 +626,7 @@ func str.hex_from_i64[n: i64] : str return out func str.from_char[c: u8] : str - let s: str = mem.alloc(2) as str + let s: str = mem.alloc(2) s[0] = c s[1] = 0 return s @@ -652,7 +652,7 @@ func str.parse_i64[s: str] : i64 func str.hex_encode[s: str, s_len: i64] : str let hex_chars: str = "0123456789abcdef" let j = 0 - let out: str = mem.alloc(s_len * 2 + 1) as str + let out: str = mem.alloc(s_len * 2 + 1) for i in 0..s_len let high: u8 = (s[i] >> 4) & 15 @@ -678,7 +678,7 @@ func str.hex_decode[s: str] : str let i = 0 let j = 0 - let out: str = mem.alloc(s_len / 2 + 1) as str + let out: str = mem.alloc(s_len / 2 + 1) while i < s_len out[j] = str._hex_digit_to_int(s[i]) * 16 + str._hex_digit_to_int(s[i + 1]) diff --git a/src/typechecker.rs b/src/typechecker.rs index d1fb0dc..d044521 100644 --- a/src/typechecker.rs +++ b/src/typechecker.rs @@ -4,13 +4,11 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc}; use crate::{ - analyzer::Analyzer, + analyzer::{Analyzer, Type}, parser::{Expr, Stmt}, tokenizer::{TokenType, ZernError, error}, }; -type Type = String; - macro_rules! expect_type { ($expr_type:expr, $expected:expr, $loc:expr) => { if $expected != "any" && $expr_type != "any" && $expr_type != $expected { @@ -330,14 +328,15 @@ impl TypeChecker { .functions .contains_key(&callee_name.lexeme) { - let params = self.analyzer.borrow().functions[&callee_name.lexeme].clone(); - if let (_, Some(params)) = params { + let fn_type = + &self.analyzer.borrow().functions[&callee_name.lexeme].clone(); + if let Some(params) = fn_type.params.clone() { // its a function (defined/builtin/extern) for (i, arg) in args.iter().enumerate() { expect_type!(self.typecheck_expr(env, arg)?, params[i], paren.loc); } } - Ok(params.0) + Ok(fn_type.return_type.clone()) } else { // its a variable containing function address expect_type!(self.typecheck_expr(env, callee)?, "fnptr", paren.loc); From 664a51c88de6b597117627dd13cdb4f654c878d6 Mon Sep 17 00:00:00 2001 From: Toni Date: Tue, 17 Mar 2026 16:34:20 +0100 Subject: [PATCH 06/12] add casts to examples --- examples/curl.zr | 8 ++++---- examples/puzzles/aoc2025_03.zr | 2 +- examples/tcp_server.zr | 2 +- examples/udp_server.zr | 2 +- src/std/std.zr | 8 ++++---- src/typechecker.rs | 19 +++++++++++++------ 6 files changed, 24 insertions(+), 17 deletions(-) diff --git a/examples/curl.zr b/examples/curl.zr index 6034ddf..2f7540f 100644 --- a/examples/curl.zr +++ b/examples/curl.zr @@ -3,7 +3,7 @@ func main[argc: i64, argv: ptr] : i64 io.println("ERROR: url is missing") return 1 - let url: str = mem.read64(argv + 8) + let url: str = mem.read64(argv + 8) as str if str.len(url) <= 7 io.println("ERROR: invalid url (Did you forget \"http://\"?)") @@ -36,7 +36,7 @@ func main[argc: i64, argv: ptr] : i64 req = str.concat(req, " HTTP/1.0\r\nHost: ") req = str.concat(req, host) req = str.concat(req, "\r\nConnection: close\r\n\r\n") - net.send(s, req, str.len(req)) + net.send(s, req as ptr, str.len(req)) mem.free(req) let header_buf: str = mem.alloc(8192) @@ -45,7 +45,7 @@ func main[argc: i64, argv: ptr] : i64 let end_index: i64 = -1 while !found && header_size < 8192 - let n: i64 = net.read(s, header_buf + header_size, 8192 - header_size) + let n: i64 = net.read(s, header_buf as ptr + header_size, 8192 - header_size) if n <= 0 break let current_size: i64 = header_size + n @@ -59,7 +59,7 @@ func main[argc: i64, argv: ptr] : i64 header_size = current_size if end_index < header_size - io.print_sized(header_buf + end_index, header_size - end_index) + io.print_sized(header_buf as ptr + end_index, header_size as i64 - end_index) mem.free(header_buf) let buffer: ptr = mem.alloc(4096) diff --git a/examples/puzzles/aoc2025_03.zr b/examples/puzzles/aoc2025_03.zr index 6054439..61cc246 100644 --- a/examples/puzzles/aoc2025_03.zr +++ b/examples/puzzles/aoc2025_03.zr @@ -25,7 +25,7 @@ func part2_rec[bank: str, start: i64, remaining: i64] : i64 let len: i64 = str.len(bank) for i in (start)..(len-remaining+1) - let v: i64 = bank[i] - '0' + let v: i64 = (bank[i] - '0') as i64 if v > largest largest = v largest_idx = i diff --git a/examples/tcp_server.zr b/examples/tcp_server.zr index de94fb7..a45250f 100644 --- a/examples/tcp_server.zr +++ b/examples/tcp_server.zr @@ -2,7 +2,7 @@ func main[] : i64 let s: i64 = must(net.listen?(net.pack_addr(127, 0, 0, 1), 8000)) io.println("Listening on port 8000...") - let resp: str = mem.alloc(60000) + let resp: ptr = mem.alloc(60000) while true let conn: i64 = net.accept(s) if conn < 0 diff --git a/examples/udp_server.zr b/examples/udp_server.zr index 5f2f345..e784f5d 100644 --- a/examples/udp_server.zr +++ b/examples/udp_server.zr @@ -1,5 +1,5 @@ func main[] : i64 - let s: i64 = must(net.udp_listen?(net.pack_addr(127, 0, 0, 1), 8000)) + let s: net.UDPSocket = must(net.udp_listen?(net.pack_addr(127, 0, 0, 1), 8000)) io.println("Listening on port 8000...") while true diff --git a/src/std/std.zr b/src/std/std.zr index a1d7ce6..165eafe 100644 --- a/src/std/std.zr +++ b/src/std/std.zr @@ -289,18 +289,18 @@ func io._printf_impl[s: ptr, args: ptr] : void io.print_char(s[0]) s = s + 1 -func io.print_sized[x: str, size: i64] : void +func io.print_sized[x: ptr, size: i64] : void _builtin_syscall(SYS_write, 1, x, size) func io.print[x: str] : void - io.print_sized(x, str.len(x)) + io.print_sized(x as ptr, str.len(x)) func io.println[x: str] : void io.print(x) io.print("\n") func io.print_char[x: u8] : void - io.print_sized(^x as str, 1) + io.print_sized(^x, 1) func io.print_bool[x: bool] : void if x @@ -860,7 +860,7 @@ func alg.count[arr: Array, item: any] : i64 count = count + 1 return count -func alg.map[arr: Array, fn: ptr] : Array +func alg.map[arr: Array, fn: fnptr] : Array let out: Array = array.new_with_size_zeroed(arr->size) for i in 0..arr->size array.set(out, i, fn(array.nth(arr, i))) diff --git a/src/typechecker.rs b/src/typechecker.rs index d044521..d772ffe 100644 --- a/src/typechecker.rs +++ b/src/typechecker.rs @@ -35,7 +35,6 @@ macro_rules! expect_types { }; } -// TODO: currently they are all just 64 bit values static BUILTIN_TYPES: [&str; 8] = ["void", "u8", "i64", "str", "bool", "ptr", "fnptr", "any"]; pub struct Env { @@ -245,7 +244,7 @@ impl TypeChecker { Ok("i64".into()) } TokenType::Bang => { - expect_types!(right_type, ["bool", "i64", "ptr"], op.loc); + expect_types!(right_type, ["bool", "i64", "ptr", "u8"], op.loc); Ok("bool".into()) } _ => unreachable!(), @@ -371,10 +370,18 @@ impl TypeChecker { expect_types!(self.typecheck_expr(env, index)?, ["i64", "u8"], bracket.loc); Ok("u8".into()) } - Expr::AddrOf { op, expr } => { - self.typecheck_expr(env, expr)?; - Ok("ptr".into()) - } + Expr::AddrOf { op, expr } => match *expr.clone() { + Expr::Variable(name) => { + if self.analyzer.borrow().functions.contains_key(&name.lexeme) { + Ok("fnptr".into()) + } else { + Ok("ptr".into()) + } + } + _ => { + error!(&op.loc, "can only take address of variables and functions") + } + }, Expr::New(struct_name) => Ok(struct_name.lexeme.clone()), Expr::MemberAccess { left, field } => { let left_type = self.typecheck_expr(env, left)?; From 41549013396fd8579537eccf2b2b1ff00d97020f Mon Sep 17 00:00:00 2001 From: Toni Date: Tue, 17 Mar 2026 17:11:14 +0100 Subject: [PATCH 07/12] typecheck return types --- examples/udp_server.zr | 2 +- src/analyzer.rs | 6 +++--- src/codegen_x86_64.rs | 2 +- src/parser.rs | 11 +++++++++-- src/std/net.zr | 12 +++++------ src/std/std.zr | 44 ++++++++++++++++++++--------------------- src/typechecker.rs | 45 ++++++++++++++++++++++++++++-------------- 7 files changed, 72 insertions(+), 50 deletions(-) diff --git a/examples/udp_server.zr b/examples/udp_server.zr index e784f5d..1ec4c65 100644 --- a/examples/udp_server.zr +++ b/examples/udp_server.zr @@ -1,5 +1,5 @@ func main[] : i64 - let s: net.UDPSocket = must(net.udp_listen?(net.pack_addr(127, 0, 0, 1), 8000)) + let s: net.UDPSocket = must(net.create_udp_server?(net.pack_addr(127, 0, 0, 1), 8000)) io.println("Listening on port 8000...") while true diff --git a/src/analyzer.rs b/src/analyzer.rs index 6e4c84c..113b0d8 100644 --- a/src/analyzer.rs +++ b/src/analyzer.rs @@ -51,9 +51,9 @@ impl Analyzer { ("_builtin_read64".into(), FnType::new("i64", vec!["ptr"])), ( "_builtin_set64".into(), - FnType::new("void", vec!["ptr", "any"]), + FnType::new("void", vec!["ptr", "i64"]), ), - ("_builtin_syscall".into(), FnType::new_variadic("any")), + ("_builtin_syscall".into(), FnType::new_variadic("i64")), ("io.printf".into(), FnType::new_variadic("void")), ("_builtin_environ".into(), FnType::new("ptr", vec![])), ]), @@ -145,7 +145,7 @@ impl Analyzer { self.analyze_stmt(body)?; } - Stmt::Return(expr) => { + Stmt::Return { expr, keyword: _ } => { self.analyze_expr(expr)?; } Stmt::For { diff --git a/src/codegen_x86_64.rs b/src/codegen_x86_64.rs index 05650a5..3f0a3be 100644 --- a/src/codegen_x86_64.rs +++ b/src/codegen_x86_64.rs @@ -339,7 +339,7 @@ _builtin_environ: emit!(&mut self.output, " ret"); } } - Stmt::Return(expr) => { + Stmt::Return { expr, keyword: _ } => { self.compile_expr(env, expr)?; emit!(&mut self.output, " mov rsp, rbp"); emit!(&mut self.output, " pop rbp"); diff --git a/src/parser.rs b/src/parser.rs index 857fffb..a49f53d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -41,7 +41,10 @@ pub enum Stmt { body: Box, exported: bool, }, - Return(Expr), + Return { + expr: Expr, + keyword: Token, + }, Break, Continue, Extern(Token), @@ -262,7 +265,11 @@ impl Parser { } else if self.match_token(&[TokenType::KeywordFor]) { self.for_statement() } else if self.match_token(&[TokenType::KeywordReturn]) { - Ok(Stmt::Return(self.expression()?)) + let keyword = self.previous().clone(); + Ok(Stmt::Return { + expr: self.expression()?, + keyword, + }) } else if self.match_token(&[TokenType::KeywordBreak]) { Ok(Stmt::Break) } else if self.match_token(&[TokenType::KeywordContinue]) { diff --git a/src/std/net.zr b/src/std/net.zr index 1a6bbca..ac3e7f1 100644 --- a/src/std/net.zr +++ b/src/std/net.zr @@ -82,7 +82,7 @@ func net.close[s: i64] : void _builtin_syscall(SYS_close, s) func net.pack_addr[a: u8, b: u8, c: u8, d: u8] : i64 - return (a << 24) | (b << 16) | (c << 8) | d + return (a as i64 << 24) | (b << 16) | (c << 8) | d struct net.UDPSocket fd: i64 @@ -101,7 +101,7 @@ func net.create_udp_client?[packed_host: i64, port: i64] : net.UDPSocket if s->fd < 0 mem.free(s) err.set(ERR_SYSCALL_FAILED, "net.create_udp_client?: failed to create a socket") - return 0 + return 0 as net.UDPSocket s->addr = mem.alloc(16) mem.zero(s->addr, 16) @@ -115,16 +115,16 @@ func net.create_udp_client?[packed_host: i64, port: i64] : net.UDPSocket s->addr[7] = packed_host & 255 return s -func net.udp_listen?[packed_host: i64, port: i64] : net.UDPSocket +func net.create_udp_server?[packed_host: i64, port: i64] : net.UDPSocket err.clear() let s: net.UDPSocket = net.create_udp_client?(packed_host, port) if err.check() - return 0 + return 0 as net.UDPSocket if _builtin_syscall(SYS_bind, s->fd, s->addr, 16) < 0 net.UDPSocket.close_and_free(s) - err.set(ERR_SYSCALL_FAILED, "net.udp_listen?: failed to bind") - return 0 + err.set(ERR_SYSCALL_FAILED, "net.create_udp_server?: failed to bind") + return 0 as net.UDPSocket return s diff --git a/src/std/std.zr b/src/std/std.zr index 165eafe..b9afe49 100644 --- a/src/std/std.zr +++ b/src/std/std.zr @@ -10,7 +10,7 @@ func err.code[] : i64 return mem.read64(_builtin_err_code()) func err.msg[] : str - return mem.read64(_builtin_err_msg()) + return mem.read64(_builtin_err_msg()) as str func err.check[] : bool return err.code() != 0 @@ -73,7 +73,7 @@ func mem._request_space?[size: i64] : mem.Block let blk_ptr: ptr = _builtin_syscall(SYS_mmap, 0, alloc_size, 3, 34, -1, 0) as ptr if blk_ptr == -1 err.set(ERR_ALLOC_FAILED, "mem._request_space: mmap failed") - return 0 + return 0 as mem.Block let blk: mem.Block = blk_ptr as mem.Block @@ -173,11 +173,11 @@ func mem.realloc?[x: ptr, new_size: i64] : ptr if new_size < 0 err.set(ERR_ALLOC_FAILED, "mem.realloc? called with negative size") - return 0 + return 0 as ptr if new_size == 0 mem.free(x) - return 0 + return 0 as ptr new_size = mem._align(new_size) @@ -204,7 +204,7 @@ func mem.realloc?[x: ptr, new_size: i64] : ptr let new_ptr: ptr = mem.alloc?(new_size) if err.check() - return 0 + return 0 as ptr mem.copy(x, new_ptr, math.min(blk->size, new_size)) mem.free(x) @@ -232,16 +232,16 @@ func mem.read8[x: ptr] : u8 return x[0] func mem.read16[x: ptr] : i64 - return x[0] | (x[1] << 8) + return x[0] as i64 | (x[1] << 8) func mem.read16be[x: ptr] : i64 - return (x[0] << 8) | x[1] + return (x[0] << 8) as i64 | x[1] func mem.read32[x: ptr] : i64 - return x[0] | (x[1] << 8) | (x[2] << 16) | (x[3] << 24) + return x[0] as i64 | (x[1] << 8) | (x[2] << 16) | (x[3] << 24) func mem.read32be[x: ptr] : i64 - return (x[0] << 24) | (x[1] << 16) | (x[2] << 8) | x[3] + return (x[0] as i64 << 24) | (x[1] << 16) | (x[2] << 8) | x[3] func mem.read64[x: ptr] : i64 return _builtin_read64(x) @@ -349,7 +349,7 @@ func io.Buffer.alloc?[size: i64] : io.Buffer buffer->data = mem.alloc?(size) if err.check() mem.free(buffer) - return 0 + return 0 as io.Buffer return buffer func io.Buffer.free[buf: io.Buffer] : void @@ -364,7 +364,7 @@ func io.read_text_file?[path: str] : str let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0, 0) if fd < 0 err.set(ERR_OPEN_FAILED, "io.read_text_file?: failed to open file") - return 0 + return 0 as str let size: i64 = _builtin_syscall(SYS_lseek, fd, 0, 2) _builtin_syscall(SYS_lseek, fd, 0, 0) @@ -372,7 +372,7 @@ func io.read_text_file?[path: str] : str let buffer: str = mem.alloc?(size + 1) as str if err.check() _builtin_syscall(SYS_close, fd) - return 0 + return 0 as str let n: i64 = _builtin_syscall(SYS_read, fd, buffer, size) _builtin_syscall(SYS_close, fd) @@ -380,7 +380,7 @@ func io.read_text_file?[path: str] : str if n != size mem.free(buffer) err.set(ERR_READ_FAILED, "io.read_text_file?: failed to read file") - return 0 + return 0 as str buffer[n] = 0 return buffer @@ -390,7 +390,7 @@ func io.read_binary_file?[path: str] : io.Buffer let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0, 0) if fd < 0 err.set(ERR_OPEN_FAILED, "io.read_binary_file?: failed to open file") - return 0 + return 0 as io.Buffer let buf: io.Buffer = new io.Buffer buf->size = _builtin_syscall(SYS_lseek, fd, 0, 2) @@ -400,7 +400,7 @@ func io.read_binary_file?[path: str] : io.Buffer if err.check() io.Buffer.free(buf) _builtin_syscall(SYS_close, fd) - return 0 + return 0 as io.Buffer let n: i64 = _builtin_syscall(SYS_read, fd, buf->data, buf->size) _builtin_syscall(SYS_close, fd) @@ -408,7 +408,7 @@ func io.read_binary_file?[path: str] : io.Buffer if n != buf->size io.Buffer.free(buf) err.set(ERR_READ_FAILED, "io.read_binary_file?: failed to read file") - return 0 + return 0 as io.Buffer return buf @@ -664,7 +664,7 @@ func str.hex_encode[s: str, s_len: i64] : str out[j] = 0 return out -func str._hex_digit_to_int[d: u8] : i64 +func str._hex_digit_to_int[d: u8] : u8 if d >= 'a' && d <= 'f' return d - 'a' + 10 if d >= 'A' && d <= 'F' @@ -902,13 +902,13 @@ func os.urandom?[n: i64]: ptr err.clear() let buffer: ptr = mem.alloc(n) if err.check() - return 0 + return 0 as ptr let fd: i64 = _builtin_syscall(SYS_openat, -100, "/dev/urandom", 0, 0) if fd < 0 mem.free(buffer) err.set(ERR_READ_FAILED, "os.urandom?: failed to open /dev/urandom") - return 0 + return 0 as ptr let bytes_read: i64 = _builtin_syscall(SYS_read, fd, buffer, n) _builtin_syscall(SYS_close, fd) @@ -916,7 +916,7 @@ func os.urandom?[n: i64]: ptr if n != bytes_read mem.free(buffer) err.set(ERR_READ_FAILED, "os.urandom?: failed to read /dev/urandom") - return 0 + return 0 as ptr return buffer @@ -968,7 +968,7 @@ func os.list_directory?[path: str] : Array let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0, 0) if fd < 0 err.set(ERR_OPEN_FAILED, "os.list_directory?: failed to open dir") - return 0 + return 0 as Array let files: Array = [] let buf: ptr = mem.alloc(1024) @@ -983,7 +983,7 @@ func os.list_directory?[path: str] : Array _builtin_syscall(SYS_close, fd) err.set(ERR_SYSCALL_FAILED, "os.list_directory?: getdents64() failed") - return 0 + return 0 as Array else if n == 0 break diff --git a/src/typechecker.rs b/src/typechecker.rs index d772ffe..54beae8 100644 --- a/src/typechecker.rs +++ b/src/typechecker.rs @@ -1,6 +1,3 @@ -#![allow(dead_code)] -#![allow(unused_variables)] - use std::{cell::RefCell, collections::HashMap, rc::Rc}; use crate::{ @@ -72,11 +69,15 @@ impl Env { pub struct TypeChecker { analyzer: Rc>, + current_function_return_type: String, } impl TypeChecker { pub fn new(analyzer: Rc>) -> TypeChecker { - TypeChecker { analyzer } + TypeChecker { + analyzer, + current_function_return_type: String::new(), + } } pub fn typecheck_stmt(&mut self, env: &mut Env, stmt: &Stmt) -> Result<(), ZernError> { @@ -106,11 +107,15 @@ impl TypeChecker { env.define_var(name.lexeme.clone(), actual_type); } - Stmt::Const { name, value } => {} + Stmt::Const { name: _, value: _ } => { + // handled in the analyzer + } Stmt::Block(stmts) => { + env.push_scope(); for stmt in stmts { self.typecheck_stmt(env, stmt)?; } + env.pop_scope(); } Stmt::If { condition, @@ -140,14 +145,12 @@ impl TypeChecker { env.pop_scope(); } Stmt::Function { - name, + name: _, params, return_type, body, - exported, + exported: _, } => { - env.push_scope(); - if !self.is_valid_type_name(&return_type.lexeme) { return error!( &return_type.loc, @@ -155,7 +158,11 @@ impl TypeChecker { ); } - for (i, param) in params.iter().enumerate() { + self.current_function_return_type = return_type.lexeme.clone(); + + env.push_scope(); + + for param in params { if !self.is_valid_type_name(¶m.var_type.lexeme) { return error!( ¶m.var_name.loc, @@ -170,15 +177,22 @@ impl TypeChecker { env.pop_scope(); } - Stmt::Return(expr) => { - // TODO + Stmt::Return { expr, keyword } => { + let expected = if self.current_function_return_type == "void" { + "i64".into() + } else { + self.current_function_return_type.clone() + }; + expect_type!(self.typecheck_expr(env, expr)?, expected, keyword.loc); } Stmt::Break => {} Stmt::Continue => {} Stmt::Extern(_) => { // handled in the analyzer } - Stmt::Struct { name, fields } => {} + Stmt::Struct { name: _, fields: _ } => { + // TODO: validate types + } } Ok(()) } @@ -265,7 +279,7 @@ impl TypeChecker { match left.as_ref() { Expr::Variable(name) => { - let var_type = match env.get_var_type(&name.lexeme) { + let existing_var_type = match env.get_var_type(&name.lexeme) { Some(x) => x, None => { return error!( @@ -274,7 +288,7 @@ impl TypeChecker { ); } }; - expect_type!(*var_type, value_type, name.loc); + expect_type!(value_type, *existing_var_type, name.loc); } Expr::Index { expr, @@ -403,6 +417,7 @@ impl TypeChecker { } Expr::Cast { expr, type_name } => { self.typecheck_expr(env, expr)?; + // TODO: validate target type Ok(type_name.lexeme.clone()) } } From 3efb3820bc1852ad864c27889b680eac1da4149d Mon Sep 17 00:00:00 2001 From: Toni Date: Tue, 17 Mar 2026 17:34:49 +0100 Subject: [PATCH 08/12] fix a bug that caused stuff to not get typechecked --- src/std/net.zr | 8 ++++---- src/std/std.zr | 8 ++++---- src/typechecker.rs | 24 +++++++++++++++--------- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/std/net.zr b/src/std/net.zr index ac3e7f1..61bdfde 100644 --- a/src/std/net.zr +++ b/src/std/net.zr @@ -81,8 +81,8 @@ func net.read[s: i64, buffer: ptr, size: i64] : i64 func net.close[s: i64] : void _builtin_syscall(SYS_close, s) -func net.pack_addr[a: u8, b: u8, c: u8, d: u8] : i64 - return (a as i64 << 24) | (b << 16) | (c << 8) | d +func net.pack_addr[a: i64, b: i64, c: i64, d: i64] : i64 + return (a << 24) | (b << 16) | (c << 8) | d struct net.UDPSocket fd: i64 @@ -201,7 +201,7 @@ func net.resolve?[domain: str] : i64 let query: io.Buffer = net.build_dns_query(domain, DNS_TYPE_A) // TODO: this probably shouldnt be hardcoded - let s: net.UDPSocket = net.create_udp_client?(net.pack_addr(8 as u8, 8 as u8, 8 as u8, 8 as u8), 53) + let s: net.UDPSocket = net.create_udp_client?(net.pack_addr(8, 8, 8, 8), 53) if err.check() return -1 @@ -221,6 +221,6 @@ func net.resolve?[domain: str] : i64 pos = pos + 12 // skip name(2), type(2), class(2), ttl(4), rdlength(2) - let out: i64 = net.pack_addr(pkt->data[pos], pkt->data[pos+1], pkt->data[pos+2], pkt->data[pos+3]) + let out: i64 = net.pack_addr(pkt->data[pos] as i64, pkt->data[pos+1] as i64, pkt->data[pos+2] as i64, pkt->data[pos+3] as i64) net.UDPPacket.free(pkt) return out diff --git a/src/std/std.zr b/src/std/std.zr index b9afe49..36faf2f 100644 --- a/src/std/std.zr +++ b/src/std/std.zr @@ -103,7 +103,7 @@ func mem.alloc?[size: i64] : any if cur->free && cur->size >= size mem._split_block(cur, size) cur->free = false - return cur + MEM_BLOCK_SIZE + return cur as ptr + MEM_BLOCK_SIZE cur = cur->next let blk: mem.Block = mem._request_space?(size) @@ -115,7 +115,7 @@ func mem.alloc?[size: i64] : any mem._split_block(blk, size) - return blk + MEM_BLOCK_SIZE + return blk as ptr + MEM_BLOCK_SIZE func mem.alloc[size: i64] : any return must(mem.alloc?(size)) @@ -335,7 +335,7 @@ func io.read_line[]: str if n < 0 n = 0 buffer[n] = 0 - buffer = must(mem.realloc?(buffer, n + 1)) + buffer = must(mem.realloc?(buffer as ptr, n + 1)) return buffer struct io.Buffer @@ -1001,7 +1001,7 @@ func os.list_directory?[path: str] : Array if name[2] == 0 skip = true if !skip - array.push(files, str.make_copy(name)) + array.push(files, str.make_copy(name as str)) pos = pos + len mem.free(buf) diff --git a/src/typechecker.rs b/src/typechecker.rs index 54beae8..2527371 100644 --- a/src/typechecker.rs +++ b/src/typechecker.rs @@ -7,14 +7,15 @@ use crate::{ }; macro_rules! expect_type { - ($expr_type:expr, $expected:expr, $loc:expr) => { - if $expected != "any" && $expr_type != "any" && $expr_type != $expected { + ($expr_type:expr, $expected:expr, $loc:expr) => {{ + let actual = $expr_type; + if $expected != "any" && actual != "any" && actual != $expected { return error!( $loc, - format!("expected type '{}', got '{}'", $expected, $expr_type) + format!("expected type '{}', got '{}'", $expected, actual) ); } - }; + }}; } macro_rules! expect_types { @@ -98,7 +99,7 @@ impl TypeChecker { "unrecognized type: ".to_owned() + &var_type.lexeme ); } - expect_type!(actual_type, var_type.lexeme, var_type.loc); + expect_type!(actual_type.clone(), var_type.lexeme, var_type.loc); if actual_type == "any" { actual_type = var_type.lexeme.clone(); @@ -288,7 +289,7 @@ impl TypeChecker { ); } }; - expect_type!(value_type, *existing_var_type, name.loc); + expect_type!(value_type.clone(), *existing_var_type, name.loc); } Expr::Index { expr, @@ -297,7 +298,7 @@ impl TypeChecker { } => { expect_types!(self.typecheck_expr(env, expr)?, ["ptr", "str"], bracket.loc); expect_types!(self.typecheck_expr(env, index)?, ["i64", "u8"], bracket.loc); - expect_types!(value_type, ["u8", "i64"], bracket.loc); + expect_types!(value_type.clone(), ["u8", "i64"], bracket.loc); } Expr::MemberAccess { left, field } => { let left_type = self.typecheck_expr(env, left)?; @@ -323,7 +324,7 @@ impl TypeChecker { } }; - expect_type!(value_type, f.field_type, field.loc); + expect_type!(value_type.clone(), f.field_type, field.loc); } _ => return error!(&op.loc, "invalid assignment target"), } @@ -343,11 +344,16 @@ impl TypeChecker { { let fn_type = &self.analyzer.borrow().functions[&callee_name.lexeme].clone(); + // its a function (defined/builtin/extern) if let Some(params) = fn_type.params.clone() { - // its a function (defined/builtin/extern) for (i, arg) in args.iter().enumerate() { expect_type!(self.typecheck_expr(env, arg)?, params[i], paren.loc); } + } else { + // its a variadic function, cant check arg types + for arg in args { + self.typecheck_expr(env, arg)?; + } } Ok(fn_type.return_type.clone()) } else { From 2137d49abdb00e9cb0f2b0d35631513ad73cde91 Mon Sep 17 00:00:00 2001 From: Toni Date: Wed, 18 Mar 2026 11:34:25 +0100 Subject: [PATCH 09/12] typecheck if and while conditions, disallow ptr multiplication --- src/analyzer.rs | 7 +++++- src/codegen_x86_64.rs | 7 +++++- src/parser.rs | 6 +++++ src/std/std.zr | 24 +++++++++--------- src/typechecker.rs | 57 ++++++++++++++++++++++++++++++++----------- 5 files changed, 73 insertions(+), 28 deletions(-) diff --git a/src/analyzer.rs b/src/analyzer.rs index 113b0d8..e218125 100644 --- a/src/analyzer.rs +++ b/src/analyzer.rs @@ -120,6 +120,7 @@ impl Analyzer { } } Stmt::If { + keyword: _, condition, then_branch, else_branch, @@ -128,7 +129,11 @@ impl Analyzer { self.analyze_stmt(then_branch)?; self.analyze_stmt(else_branch)?; } - Stmt::While { condition, body } => { + Stmt::While { + keyword: _, + condition, + body, + } => { self.analyze_expr(condition)?; self.analyze_stmt(body)?; } diff --git a/src/codegen_x86_64.rs b/src/codegen_x86_64.rs index 3f0a3be..46fbc78 100644 --- a/src/codegen_x86_64.rs +++ b/src/codegen_x86_64.rs @@ -260,6 +260,7 @@ _builtin_environ: env.pop_scope(); } Stmt::If { + keyword: _, condition, then_branch, else_branch, @@ -276,7 +277,11 @@ _builtin_environ: self.compile_stmt(env, else_branch)?; emit!(&mut self.output, "{}:", end_label); } - Stmt::While { condition, body } => { + Stmt::While { + keyword: _, + condition, + body, + } => { let old_loop_begin_label = env.loop_begin_label.clone(); let old_loop_end_label = env.loop_end_label.clone(); let old_loop_continue_label = env.loop_continue_label.clone(); diff --git a/src/parser.rs b/src/parser.rs index a49f53d..142f52c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -20,11 +20,13 @@ pub enum Stmt { }, Block(Vec), If { + keyword: Token, condition: Expr, then_branch: Box, else_branch: Box, }, While { + keyword: Token, condition: Expr, body: Box, }, @@ -280,6 +282,7 @@ impl Parser { } fn if_statement(&mut self) -> Result { + let keyword = self.previous().clone(); let condition = self.expression()?; let then_branch = self.block()?; let else_branch = if self.match_token(&[TokenType::KeywordElse]) { @@ -292,6 +295,7 @@ impl Parser { Box::new(Stmt::Block(vec![])) }; Ok(Stmt::If { + keyword, condition, then_branch: Box::new(then_branch), else_branch, @@ -299,9 +303,11 @@ impl Parser { } fn while_statement(&mut self) -> Result { + let keyword = self.previous().clone(); let condition = self.expression()?; let body = self.block()?; Ok(Stmt::While { + keyword, condition, body: Box::new(body), }) diff --git a/src/std/std.zr b/src/std/std.zr index 36faf2f..45651f9 100644 --- a/src/std/std.zr +++ b/src/std/std.zr @@ -53,7 +53,7 @@ func mem._split_block[blk: mem.Block, needed: i64] : void new_blk->next = blk->next new_blk->prev = blk - if blk->next + if blk->next as ptr let next: mem.Block = blk->next next->prev = new_blk else @@ -83,7 +83,7 @@ func mem._request_space?[size: i64] : mem.Block blk->prev = 0 as mem.Block let tail: mem.Block = mem.read64(_builtin_heap_tail()) as mem.Block - if tail + if tail as ptr tail->next = blk blk->prev = tail @@ -99,7 +99,7 @@ func mem.alloc?[size: i64] : any size = mem._align(size) let cur: mem.Block = mem.read64(_builtin_heap_head()) as mem.Block - while cur + while cur as ptr if cur->free && cur->size >= size mem._split_block(cur, size) cur->free = false @@ -133,7 +133,7 @@ func mem.free[x: any] : void if expected_next as ptr == next as ptr blk->size = blk->size + MEM_BLOCK_SIZE + next->size blk->next = next->next - if next->next + if next->next as ptr let b: mem.Block = next->next b->prev = blk if mem.read64(_builtin_heap_tail()) == next as ptr @@ -145,7 +145,7 @@ func mem.free[x: any] : void if expected_blk as ptr == blk as ptr prev->size = prev->size + MEM_BLOCK_SIZE + blk->size prev->next = blk->next - if blk->next + if blk->next as ptr let b: mem.Block = blk->next b->prev = prev if mem.read64(_builtin_heap_tail()) == blk as ptr @@ -154,12 +154,12 @@ func mem.free[x: any] : void let block_total: i64 = blk->size + MEM_BLOCK_SIZE if (blk as i64 & 4095) == 0 && (block_total & 4095) == 0 - if blk->prev + if blk->prev as ptr let b: mem.Block = blk->prev b->next = blk->next else mem.write64(_builtin_heap_head(), blk->next) - if blk->next + if blk->next as ptr let b: mem.Block = blk->next b->prev = blk->prev else @@ -194,7 +194,7 @@ func mem.realloc?[x: ptr, new_size: i64] : ptr if combined >= new_size blk->size = combined blk->next = next->next - if next->next + if next->next as ptr let b: mem.Block = next->next b->prev = blk if mem.read64(_builtin_heap_tail()) == next as ptr @@ -771,7 +771,7 @@ struct Array func array.new[] : Array return new Array -func array.new_with_size_zeroed[size: i64] : Array +func array.new_preallocated_and_zeroed[size: i64] : Array let xs: Array = new Array if size > 0 xs->data = must(mem.realloc?(xs->data, size * 8)) as ptr @@ -817,13 +817,13 @@ func array.slice[xs: Array, start: i64, length: i64] : Array if start < 0 || length < 0 || start + length > xs->size panic("array.slice out of bounds") - let new_array: Array = array.new_with_size_zeroed(length) + let new_array: Array = array.new_preallocated_and_zeroed(length) for i in 0..length array.set(new_array, i, array.nth(xs, start + i)) return new_array func array.concat[a: Array, b: Array] : Array - let new_array: Array = array.new_with_size_zeroed(a->size + b->size) + let new_array: Array = array.new_preallocated_and_zeroed(a->size + b->size) for i in 0..a->size array.set(new_array, i, array.nth(a, i)) for i in 0..b->size @@ -861,7 +861,7 @@ func alg.count[arr: Array, item: any] : i64 return count func alg.map[arr: Array, fn: fnptr] : Array - let out: Array = array.new_with_size_zeroed(arr->size) + let out: Array = array.new_preallocated_and_zeroed(arr->size) for i in 0..arr->size array.set(out, i, fn(array.nth(arr, i))) return out diff --git a/src/typechecker.rs b/src/typechecker.rs index 2527371..a857c9f 100644 --- a/src/typechecker.rs +++ b/src/typechecker.rs @@ -51,6 +51,7 @@ impl Env { } pub fn pop_scope(&mut self) { + assert!(!self.scopes.is_empty()); self.scopes.pop(); } @@ -119,15 +120,29 @@ impl TypeChecker { env.pop_scope(); } Stmt::If { + keyword, condition, then_branch, else_branch, } => { - self.typecheck_expr(env, condition)?; + expect_types!( + self.typecheck_expr(env, condition)?, + ["i64", "u8", "ptr", "bool"], + keyword.loc + ); self.typecheck_stmt(env, then_branch)?; self.typecheck_stmt(env, else_branch)?; } - Stmt::While { condition, body } => { + Stmt::While { + keyword, + condition, + body, + } => { + expect_types!( + self.typecheck_expr(env, condition)?, + ["i64", "u8", "ptr", "bool"], + keyword.loc + ); self.typecheck_expr(env, condition)?; self.typecheck_stmt(env, body)?; } @@ -202,30 +217,43 @@ impl TypeChecker { match expr { Expr::Binary { left, op, right } => { let left_type = self.typecheck_expr(env, left)?; - expect_types!(left_type, ["i64", "ptr", "u8"], op.loc); - expect_types!( - self.typecheck_expr(env, right)?, - ["i64", "ptr", "u8"], - op.loc - ); match op.token_type { - TokenType::Plus - | TokenType::Minus - | TokenType::Star + TokenType::Plus | TokenType::Minus => { + expect_types!(left_type, ["i64", "ptr", "u8"], op.loc); + expect_types!( + self.typecheck_expr(env, right)?, + ["i64", "ptr", "u8"], + op.loc + ); + Ok(left_type) + } + TokenType::Star | TokenType::Slash | TokenType::Mod | TokenType::Xor | TokenType::BitAnd | TokenType::BitOr | TokenType::ShiftLeft - | TokenType::ShiftRight => Ok(left_type), + | TokenType::ShiftRight => { + expect_types!(left_type, ["i64", "u8"], op.loc); + expect_types!(self.typecheck_expr(env, right)?, ["i64", "u8"], op.loc); + Ok(left_type) + } TokenType::DoubleEqual | TokenType::NotEqual | TokenType::Greater | TokenType::GreaterEqual | TokenType::Less - | TokenType::LessEqual => Ok("bool".into()), + | TokenType::LessEqual => { + expect_types!(left_type, ["i64", "ptr", "u8"], op.loc); + expect_types!( + self.typecheck_expr(env, right)?, + ["i64", "ptr", "u8"], + op.loc + ); + Ok("bool".into()) + } _ => unreachable!(), } } @@ -347,6 +375,7 @@ impl TypeChecker { // its a function (defined/builtin/extern) if let Some(params) = fn_type.params.clone() { for (i, arg) in args.iter().enumerate() { + // arity is checked in the analyzer expect_type!(self.typecheck_expr(env, arg)?, params[i], paren.loc); } } else { @@ -390,7 +419,7 @@ impl TypeChecker { expect_types!(self.typecheck_expr(env, index)?, ["i64", "u8"], bracket.loc); Ok("u8".into()) } - Expr::AddrOf { op, expr } => match *expr.clone() { + Expr::AddrOf { op, expr } => match expr.as_ref() { Expr::Variable(name) => { if self.analyzer.borrow().functions.contains_key(&name.lexeme) { Ok("fnptr".into()) From e774f06e6585de4edb094de02097224a3a888e61 Mon Sep 17 00:00:00 2001 From: Toni Date: Wed, 18 Mar 2026 11:47:29 +0100 Subject: [PATCH 10/12] validate types in New/Struct/Cast --- src/typechecker.rs | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/src/typechecker.rs b/src/typechecker.rs index a857c9f..e1b4002 100644 --- a/src/typechecker.rs +++ b/src/typechecker.rs @@ -143,7 +143,6 @@ impl TypeChecker { ["i64", "u8", "ptr", "bool"], keyword.loc ); - self.typecheck_expr(env, condition)?; self.typecheck_stmt(env, body)?; } Stmt::For { @@ -206,8 +205,15 @@ impl TypeChecker { Stmt::Extern(_) => { // handled in the analyzer } - Stmt::Struct { name: _, fields: _ } => { - // TODO: validate types + Stmt::Struct { name: _, fields } => { + for field in fields { + if !self.is_valid_type_name(&field.var_type.lexeme) { + return error!( + &field.var_type.loc, + format!("unknown type: {}", &field.var_type.lexeme) + ); + } + } } } Ok(()) @@ -431,7 +437,20 @@ impl TypeChecker { error!(&op.loc, "can only take address of variables and functions") } }, - Expr::New(struct_name) => Ok(struct_name.lexeme.clone()), + Expr::New(struct_name) => { + if !self + .analyzer + .borrow() + .structs + .contains_key(&struct_name.lexeme) + { + return error!( + &struct_name.loc, + format!("unknown struct name: {}", &struct_name.lexeme) + ); + } + Ok(struct_name.lexeme.clone()) + } Expr::MemberAccess { left, field } => { let left_type = self.typecheck_expr(env, left)?; @@ -452,7 +471,12 @@ impl TypeChecker { } Expr::Cast { expr, type_name } => { self.typecheck_expr(env, expr)?; - // TODO: validate target type + if !self.is_valid_type_name(&type_name.lexeme) { + return error!( + &type_name.loc, + format!("unknown type: {}", &type_name.lexeme) + ); + } Ok(type_name.lexeme.clone()) } } From 0dbe7560b60c7e4b2190210730c587e02461d057 Mon Sep 17 00:00:00 2001 From: Toni Date: Wed, 18 Mar 2026 18:53:13 +0100 Subject: [PATCH 11/12] refactor the nasty RefCell by parsing everything first --- src/codegen_x86_64.rs | 28 +++++------ src/main.rs | 110 ++++++++++++++++-------------------------- src/typechecker.rs | 39 +++++---------- 3 files changed, 66 insertions(+), 111 deletions(-) diff --git a/src/codegen_x86_64.rs b/src/codegen_x86_64.rs index 46fbc78..9fecc46 100644 --- a/src/codegen_x86_64.rs +++ b/src/codegen_x86_64.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, collections::HashMap, fmt::Write, rc::Rc}; +use std::{collections::HashMap, fmt::Write}; use crate::{ analyzer::Analyzer, @@ -69,16 +69,16 @@ macro_rules! emit { static REGISTERS: [&str; 6] = ["rdi", "rsi", "rdx", "rcx", "r8", "r9"]; -pub struct CodegenX86_64 { +pub struct CodegenX86_64<'a> { output: String, data_section: String, label_counter: usize, data_counter: usize, - pub analyzer: Rc>, + pub analyzer: &'a Analyzer, } -impl CodegenX86_64 { - pub fn new(analyzer: Rc>) -> CodegenX86_64 { +impl<'a> CodegenX86_64<'a> { + pub fn new(analyzer: &'a Analyzer) -> CodegenX86_64<'a> { CodegenX86_64 { output: String::new(), data_section: String::new(), @@ -555,11 +555,11 @@ _builtin_environ: } } Expr::Variable(name) => { - if self.analyzer.borrow().constants.contains_key(&name.lexeme) { + if self.analyzer.constants.contains_key(&name.lexeme) { emit!( &mut self.output, " mov rax, {}", - self.analyzer.borrow().constants[&name.lexeme] + self.analyzer.constants[&name.lexeme] ); } else { let var = match env.get_var(&name.lexeme) { @@ -653,12 +653,7 @@ _builtin_environ: } if let Expr::Variable(callee_name) = &**callee { - if self - .analyzer - .borrow() - .functions - .contains_key(&callee_name.lexeme) - { + if self.analyzer.functions.contains_key(&callee_name.lexeme) { // its a function (defined/builtin/extern) emit!(&mut self.output, " call {}", callee_name.lexeme); } else { @@ -705,7 +700,7 @@ _builtin_environ: } Expr::AddrOf { op, expr } => match *expr.clone() { Expr::Variable(name) => { - if self.analyzer.borrow().functions.contains_key(&name.lexeme) { + if self.analyzer.functions.contains_key(&name.lexeme) { emit!(&mut self.output, " mov rax, {}", name.lexeme); } else { let var = match env.get_var(&name.lexeme) { @@ -729,7 +724,7 @@ _builtin_environ: } }, Expr::New(struct_name) => { - let struct_fields = &self.analyzer.borrow().structs[&struct_name.lexeme]; + let struct_fields = &self.analyzer.structs[&struct_name.lexeme]; // TODO: panic on mem.alloc error let memory_size = struct_fields.len() * 8; @@ -774,8 +769,7 @@ _builtin_environ: } }; - let analyzer = self.analyzer.borrow(); - let fields = match analyzer.structs.get(&struct_name) { + let fields = match self.analyzer.structs.get(&struct_name) { Some(f) => f, None => { return error!(&field.loc, format!("unknown struct type: {}", struct_name)); diff --git a/src/main.rs b/src/main.rs index df66e36..da7fdd3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,74 +5,24 @@ mod tokenizer; mod typechecker; use std::{ - cell::RefCell, fs, path::Path, process::{self, Command}, - rc::Rc, }; use tokenizer::ZernError; use clap::Parser; -macro_rules! compile_std { - ($codegen:expr, $typechecker:expr, $analyzer:expr, $( $name:literal ),* $(,)? ) => { - $( - compile_file_to( - $codegen, - $typechecker, - $analyzer, - $name, - include_str!(concat!("std/", $name)).into() - )?; - )* +macro_rules! parse_std_file { + ($statements:expr, $filename:expr) => { + let source: String = include_str!($filename).into(); + let tokenizer = tokenizer::Tokenizer::new($filename.to_owned(), source); + let parser = parser::Parser::new(tokenizer.tokenize()?); + $statements.extend(parser.parse()?); }; } -fn compile_file_to( - codegen: &mut codegen_x86_64::CodegenX86_64, - typechecker: &mut typechecker::TypeChecker, - analyzer: Rc>, - filename: &str, - source: String, -) -> Result<(), ZernError> { - let tokenizer = tokenizer::Tokenizer::new(filename.to_owned(), source); - let tokens = tokenizer.tokenize()?; - - let parser = parser::Parser::new(tokens); - let statements = parser.parse()?; - - for stmt in &statements { - analyzer.borrow_mut().register_function(stmt)?; - } - - for stmt in &statements { - analyzer.borrow_mut().analyze_stmt(stmt)?; - } - - for stmt in &statements { - typechecker.typecheck_stmt(&mut typechecker::Env::new(), stmt)?; - } - - for stmt in statements { - // top level statements are all function/const/extern declarations so a new env for each - codegen.compile_stmt(&mut codegen_x86_64::Env::new(), &stmt)?; - } - Ok(()) -} - -fn run_command(cmd: String) { - if !Command::new("sh") - .args(["-c", &cmd]) - .status() - .unwrap() - .success() - { - process::exit(1); - } -} - fn compile_file(args: Args) -> Result<(), ZernError> { let source = match fs::read_to_string(&args.path) { Ok(x) => x, @@ -84,21 +34,34 @@ fn compile_file(args: Args) -> Result<(), ZernError> { let filename = Path::new(&args.path).file_name().unwrap().to_str().unwrap(); - let analyzer = Rc::new(RefCell::new(analyzer::Analyzer::new())); + let mut statements = Vec::new(); - let mut typechecker = typechecker::TypeChecker::new(analyzer.clone()); + parse_std_file!(statements, "std/syscalls.zr"); + parse_std_file!(statements, "std/std.zr"); + parse_std_file!(statements, "std/net.zr"); - let mut codegen = codegen_x86_64::CodegenX86_64::new(analyzer.clone()); + let tokenizer = tokenizer::Tokenizer::new(filename.to_owned(), source); + let parser = parser::Parser::new(tokenizer.tokenize()?); + statements.extend(parser.parse()?); + + let mut analyzer = analyzer::Analyzer::new(); + for stmt in &statements { + analyzer.register_function(stmt)?; + } + for stmt in &statements { + analyzer.analyze_stmt(stmt)?; + } + + let mut typechecker = typechecker::TypeChecker::new(&analyzer); + for stmt in &statements { + typechecker.typecheck_stmt(&mut typechecker::Env::new(), stmt)?; + } + + let mut codegen = codegen_x86_64::CodegenX86_64::new(&analyzer); codegen.emit_prologue(args.use_gcc)?; - compile_std!( - &mut codegen, - &mut typechecker, - analyzer.clone(), - "syscalls.zr", - "std.zr", - "net.zr" - ); - compile_file_to(&mut codegen, &mut typechecker, analyzer, filename, source)?; + for stmt in statements { + codegen.compile_stmt(&mut codegen_x86_64::Env::new(), &stmt)?; + } if !args.output_asm { let out = args.out.unwrap_or_else(|| "out".into()); @@ -138,6 +101,17 @@ fn compile_file(args: Args) -> Result<(), ZernError> { Ok(()) } +fn run_command(cmd: String) { + if !Command::new("sh") + .args(["-c", &cmd]) + .status() + .unwrap() + .success() + { + process::exit(1); + } +} + #[derive(Parser, Debug)] #[command(version, about, long_about = None)] struct Args { diff --git a/src/typechecker.rs b/src/typechecker.rs index e1b4002..3ec74a4 100644 --- a/src/typechecker.rs +++ b/src/typechecker.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, collections::HashMap, rc::Rc}; +use std::collections::HashMap; use crate::{ analyzer::{Analyzer, Type}, @@ -69,13 +69,13 @@ impl Env { } } -pub struct TypeChecker { - analyzer: Rc>, +pub struct TypeChecker<'a> { + analyzer: &'a Analyzer, current_function_return_type: String, } -impl TypeChecker { - pub fn new(analyzer: Rc>) -> TypeChecker { +impl<'a> TypeChecker<'a> { + pub fn new(analyzer: &'a Analyzer) -> TypeChecker<'a> { TypeChecker { analyzer, current_function_return_type: String::new(), @@ -300,7 +300,7 @@ impl TypeChecker { } } Expr::Variable(name) => { - if self.analyzer.borrow().constants.contains_key(&name.lexeme) { + if self.analyzer.constants.contains_key(&name.lexeme) { Ok("i64".into()) } else { match env.get_var_type(&name.lexeme) { @@ -337,8 +337,7 @@ impl TypeChecker { Expr::MemberAccess { left, field } => { let left_type = self.typecheck_expr(env, left)?; - let analyzer = self.analyzer.borrow(); - let fields = match analyzer.structs.get(&left_type) { + let fields = match self.analyzer.structs.get(&left_type) { Some(f) => f, None => { return error!( @@ -370,14 +369,8 @@ impl TypeChecker { args, } => { if let Expr::Variable(callee_name) = &**callee { - if self - .analyzer - .borrow() - .functions - .contains_key(&callee_name.lexeme) - { - let fn_type = - &self.analyzer.borrow().functions[&callee_name.lexeme].clone(); + if self.analyzer.functions.contains_key(&callee_name.lexeme) { + let fn_type = &self.analyzer.functions[&callee_name.lexeme].clone(); // its a function (defined/builtin/extern) if let Some(params) = fn_type.params.clone() { for (i, arg) in args.iter().enumerate() { @@ -427,7 +420,7 @@ impl TypeChecker { } Expr::AddrOf { op, expr } => match expr.as_ref() { Expr::Variable(name) => { - if self.analyzer.borrow().functions.contains_key(&name.lexeme) { + if self.analyzer.functions.contains_key(&name.lexeme) { Ok("fnptr".into()) } else { Ok("ptr".into()) @@ -438,12 +431,7 @@ impl TypeChecker { } }, Expr::New(struct_name) => { - if !self - .analyzer - .borrow() - .structs - .contains_key(&struct_name.lexeme) - { + if !self.analyzer.structs.contains_key(&struct_name.lexeme) { return error!( &struct_name.loc, format!("unknown struct name: {}", &struct_name.lexeme) @@ -454,8 +442,7 @@ impl TypeChecker { Expr::MemberAccess { left, field } => { let left_type = self.typecheck_expr(env, left)?; - let analyzer = self.analyzer.borrow(); - let fields = match analyzer.structs.get(&left_type) { + let fields = match self.analyzer.structs.get(&left_type) { Some(f) => f, None => { return error!(&field.loc, format!("unknown struct type: {}", left_type)); @@ -486,7 +473,7 @@ impl TypeChecker { if BUILTIN_TYPES.contains(&name) { return true; } - if self.analyzer.borrow().structs.contains_key(name) { + if self.analyzer.structs.contains_key(name) { return true; } false From 625fc0d8118a35b22003353ab6dc84e37de76257 Mon Sep 17 00:00:00 2001 From: Toni Date: Wed, 18 Mar 2026 19:08:10 +0100 Subject: [PATCH 12/12] typecheck_expr doesnt need to mutate the TypeChecker --- src/codegen_x86_64.rs | 2 +- src/typechecker.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/codegen_x86_64.rs b/src/codegen_x86_64.rs index 9fecc46..dd6ad99 100644 --- a/src/codegen_x86_64.rs +++ b/src/codegen_x86_64.rs @@ -564,7 +564,7 @@ _builtin_environ: } else { let var = match env.get_var(&name.lexeme) { Some(x) => x, - None => unreachable!(), + None => unreachable!("this should be caught in the typechecker"), }; emit!( &mut self.output, diff --git a/src/typechecker.rs b/src/typechecker.rs index 3ec74a4..07fb7a7 100644 --- a/src/typechecker.rs +++ b/src/typechecker.rs @@ -219,7 +219,7 @@ impl<'a> TypeChecker<'a> { Ok(()) } - pub fn typecheck_expr(&mut self, env: &mut Env, expr: &Expr) -> Result { + pub fn typecheck_expr(&self, env: &mut Env, expr: &Expr) -> Result { match expr { Expr::Binary { left, op, right } => { let left_type = self.typecheck_expr(env, left)?; @@ -370,7 +370,7 @@ impl<'a> TypeChecker<'a> { } => { if let Expr::Variable(callee_name) = &**callee { if self.analyzer.functions.contains_key(&callee_name.lexeme) { - let fn_type = &self.analyzer.functions[&callee_name.lexeme].clone(); + let fn_type = &self.analyzer.functions[&callee_name.lexeme]; // its a function (defined/builtin/extern) if let Some(params) = fn_type.params.clone() { for (i, arg) in args.iter().enumerate() {