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..1ec4c65 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.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 2abb0e7..e218125 100644 --- a/src/analyzer.rs +++ b/src/analyzer.rs @@ -5,25 +5,57 @@ use crate::{ tokenizer::{ZernError, error}, }; +pub type Type = String; + +pub struct StructField { + pub offset: usize, + 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>, + 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(), 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(), + FnType::new("void", vec!["ptr", "i64"]), + ), + ("_builtin_syscall".into(), FnType::new_variadic("i64")), + ("io.printf".into(), FnType::new_variadic("void")), + ("_builtin_environ".into(), FnType::new("ptr", vec![])), ]), constants: HashMap::new(), structs: HashMap::new(), @@ -34,7 +66,7 @@ impl Analyzer { if let Stmt::Function { name, params, - return_type: _, + return_type, body: _, exported: _, } = stmt @@ -42,8 +74,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(), + FnType { + return_type: return_type.lexeme.clone(), + params: Some(params.iter().map(|x| x.var_type.lexeme.clone()).collect()), + }, + ); } Ok(()) } @@ -83,6 +120,7 @@ impl Analyzer { } } Stmt::If { + keyword: _, condition, then_branch, else_branch, @@ -91,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)?; } @@ -108,7 +150,7 @@ impl Analyzer { self.analyze_stmt(body)?; } - Stmt::Return(expr) => { + Stmt::Return { expr, keyword: _ } => { self.analyze_expr(expr)?; } Stmt::For { @@ -127,14 +169,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(), FnType::new_variadic("any")); } 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 +221,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(fn_type) = self.functions.get(&callee_name.lexeme) { + // if its None, its variadic + if let Some(params) = &fn_type.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 +259,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 +274,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/codegen_x86_64.rs b/src/codegen_x86_64.rs index 94f4998..dd6ad99 100644 --- a/src/codegen_x86_64.rs +++ b/src/codegen_x86_64.rs @@ -69,25 +69,22 @@ 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 { +pub struct CodegenX86_64<'a> { output: String, data_section: String, label_counter: usize, data_counter: usize, - pub analyzer: Analyzer, + pub analyzer: &'a Analyzer, } -impl CodegenX86_64 { - pub fn new() -> 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(), label_counter: 0, data_counter: 1, - analyzer: Analyzer::new(), + analyzer, } } @@ -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); @@ -267,6 +260,7 @@ _builtin_environ: env.pop_scope(); } Stmt::If { + keyword: _, condition, then_branch, else_branch, @@ -283,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(); @@ -306,7 +304,7 @@ _builtin_environ: Stmt::Function { name, params, - return_type, + return_type: _, body, exported, } => { @@ -319,21 +317,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) { @@ -360,7 +344,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"); @@ -578,15 +562,9 @@ _builtin_environ: self.analyzer.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!("this should be caught in the typechecker"), }; emit!( &mut self.output, @@ -600,15 +578,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, @@ -616,7 +588,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"); @@ -710,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)?; @@ -761,20 +741,13 @@ _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(()) } - fn is_valid_type_name(&self, name: &str) -> bool { - if BUILTIN_TYPES.contains(&name) { - return true; - } - if self.analyzer.structs.contains_key(name) { - return true; - } - false - } - fn get_field_offset( &self, env: &mut Env, @@ -803,11 +776,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/main.rs b/src/main.rs index 5220485..da7fdd3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ mod analyzer; mod codegen_x86_64; mod parser; mod tokenizer; +mod typechecker; use std::{ fs, @@ -13,55 +14,15 @@ use tokenizer::ZernError; use clap::Parser; -macro_rules! compile_std { - ($codegen:expr, $( $name:literal ),* $(,)? ) => { - $( - compile_file_to( - $codegen, - $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, - 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 { - codegen.analyzer.register_function(stmt)?; - } - - for stmt in &statements { - codegen.analyzer.analyze_stmt(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, @@ -73,10 +34,34 @@ 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 mut statements = Vec::new(); + + parse_std_file!(statements, "std/syscalls.zr"); + parse_std_file!(statements, "std/std.zr"); + parse_std_file!(statements, "std/net.zr"); + + 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, "syscalls.zr", "std.zr", "net.zr"); - compile_file_to(&mut codegen, 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()); @@ -116,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/parser.rs b/src/parser.rs index da88eaf..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, }, @@ -41,7 +43,10 @@ pub enum Stmt { body: Box, exported: bool, }, - Return(Expr), + Return { + expr: Expr, + keyword: Token, + }, Break, Continue, Extern(Token), @@ -83,6 +88,7 @@ pub enum Expr { ArrayLiteral(Vec), Index { expr: Box, + bracket: Token, index: Box, }, AddrOf { @@ -94,6 +100,10 @@ pub enum Expr { left: Box, field: Token, }, + Cast { + expr: Box, + type_name: Token, + }, } pub struct Parser { @@ -257,7 +267,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]) { @@ -268,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]) { @@ -280,6 +295,7 @@ impl Parser { Box::new(Stmt::Block(vec![])) }; Ok(Stmt::If { + keyword, condition, then_branch: Box::new(then_branch), else_branch, @@ -287,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), }) @@ -438,7 +456,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 +477,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 +536,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/net.zr b/src/std/net.zr index f47df90..61bdfde 100644 --- a/src/std/net.zr +++ b/src/std/net.zr @@ -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 @@ -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 @@ -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 be155bf..45651f9 100644 --- a/src/std/std.zr +++ b/src/std/std.zr @@ -10,13 +10,13 @@ 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 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) @@ -47,13 +47,13 @@ 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 new_blk->prev = blk - if blk->next + if blk->next as ptr let next: mem.Block = blk->next next->prev = new_blk else @@ -70,25 +70,27 @@ 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 + return 0 as mem.Block + + 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()) - if tail + let tail: mem.Block = mem.read64(_builtin_heap_tail()) as mem.Block + if tail as ptr tail->next = blk blk->prev = tail 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") @@ -96,12 +98,12 @@ func mem.alloc?[size: i64] : ptr size = mem._align(size) - let cur: mem.Block = mem.read64(_builtin_heap_head()) - while cur + let cur: mem.Block = mem.read64(_builtin_heap_head()) as mem.Block + while cur as ptr 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) @@ -113,51 +115,51 @@ func mem.alloc?[size: i64] : ptr mem._split_block(blk, size) - return blk + MEM_BLOCK_SIZE + return blk as ptr + MEM_BLOCK_SIZE -func mem.alloc[size: i64] : ptr +func mem.alloc[size: i64] : any 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 + if next->next as ptr let b: mem.Block = next->next b->prev = blk - if mem.read64(_builtin_heap_tail()) == next + if mem.read64(_builtin_heap_tail()) == next as ptr mem.write64(_builtin_heap_tail(), blk) 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 + if blk->next as ptr let b: mem.Block = blk->next b->prev = prev - if mem.read64(_builtin_heap_tail()) == blk + if mem.read64(_builtin_heap_tail()) == blk as ptr mem.write64(_builtin_heap_tail(), prev) blk = prev let block_total: i64 = blk->size + MEM_BLOCK_SIZE - if (blk & 4095) == 0 && (block_total & 4095) == 0 - if blk->prev + if (blk as i64 & 4095) == 0 && (block_total & 4095) == 0 + 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 @@ -171,38 +173,38 @@ 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) - 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 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 + if mem.read64(_builtin_heap_tail()) == next as ptr mem.write64(_builtin_heap_tail(), blk) mem._split_block(blk, new_size) return x 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) @@ -214,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 @@ -230,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) @@ -258,11 +260,11 @@ 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 -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 @@ -274,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('%') @@ -287,11 +289,11 @@ func io._printf_impl[s: str, 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) @@ -322,7 +324,7 @@ 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 @@ -333,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 @@ -347,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 @@ -362,15 +364,15 @@ 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) - 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 + return 0 as str let n: i64 = _builtin_syscall(SYS_read, fd, buffer, size) _builtin_syscall(SYS_close, fd) @@ -378,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 @@ -388,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) @@ -398,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) @@ -406,12 +408,12 @@ 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 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() @@ -435,7 +437,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) - mem.copy(s, dup, size) + mem.copy(s as ptr, dup as ptr, size) return dup func str.equal[a: str, b: str] : bool @@ -471,8 +473,8 @@ 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) + 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 @@ -504,7 +506,7 @@ func str.substr[s: str, start: i64, length: i64] : str panic("str.substr out of bounds") let out: str = mem.alloc(length + 1) - mem.copy(s + start, out, length) + mem.copy(s as ptr + start, out as ptr, length) out[length] = 0 return out @@ -591,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 @@ -662,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' @@ -769,10 +771,10 @@ 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)) + xs->data = must(mem.realloc?(xs->data, size * 8)) as ptr mem.zero(xs->data, size * 8) xs->size = size xs->capacity = size @@ -815,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 @@ -841,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)) @@ -858,20 +860,20 @@ func alg.count[arr: Array, item: any] : i64 count = count + 1 return count -func alg.map[arr: Array, fn: ptr] : Array - let out: Array = array.new_with_size_zeroed(arr->size) +func alg.map[arr: Array, fn: fnptr] : Array + 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 -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 @@ -900,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) @@ -914,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 @@ -966,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) @@ -981,14 +983,14 @@ 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 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 '..' @@ -999,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/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 new file mode 100644 index 0000000..07fb7a7 --- /dev/null +++ b/src/typechecker.rs @@ -0,0 +1,481 @@ +use std::collections::HashMap; + +use crate::{ + analyzer::{Analyzer, Type}, + parser::{Expr, Stmt}, + tokenizer::{TokenType, ZernError, error}, +}; + +macro_rules! expect_type { + ($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, actual) + ); + } + }}; +} + +macro_rules! expect_types { + ($expr_type:expr, [$( $expected:expr ),+], $loc:expr) => { + if $expr_type != "any" && $( $expr_type != $expected )&&+ { + return error!( + $loc, + format!( + "expected one of [{}], got '{}'", + [$( $expected ),+].join(", "), + $expr_type + ) + ); + } + }; +} + +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) { + assert!(!self.scopes.is_empty()); + 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<'a> { + analyzer: &'a Analyzer, + current_function_return_type: String, +} + +impl<'a> TypeChecker<'a> { + pub fn new(analyzer: &'a Analyzer) -> TypeChecker<'a> { + TypeChecker { + analyzer, + current_function_return_type: String::new(), + } + } + + pub fn typecheck_stmt(&mut self, env: &mut Env, stmt: &Stmt) -> Result<(), ZernError> { + match stmt { + Stmt::Expression(expr) => { + self.typecheck_expr(env, expr)?; + } + Stmt::Let { + name, + var_type, + 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!( + &name.loc, + "unrecognized type: ".to_owned() + &var_type.lexeme + ); + } + expect_type!(actual_type.clone(), var_type.lexeme, var_type.loc); + + if actual_type == "any" { + actual_type = var_type.lexeme.clone(); + } + } + + env.define_var(name.lexeme.clone(), actual_type); + } + 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 { + keyword, + condition, + then_branch, + else_branch, + } => { + 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 { + keyword, + condition, + body, + } => { + expect_types!( + self.typecheck_expr(env, condition)?, + ["i64", "u8", "ptr", "bool"], + keyword.loc + ); + self.typecheck_stmt(env, body)?; + } + Stmt::For { + var, + start, + end, + body, + } => { + 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, + 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 + ); + } + + 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, + "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)?; + + env.pop_scope(); + } + 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 } => { + 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(()) + } + + 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)?; + + match op.token_type { + 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 => { + 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 => { + 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!(), + } + } + 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()), + 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 } => { + 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_types!(right_type, ["bool", "i64", "ptr", "u8"], op.loc); + Ok("bool".into()) + } + _ => unreachable!(), + } + } + Expr::Variable(name) => { + if self.analyzer.constants.contains_key(&name.lexeme) { + Ok("i64".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 } => { + let value_type = self.typecheck_expr(env, value)?; + + match left.as_ref() { + Expr::Variable(name) => { + let existing_var_type = match env.get_var_type(&name.lexeme) { + Some(x) => x, + None => { + return error!( + name.loc, + format!("undefined variable: {}", &name.lexeme) + ); + } + }; + expect_type!(value_type.clone(), *existing_var_type, name.loc); + } + Expr::Index { + expr, + bracket, + index, + } => { + 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.clone(), ["u8", "i64"], bracket.loc); + } + Expr::MemberAccess { left, field } => { + let left_type = self.typecheck_expr(env, left)?; + + let fields = match self.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.clone(), f.field_type, field.loc); + } + _ => return error!(&op.loc, "invalid assignment target"), + } + Ok(value_type) + } + Expr::Call { + callee, + paren, + args, + } => { + 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]; + // 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 { + // its a variadic function, cant check arg types + for arg in args { + self.typecheck_expr(env, arg)?; + } + } + Ok(fn_type.return_type.clone()) + } 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 { + self.typecheck_expr(env, arg)?; + } + Ok("any".into()) + } + } + Expr::ArrayLiteral(exprs) => { + for expr in exprs { + self.typecheck_expr(env, expr)?; + } + Ok("Array".into()) + } + Expr::Index { + expr, + bracket, + index, + } => { + 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 } => match expr.as_ref() { + Expr::Variable(name) => { + if self.analyzer.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) => { + if !self.analyzer.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)?; + + let fields = match self.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)?; + 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()) + } + } + } + + fn is_valid_type_name(&self, name: &str) -> bool { + if BUILTIN_TYPES.contains(&name) { + return true; + } + if self.analyzer.structs.contains_key(name) { + return true; + } + false + } +}