From 1471c229c29b2cad431530403bf2ab306889f6ac Mon Sep 17 00:00:00 2001 From: Toni Date: Fri, 17 Jul 2026 11:44:05 +0200 Subject: [PATCH] generic types, any -> opaque --- README.md | 2 +- examples/chip8.zr | 12 ++-- examples/euler/euler_11.zr | 10 +-- examples/euler/euler_18.zr | 8 +-- examples/euler/euler_20.zr | 2 +- examples/raylib.zr | 10 +-- examples/rule110.zr | 8 +-- examples/sqlite_todo.zr | 16 ++--- src/codegen_x86_64.rs | 21 +++--- src/parser.rs | 48 ++++++++++--- src/symbol_table.rs | 6 +- src/tokenizer.rs | 2 + src/typechecker.rs | 134 ++++++++++++++++++++++++++++++------- std/containers.zr | 78 ++++++++++----------- std/json.zr | 42 ++++++------ std/mem.zr | 18 ++--- std/net.zr | 10 +-- std/os.zr | 8 +-- std/str.zr | 2 +- test.zr | 14 ++-- 20 files changed, 284 insertions(+), 167 deletions(-) diff --git a/README.md b/README.md index b706ed6..f88dad9 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ A very cool language * Compiles to x86-64 Assembly * No libc required! * Produces tiny static executables (11KB for `hello.zr`) -* Has static typing, [UFCS](https://en.wikipedia.org/wiki/Uniform_function_call_syntax), variadics, dynamic arrays, hashmaps, DNS resolver, etc. +* Has static typing, [UFCS](https://en.wikipedia.org/wiki/Uniform_function_call_syntax), generics, variadics, dynamic arrays, hashmaps, DNS resolver, etc. ## Syntax ```rust diff --git a/examples/chip8.zr b/examples/chip8.zr index 7ff66ff..1fc45b1 100644 --- a/examples/chip8.zr +++ b/examples/chip8.zr @@ -14,15 +14,15 @@ extern IsKeyDown struct CHIP8 memory: ptr pc: i64 - stack: Array + stack: Array sp: i64 reg: ptr I: i64 delay_timer: u8 sound_timer: u8 - keyboard: Array + keyboard: Array display: ptr - keyboard_map: Array + keyboard_map: Array func CHIP8.init[c: CHIP8] : void c->memory = mem.alloc(4096) @@ -242,14 +242,14 @@ func CHIP8.step[c: CHIP8] : void if IsKeyDown(c->keyboard_map->nth(c->reg[x] as i64)) c->pc += 2 else if kk == 0xa1 - if !IsKeyDown(c->keyboard_map->nth(c->reg[x] as i64)) + if !(IsKeyDown(c->keyboard_map->nth(c->reg[x] as i64)) as bool) c->pc += 2 else if op == 0xf if kk == 0x07 c->reg[x] = c->delay_timer else if kk == 0x0A key_pressed := false - while !key_pressed && !WindowShouldClose() + while !key_pressed && !(WindowShouldClose() as bool) for i in 0..16 if IsKeyDown(c->keyboard_map->nth(i)) key_pressed = true @@ -305,7 +305,7 @@ func main[argc: i64, argv: ptr] : i64 InitWindow(640, 320, "CHIP-8") SetTargetFPS(60) - while !WindowShouldClose() + while !(WindowShouldClose() as bool) if c->delay_timer > 0 c->delay_timer = c->delay_timer - 1 if c->sound_timer > 0 diff --git a/examples/euler/euler_11.zr b/examples/euler/euler_11.zr index b9052a1..1f90f80 100644 --- a/examples/euler/euler_11.zr +++ b/examples/euler/euler_11.zr @@ -4,7 +4,7 @@ include "$/containers.zr" func main[] : i64 N := 20 - grid := [] + grid := [] as Array > grid->push([8, 2, 22, 97, 38, 15, 0, 40, 0, 75, 4, 5, 7, 78, 52, 12, 50, 77, 91, 8]) grid->push([49, 49, 99, 40, 17, 81, 18, 57, 60, 87, 17, 40, 98, 43, 69, 48, 4, 56, 62, 0]) grid->push([81, 49, 31, 73, 55, 79, 14, 29, 93, 71, 40, 67, 53, 88, 30, 3, 49, 13, 36, 65]) @@ -30,10 +30,10 @@ func main[] : i64 for i in 0..N-3 for j in 0..N-3 - h : i64 = (grid->nth(i) as Array)->nth(j) * (grid->nth(i) as Array)->nth(j+1) * (grid->nth(i) as Array)->nth(j+2) * (grid->nth(i) as Array)->nth(j+3) - v : i64 = (grid->nth(j) as Array)->nth(i) * (grid->nth(j+1) as Array)->nth(i) * (grid->nth(j+2) as Array)->nth(i) * (grid->nth(j+3) as Array)->nth(i) - d1 : i64 = (grid->nth(i) as Array)->nth(j) * (grid->nth(i+1) as Array)->nth(j+1) * (grid->nth(i+2) as Array)->nth(j+2) * (grid->nth(i+3) as Array)->nth(j+3) - d2 : i64 = (grid->nth(i) as Array)->nth(N-j-1) * (grid->nth(i+1) as Array)->nth(N-j-2) * (grid->nth(i+2) as Array)->nth(N-j-3) * (grid->nth(i+3) as Array)->nth(N-j-4) + h : i64 = grid->nth(i)->nth(j) * grid->nth(i)->nth(j+1) * grid->nth(i)->nth(j+2) * grid->nth(i)->nth(j+3) + v : i64 = grid->nth(j)->nth(i) * grid->nth(j+1)->nth(i) * grid->nth(j+2)->nth(i) * grid->nth(j+3)->nth(i) + d1 : i64 = grid->nth(i)->nth(j) * grid->nth(i+1)->nth(j+1) * grid->nth(i+2)->nth(j+2) * grid->nth(i+3)->nth(j+3) + d2 : i64 = grid->nth(i)->nth(N-j-1) * grid->nth(i+1)->nth(N-j-2) * grid->nth(i+2)->nth(N-j-3) * grid->nth(i+3)->nth(N-j-4) out = math.max(out, math.max(h, math.max(v, math.max(d1, d2)))) io.println_i64(out) diff --git a/examples/euler/euler_18.zr b/examples/euler/euler_18.zr index 75f9733..4a3265d 100644 --- a/examples/euler/euler_18.zr +++ b/examples/euler/euler_18.zr @@ -1,17 +1,17 @@ include "$/io.zr" include "$/containers.zr" -func findmax[triangle: Array, row: i64, col: i64] : i64 +func findmax[triangle: Array >, row: i64, col: i64] : i64 if row == 14 - return (triangle->nth(row) as Array)->nth(col) + return triangle->nth(row)->nth(col) left := findmax(triangle, row + 1, col) right := findmax(triangle, row + 1, col + 1) - return (triangle->nth(row) as Array)->nth(col) + math.max(left, right) + return triangle->nth(row)->nth(col) + math.max(left, right) func main[] : i64 - triangle := [] + triangle := [] as Array > triangle->push([75]) triangle->push([95, 64]) triangle->push([17, 47, 82]) diff --git a/examples/euler/euler_20.zr b/examples/euler/euler_20.zr index 50b517b..d3c6721 100644 --- a/examples/euler/euler_20.zr +++ b/examples/euler/euler_20.zr @@ -1,7 +1,7 @@ include "$/io.zr" include "$/containers.zr" -func multiply[n: Array, x: i64] : void +func multiply[n: Array, x: i64] : void carry := 0 for i in 0..n->size prod : i64 = n->nth(i) * x + carry diff --git a/examples/raylib.zr b/examples/raylib.zr index 62b0c2e..4f96dc8 100644 --- a/examples/raylib.zr +++ b/examples/raylib.zr @@ -26,14 +26,14 @@ func main[] : i64 InitWindow(800, 600, "Hello, World!") SetTargetFPS(60) - while !WindowShouldClose() - if IsKeyDown(KEY_W) & 255 + while !(WindowShouldClose() as bool) + if IsKeyDown(KEY_W) as u8 & 255 y -= 10 - if IsKeyDown(KEY_S) & 255 + if IsKeyDown(KEY_S) as u8 & 255 y += 10 - if IsKeyDown(KEY_A) & 255 + if IsKeyDown(KEY_A) as u8 & 255 x -= 10 - if IsKeyDown(KEY_D) & 255 + if IsKeyDown(KEY_D) as u8 & 255 x += 10 BeginDrawing() diff --git a/examples/rule110.zr b/examples/rule110.zr index f6b7c16..e4b62ed 100644 --- a/examples/rule110.zr +++ b/examples/rule110.zr @@ -1,8 +1,8 @@ include "$/io.zr" include "$/containers.zr" -func rule110_step[state: Array] : void - new_state := [] +func rule110_step[state: Array] : void + new_state := [] as Array for i in 0..state->size left := false @@ -18,7 +18,7 @@ func rule110_step[state: Array] : void mem.copy(new_state->data, state->data, state->size * 8) new_state->free() -func print_state[state: Array]: void +func print_state[state: Array]: void for i in 0..state->size if state->nth(i) io.print_char('#') @@ -29,7 +29,7 @@ func print_state[state: Array]: void func main[] : i64 SIZE := 60 - state := [] + state := [] as Array for i in 0..SIZE state->push(false) state->push(true) diff --git a/examples/sqlite_todo.zr b/examples/sqlite_todo.zr index 1330288..adb2e89 100644 --- a/examples/sqlite_todo.zr +++ b/examples/sqlite_todo.zr @@ -20,13 +20,13 @@ func main[] : i64 db := 0 stmt := 0 - rc = sqlite3_open("todo.db", ^db) + rc = sqlite3_open("todo.db", ^db) as i64 if rc panic("failed to open db") - rc = sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS todo(id INTEGER PRIMARY KEY AUTOINCREMENT, task TEXT NOT NULL);", 0, 0, 0) + rc = sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS todo(id INTEGER PRIMARY KEY AUTOINCREMENT, task TEXT NOT NULL);", 0, 0, 0) as i64 if rc - panic(sqlite3_errmsg(db)) + panic(sqlite3_errmsg(db) as str) while true io.println("1. List tasks") @@ -43,7 +43,7 @@ func main[] : i64 io.println("============") sqlite3_prepare_v2(db, "SELECT * FROM todo", -1, ^stmt, 0) - while sqlite3_step(stmt) == SQLITE_ROW + while sqlite3_step(stmt) as i64 == SQLITE_ROW id := sqlite3_column_int(stmt, 0) as i64 task := sqlite3_column_text(stmt, 1) as str io.printf("%d - %s\n", id, task) @@ -55,8 +55,8 @@ func main[] : i64 sqlite3_prepare_v2(db, "INSERT INTO todo(task) VALUES (?);", -1, ^stmt, 0) sqlite3_bind_text(stmt, 1, task, -1, 0) - if sqlite3_step(stmt) != SQLITE_DONE - panic(sqlite3_errmsg(db)) + if sqlite3_step(stmt) as i64 != SQLITE_DONE + panic(sqlite3_errmsg(db) as str) io.println("\nTask added\n") else if choice == 3 @@ -65,8 +65,8 @@ func main[] : i64 sqlite3_prepare_v2(db, "DELETE FROM todo WHERE id = ?;", -1, ^stmt, 0) sqlite3_bind_int(stmt, 1, id, -1, 0) - if sqlite3_step(stmt) != SQLITE_DONE - panic(sqlite3_errmsg(db)) + if sqlite3_step(stmt) as i64 != SQLITE_DONE + panic(sqlite3_errmsg(db) as str) io.println("\nTask deleted\n") diff --git a/src/codegen_x86_64.rs b/src/codegen_x86_64.rs index c1d8763..0f8430c 100644 --- a/src/codegen_x86_64.rs +++ b/src/codegen_x86_64.rs @@ -231,7 +231,7 @@ _builtin_environ: let var_type: String = match var_type { Some(t) => t.lexeme.clone(), None => match self.expr_types[&initializer.id].as_str() { - "any" => return error!(name.loc, "cannot infer type from any"), + "opaque" => return error!(name.loc, "cannot infer type from opaque"), t => t.into(), }, }; @@ -660,12 +660,7 @@ _builtin_environ: self.compile_expr(env, right)?; match op.token_type { TokenType::Minus => { - if self.expr_types[&right.id] == "f64" { - emit!(&mut self.output, " mov rbx, 0x8000000000000000"); - emit!(&mut self.output, " xor rax, rbx"); - } else { - emit!(&mut self.output, " neg rax"); - } + emit!(&mut self.output, " neg rax"); } TokenType::Bang => { emit!(&mut self.output, " test rax, rax"); @@ -806,7 +801,8 @@ _builtin_environ: struct_name, use_heap, } => { - let struct_fields = &self.symbol_table.structs[&struct_name.lexeme]; + let struct_fields = + &self.symbol_table.structs[self.strip_generic(&struct_name.lexeme)]; let memory_size = struct_fields.len() * 8; if *use_heap { @@ -835,7 +831,8 @@ _builtin_environ: } ExprKind::MethodCall { expr, method, args } => { let receiver_type = &self.expr_types[&expr.id]; - let func_name = format!("{}.{}", receiver_type, method.lexeme); + let base_type = self.strip_generic(receiver_type); + let func_name = format!("{}.{}", base_type, method.lexeme); self.compile_expr(env, expr)?; emit!(&mut self.output, " push rax"); @@ -910,8 +907,12 @@ _builtin_environ: } } + fn strip_generic<'b>(&self, type_name: &'b str) -> &'b str { + type_name.split('<').next().unwrap_or(type_name) + } + fn get_field_offset(&self, left: &Expr, field: &Token) -> Result { - let struct_name = &self.expr_types[&left.id]; + let struct_name = self.strip_generic(&self.expr_types[&left.id]); let fields = match self.symbol_table.structs.get(struct_name) { Some(f) => f, diff --git a/src/parser.rs b/src/parser.rs index daf5095..19119f1 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -175,6 +175,38 @@ impl Parser { Ok(statements) } + fn parse_type_ref(&mut self) -> Result { + if self.match_token(&[TokenType::Dollar]) { + Ok(Token { + token_type: TokenType::Identifier, + lexeme: "$".to_string(), + loc: self.previous().loc.clone(), + }) + } else { + let ident = self.consume(TokenType::Identifier, "expected type name")?; + let mut type_name = ident.lexeme.clone(); + if self.match_token(&[TokenType::Less]) { + type_name.push('<'); + loop { + let inner = self.parse_type_ref()?; + type_name.push_str(&inner.lexeme); + if self.match_token(&[TokenType::Comma]) { + type_name.push(','); + } else { + break; + } + } + self.consume(TokenType::Greater, "expected '>'")?; + type_name.push('>'); + } + Ok(Token { + token_type: TokenType::Identifier, + lexeme: type_name, + loc: ident.loc, + }) + } + } + fn declaration(&mut self) -> Result { if !self.is_inside_function { if self.match_token(&[TokenType::KeywordFunc]) { @@ -217,8 +249,7 @@ impl Parser { self.consume(TokenType::Identifier, "expected parameter name")?; self.consume(TokenType::Colon, "expected ':' after parameter name")?; - let var_type = - self.consume(TokenType::Identifier, "expected parameter type")?; + let var_type = self.parse_type_ref()?; params.push(Param { var_type, var_name }); if !self.match_token(&[TokenType::Comma]) { @@ -233,7 +264,7 @@ impl Parser { let mut return_types = vec![]; loop { - return_types.push(self.consume(TokenType::Identifier, "expected return type")?); + return_types.push(self.parse_type_ref()?); if !self.match_token(&[TokenType::Comma]) { break; } @@ -266,7 +297,7 @@ impl Parser { let var_name = self.consume(TokenType::Identifier, "expected field name")?; self.consume(TokenType::Colon, "expected ':' after field name")?; - let var_type = self.consume(TokenType::Identifier, "expected field type")?; + let var_type = self.parse_type_ref()?; fields.push(Param { var_type, var_name }); } @@ -309,8 +340,8 @@ impl Parser { let var_type = if self.match_token(&[TokenType::Equal]) { None } else { - let var_type = self.consume(TokenType::Identifier, "expected variable type")?; - self.consume(TokenType::Equal, "expected '=' after varaible type")?; + let var_type = self.parse_type_ref()?; + self.consume(TokenType::Equal, "expected '=' after variable type")?; Some(var_type) }; @@ -559,7 +590,7 @@ impl Parser { let mut expr = self.unary()?; while self.match_token(&[TokenType::KeywordAs]) { - let type_name = self.consume(TokenType::Identifier, "expected type after 'as'")?; + let type_name = self.parse_type_ref()?; expr = Expr::new(ExprKind::Cast { expr: Box::new(expr), type_name, @@ -688,8 +719,7 @@ impl Parser { Ok(Expr::new(ExprKind::ArrayLiteral(xs))) } else if self.match_token(&[TokenType::KeywordNew]) { let use_heap = self.match_token(&[TokenType::Star]); - let struct_name = - self.consume(TokenType::Identifier, "expected struct name after 'new'")?; + let struct_name = self.parse_type_ref()?; Ok(Expr::new(ExprKind::New { struct_name, use_heap, diff --git a/src/symbol_table.rs b/src/symbol_table.rs index 16af961..0b9afaa 100644 --- a/src/symbol_table.rs +++ b/src/symbol_table.rs @@ -59,11 +59,11 @@ impl SymbolTable { ("_builtin_cvttsd2si".into(), FnType::new("i64", vec!["f64"])), ( "_builtin_f64_to_f32".into(), - FnType::new("any", vec!["f64"]), + FnType::new("opaque", vec!["f64"]), ), ("_builtin_syscall".into(), FnType::new_variadic("i64")), ("_builtin_environ".into(), FnType::new("ptr", vec![])), - ("_var_arg".into(), FnType::new("any", vec!["i64"])), + ("_var_arg".into(), FnType::new("opaque", vec!["i64"])), ("_stackalloc".into(), FnType::new("ptr", vec!["i64"])), ]), constants: HashMap::new(), @@ -98,7 +98,7 @@ impl SymbolTable { return error!(name.loc, format!("tried to redefine '{}'", name.lexeme)); } self.functions - .insert(name.lexeme.clone(), FnType::new_variadic("any")); + .insert(name.lexeme.clone(), FnType::new_variadic("opaque")); } Stmt::Function { name, diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 6d41ee9..915ee19 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -65,6 +65,7 @@ pub enum TokenType { Indent, Dedent, + Dollar, Eof, } @@ -249,6 +250,7 @@ impl<'a> Tokenizer<'a> { self.add_token(TokenType::Less)? } } + '$' => self.add_token(TokenType::Dollar)?, '\'' => { if self.eof() { return error!(self.loc, "unterminated char literal"); diff --git a/src/typechecker.rs b/src/typechecker.rs index 936738a..bd477b3 100644 --- a/src/typechecker.rs +++ b/src/typechecker.rs @@ -9,7 +9,7 @@ use crate::{ macro_rules! expect_type { ($expr_type:expr, $expected:expr, $loc:expr) => {{ let actual = $expr_type; - if $expected != "any" && actual != "any" && actual != $expected { + if $expected != "$" && actual != "$" && actual != $expected { return error!( $loc, format!("expected type '{}', got {}", $expected, actual) @@ -20,7 +20,7 @@ macro_rules! expect_type { macro_rules! expect_types { ($expr_type:expr, [$( $expected:expr ),+], $loc:expr) => { - if $expr_type != "any" && $( $expr_type != $expected )&&+ { + if $expr_type != "$" && $( $expr_type != $expected )&&+ { return error!( $loc, format!( @@ -33,7 +33,7 @@ macro_rules! expect_types { }; } -static BUILTIN_TYPES: [&str; 8] = ["void", "u8", "i64", "f64", "str", "bool", "ptr", "any"]; +static BUILTIN_TYPES: [&str; 8] = ["void", "u8", "i64", "f64", "str", "bool", "ptr", "opaque"]; pub struct Env { scopes: Vec>, @@ -110,7 +110,7 @@ impl<'a> TypeChecker<'a> { } expect_type!(actual_type.clone(), var_type.lexeme, var_type.loc); - if actual_type == "any" { + if actual_type == "opaque" { actual_type = var_type.lexeme.clone(); } } @@ -150,8 +150,9 @@ impl<'a> TypeChecker<'a> { } ExprKind::MemberAccess { left, field } => { let left_type = self.typecheck_expr(env, left)?; + let (base_name, generic_args) = parse_generic_type(&left_type); - let fields = match self.symbol_table.structs.get(&left_type) { + let fields = match self.symbol_table.structs.get(base_name) { Some(f) => f, None => { return error!( @@ -171,7 +172,13 @@ impl<'a> TypeChecker<'a> { } }; - expect_type!(value_type.clone(), f.field_type, field.loc); + let field_type = if let Some(args) = generic_args { + substitute_type(&f.field_type, args[0]) + } else { + f.field_type.clone() + }; + + expect_type!(value_type.clone(), field_type, field.loc); } _ => return error!(&op.loc, "invalid assignment target"), } @@ -214,7 +221,7 @@ impl<'a> TypeChecker<'a> { } => { expect_types!( self.typecheck_expr(env, condition)?, - ["i64", "u8", "ptr", "bool"], + ["i64", "u8", "ptr", "bool", "opaque"], keyword.loc ); self.typecheck_stmt(env, then_branch)?; @@ -436,7 +443,7 @@ impl<'a> TypeChecker<'a> { let right_type = self.typecheck_expr(env, right)?; match op.token_type { TokenType::Minus => { - expect_types!(right_type, ["f64", "i64"], op.loc); + expect_types!(right_type, ["i64"], op.loc); Ok(right_type) } TokenType::Bang => { @@ -499,7 +506,7 @@ impl<'a> TypeChecker<'a> { for arg in args { self.typecheck_expr(env, arg)?; } - Ok("any".into()) + Ok("opaque".into()) } } else { // its an expression that evalutes to function address @@ -508,14 +515,19 @@ impl<'a> TypeChecker<'a> { for arg in args { self.typecheck_expr(env, arg)?; } - Ok("any".into()) + Ok("opaque".into()) } } ExprKind::ArrayLiteral(exprs) => { for expr in exprs { self.typecheck_expr(env, expr)?; } - Ok("Array".into()) + if exprs.is_empty() { + Ok("Array".into()) + } else { + let first_item_type = self.typecheck_expr(env, &exprs[0])?; + Ok(format!("Array<{}>", first_item_type)) + } } ExprKind::Index { expr, @@ -536,34 +548,42 @@ impl<'a> TypeChecker<'a> { struct_name, use_heap: _, } => { - if !self.symbol_table.structs.contains_key(&struct_name.lexeme) { + let (base_name, _) = parse_generic_type(&struct_name.lexeme); + if !self.symbol_table.structs.contains_key(base_name) { return error!( &struct_name.loc, - format!("unknown struct name: {}", &struct_name.lexeme) + format!("unknown struct name: {}", base_name) ); } Ok(struct_name.lexeme.clone()) } ExprKind::MemberAccess { left, field } => { let left_type = self.typecheck_expr(env, left)?; + let (base_name, generic_args) = parse_generic_type(&left_type); - let fields = match self.symbol_table.structs.get(&left_type) { + let fields = match self.symbol_table.structs.get(base_name) { Some(f) => f, None => { - return error!(&field.loc, format!("unknown struct type: {}", left_type)); + return error!(&field.loc, format!("unknown struct type: {}", base_name)); } }; - let field = match fields.get(&field.lexeme) { + let field_info = match fields.get(&field.lexeme) { Some(o) => o, None => return error!(&field.loc, format!("unknown field: {}", &field.lexeme)), }; - Ok(field.field_type.clone()) + let field_type = if let Some(args) = generic_args { + substitute_type(&field_info.field_type, args[0]) + } else { + field_info.field_type.clone() + }; + + Ok(field_type) } ExprKind::Cast { expr, type_name } => { let expr_type = self.typecheck_expr(env, expr)?; - if expr_type != "any" && type_name.lexeme == "f64" { + if expr_type != "opaque" && type_name.lexeme == "f64" { return error!( &type_name.loc, "use _builtin_cvtsi2sd and _builtin_cvttsd2si to cast between integers and f64" @@ -579,7 +599,8 @@ impl<'a> TypeChecker<'a> { } ExprKind::MethodCall { expr, method, args } => { let receiver_type = self.typecheck_expr(env, expr)?; - let func_name = format!("{}.{}", receiver_type, method.lexeme); + let (base_name, generic_args) = parse_generic_type(&receiver_type); + let func_name = format!("{}.{}", base_name, method.lexeme); let func_type = match self.symbol_table.functions.get(&func_name) { Some(f) => f, @@ -588,15 +609,26 @@ impl<'a> TypeChecker<'a> { method.loc, format!( "method {} not found on on type {}", - method.lexeme, receiver_type + method.lexeme, base_name ) ); } }; + let substitute = |s: &str| -> String { + if let Some(ref args) = generic_args { + substitute_type(s, args[0]) + } else { + s.to_string() + } + }; + match &func_type.params { FnParams::Normal(params) => { - if params.is_empty() || params[0] != receiver_type { + let substituted_params: Vec = + params.iter().map(|p| substitute(p)).collect(); + + if substituted_params.is_empty() || substituted_params[0] != receiver_type { return error!( method.loc, format!( @@ -605,26 +637,30 @@ impl<'a> TypeChecker<'a> { ) ); } - if params.len() != args.len() + 1 { + if substituted_params.len() != args.len() + 1 { return error!( method.loc, format!( "expected {} arguments, got {}", - params.len() - 1, + substituted_params.len() - 1, args.len() ) ); } for (i, arg) in args.iter().enumerate() { - expect_type!(self.typecheck_expr(env, arg)?, params[i + 1], method.loc); + expect_type!( + self.typecheck_expr(env, arg)?, + substituted_params[i + 1].as_str(), + method.loc + ); } - Ok(func_type.return_type.clone()) + Ok(substitute(&func_type.return_type)) } FnParams::Variadic => { for arg in args { self.typecheck_expr(env, arg)?; } - Ok(func_type.return_type.clone()) + Ok(substitute(&func_type.return_type)) } } } @@ -635,9 +671,22 @@ impl<'a> TypeChecker<'a> { } fn is_valid_type_name(&self, name: &str) -> bool { + if name == "$" { + return true; + } if name.contains(',') { return name.split(',').all(|part| self.is_valid_type_name(part)); } + if name.contains('<') { + let (base, inner) = parse_generic_type(name); + if !self.symbol_table.structs.contains_key(base) { + return false; + } + if let Some(args) = inner { + return args.iter().all(|arg| self.is_valid_type_name(arg)); + } + return false; + } if BUILTIN_TYPES.contains(&name) { return true; } @@ -647,3 +696,36 @@ impl<'a> TypeChecker<'a> { false } } + +fn parse_generic_type(s: &str) -> (&str, Option>) { + if let Some(lt_pos) = s.find('<') { + let base = &s[..lt_pos]; + let inner = &s[lt_pos + 1..s.len() - 1]; + let mut args = Vec::new(); + let mut depth = 0; + let mut start = 0; + for (i, ch) in inner.char_indices() { + match ch { + '<' => depth += 1, + '>' => depth -= 1, + ',' if depth == 0 => { + args.push(&inner[start..i]); + start = i + 1; + } + _ => {} + } + } + args.push(&inner[start..]); + (base, Some(args)) + } else { + (s, None) + } +} + +fn substitute_type(type_str: &str, dollar_replacement: &str) -> String { + if type_str.contains('$') { + type_str.replace('$', dollar_replacement) + } else { + type_str.to_string() + } +} diff --git a/std/containers.zr b/std/containers.zr index ae5a308..d5b5057 100644 --- a/std/containers.zr +++ b/std/containers.zr @@ -5,26 +5,24 @@ struct Array size: i64 capacity: i64 -func Array.new_preallocated_and_zeroed[size: i64] : Array - xs := new* Array +func Array.preallocate_and_zero[xs: Array<$>, size: i64] : void if size > 0 xs->data = mem.alloc(size * 8) mem.zero(xs->data, size * 8) xs->size = size xs->capacity = size - return xs -func Array.nth[xs: Array, n: i64] : any +func Array.nth[xs: Array<$>, n: i64] : $ if n < 0 || n >= xs->size panic("Array.nth out of bounds") return mem.read64(xs->data + n * 8) -func Array.set[xs: Array, n: i64, x: any] : void +func Array.set[xs: Array<$>, n: i64, x: $] : void if n < 0 || n >= xs->size panic("Array.set out of bounds") mem.write64(xs->data + n * 8, x) -func Array.push[xs: Array, x: any] : void +func Array.push[xs: Array<$>, x: $] : void if xs->size == xs->capacity new_capacity := 4 if xs->capacity != 0 @@ -35,54 +33,56 @@ func Array.push[xs: Array, x: any] : void mem.write64(xs->data + xs->size * 8, x) xs->size += 1 -func Array.free[xs: Array] : void +func Array.free[xs: Array<$>] : void if xs->data != 0 xs->data->free() (xs as ptr)->free() -func Array.free_with_items[xs: Array] : void +func Array.free_with_items[xs: Array<$>] : void if xs->data != 0 for i in 0..xs->size (xs->nth(i) as ptr)->free() xs->data->free() (xs as ptr)->free() -func Array.pop[xs: Array] : any +func Array.pop[xs: Array<$>] : $ if xs->size == 0 panic("Array.pop on empty array") - x : any = Array.last(xs) + x := Array.last(xs) xs->size = xs->size - 1 return x -func Array.last[xs: Array] : any +func Array.last[xs: Array<$>] : $ if xs->size == 0 panic("Array.last on empty array") return xs->nth(xs->size - 1) -func Array.slice[xs: Array, start: i64, length: i64] : Array +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") - new_array := Array.new_preallocated_and_zeroed(length) + new_array := [] as Array<$> + new_array->preallocate_and_zero(length) mem.copy(xs->data + start * 8, new_array->data, length * 8) return new_array -func Array.concat[a: Array, b: Array] : Array - new_array := Array.new_preallocated_and_zeroed(a->size + b->size) +func Array.concat[a: Array<$>, b: Array<$>] : Array<$> + new_array := [] as Array<$> + new_array->preallocate_and_zero(a->size + b->size) mem.copy(a->data, new_array->data, a->size * 8) mem.copy(b->data, new_array->data + a->size * 8, b->size * 8) return new_array -func Array.quicksort[arr: Array] : void +func Array.quicksort[arr: Array<$>] : void arr->_do_quicksort(0, arr->size - 1) -func Array._do_quicksort[arr: Array, low: i64, high: i64] : void +func Array._do_quicksort[arr: Array<$>, low: i64, high: i64] : void if low < high i := arr->_partition(low, high) arr->_do_quicksort(low, i - 1) arr->_do_quicksort(i + 1, high) -func Array._partition[arr: Array, low: i64, high: i64] : i64 +func Array._partition[arr: Array<$>, low: i64, high: i64] : i64 pivot : i64 = arr->nth(high) i := low - 1 for j in (low)..high @@ -96,41 +96,42 @@ func Array._partition[arr: Array, low: i64, high: i64] : i64 arr->set(high, temp) return i + 1 -func Array.contains_str[arr: Array, s: str] : bool +func Array.contains_str[arr: Array, s: str] : bool for i in 0..arr->size if (arr->nth(i) as str)->equal(s) return true return false -func Array.count[arr: Array, item: any] : i64 +func Array.count[arr: Array<$>, item: $] : i64 count := 0 for i in 0..arr->size if arr->nth(i) == item count += 1 return count -func Array.map[arr: Array, fn: ptr] : Array - out := Array.new_preallocated_and_zeroed(arr->size) +func Array.map[arr: Array<$>, fn: ptr] : Array<$> + out := [] as Array<$> + out->preallocate_and_zero(arr->size) for i in 0..arr->size out->set(i, fn(arr->nth(i))) return out -func Array.filter[arr: Array, fn: ptr] : Array - out := [] +func Array.filter[arr: Array<$>, fn: ptr] : Array<$> + out := [] as Array<$> for i in 0..arr->size - if fn(arr->nth(i)) + if fn(arr->nth(i)) as bool out->push(arr->nth(i)) return out -func Array.reduce[arr: Array, fn: ptr, acc: any] : any +func Array.reduce[arr: Array<$>, fn: ptr, acc: $] : $ for i in 0..arr->size acc = fn(acc, arr->nth(i)) return acc -func str.split[haystack: str, needle: str]: Array +func str.split[haystack: str, needle: str]: Array haystack_len := haystack->len() needle_len := needle->len() - result := [] + result := [] as Array if !needle_len if !haystack_len @@ -162,19 +163,20 @@ func str.split[haystack: str, needle: str]: Array const HashMap._TABLE_SIZE = 100 struct HashMap - table: Array + table: Array struct HashMap._Node key: str - value: any + value: $ next: HashMap._Node func HashMap.new[] : HashMap map := new* HashMap - map->table = Array.new_preallocated_and_zeroed(HashMap._TABLE_SIZE) + map->table = [] as Array + map->table->preallocate_and_zero(HashMap._TABLE_SIZE) return map -func HashMap.insert[map: HashMap, key: str, value: any] : void +func HashMap.insert[map: HashMap<$>, key: str, value: $] : void index := HashMap._djb2(key) current : HashMap._Node = map->table->nth(index) @@ -190,7 +192,7 @@ func HashMap.insert[map: HashMap, key: str, value: any] : void new_node->next = map->table->nth(index) map->table->set(index, new_node) -func HashMap.get[map: HashMap, key: str] : any, bool +func HashMap.get[map: HashMap<$>, key: str] : $, bool index := HashMap._djb2(key) current : HashMap._Node = map->table->nth(index) @@ -199,9 +201,9 @@ func HashMap.get[map: HashMap, key: str] : any, bool return current->value, true current = current->next - return 0 as any, false + return 0 as $, false -func HashMap.delete[map: HashMap, key: str] : void +func HashMap.delete[map: HashMap<$>, key: str] : void index := HashMap._djb2(key) current : HashMap._Node = map->table->nth(index) prev := 0 as HashMap._Node @@ -220,8 +222,8 @@ func HashMap.delete[map: HashMap, key: str] : void prev = current current = current->next -func HashMap.keys[map: HashMap] : Array - out := [] +func HashMap.keys[map: HashMap<$>] : Array + out := [] as Array for i in 0..HashMap._TABLE_SIZE current : HashMap._Node = map->table->nth(i) while current as ptr @@ -229,7 +231,7 @@ func HashMap.keys[map: HashMap] : Array current = current->next return out -func HashMap.free_with_keys[map: HashMap] : void +func HashMap.free_with_keys[map: HashMap<$>] : void for i in 0..HashMap._TABLE_SIZE current : HashMap._Node = map->table->nth(i) while current as ptr diff --git a/std/json.zr b/std/json.zr index 7f5e862..455b781 100644 --- a/std/json.zr +++ b/std/json.zr @@ -9,33 +9,33 @@ const json.BOOL = 5 struct json.Value type: i64 - value: any + value: opaque func json.Value.dump[v: json.Value] : str s := new str.Builder if v->type == json.OBJECT - map := v->value as HashMap + map := v->value as HashMap keys := map->keys() s->append_char('{') for i in 0..keys->size if i > 0 s->append_char(',') s->append_char('"') - s->append(keys->nth(i) as str) + s->append(keys->nth(i)) s->append_char('"') s->append_char(':') - value, _ := map->get(keys->nth(i) as str) - s->append((value as json.Value)->dump()) + value, _ := map->get(keys->nth(i)) + s->append(value->dump()) s->append_char('}') keys->free() else if v->type == json.ARRAY - arr := v->value as Array + arr := v->value as Array s->append_char('[') for i in 0..arr->size if i > 0 s->append_char(',') - s->append((arr->nth(i) as json.Value)->dump()) + s->append(arr->nth(i)->dump()) s->append_char(']') else if v->type == json.STRING // TODO: escape sequences @@ -58,25 +58,25 @@ func json.Value.dump[v: json.Value] : str func json.Value.free[v: json.Value] : void if v->type == json.OBJECT - map := v->value as HashMap + map := v->value as HashMap keys := map->keys() for i in 0..keys->size - value, _ := map->get(keys->nth(i) as str) - (value as json.Value)->free() + value, _ := map->get(keys->nth(i)) + value->free() keys->free() map->free_with_keys() else if v->type == json.ARRAY - arr := v->value as Array + arr := v->value as Array for i in 0..arr->size - (arr->nth(i) as json.Value)->free() + arr->nth(i)->free() arr->free() else if v->type == json.STRING (v->value as str)->free() (v as ptr)->free() -func json.val[x: any] : any +func json.val[x: opaque] : opaque return (x as json.Value)->value func json.parse[s: str] : json.Value @@ -96,7 +96,7 @@ func json._parse_value[s: str, i: ptr] : json.Value json._skip_whitespace(s, i) out->type = json.OBJECT - out->value = HashMap.new() + out->value = HashMap.new() as opaque if s[mem.read64(i)] != '}' while true @@ -110,7 +110,7 @@ func json._parse_value[s: str, i: ptr] : json.Value json._skip_whitespace(s, i) value := json._parse_value(s, i) - (out->value as HashMap)->insert(key, value) + (out->value as HashMap)->insert(key, value) key->free() json._skip_whitespace(s, i) @@ -128,12 +128,12 @@ func json._parse_value[s: str, i: ptr] : json.Value json._skip_whitespace(s, i) out->type = json.ARRAY - out->value = [] + out->value = [] as opaque while s[mem.read64(i)] != ']' json._skip_whitespace(s, i) - (out->value as Array)->push(json._parse_value(s, i)) + (out->value as Array)->push(json._parse_value(s, i)) json._skip_whitespace(s, i) if s[mem.read64(i)] == ',' @@ -147,7 +147,7 @@ func json._parse_value[s: str, i: ptr] : json.Value mem.write64(i, mem.read64(i) + 1) else if s[mem.read64(i)] == '"' out->type = json.STRING - out->value = json._parse_string(s, i) + out->value = json._parse_string(s, i) as opaque else if s[mem.read64(i)]->is_digit() || s[mem.read64(i)] == '-' start := mem.read64(i) @@ -164,18 +164,18 @@ func json._parse_value[s: str, i: ptr] : json.Value num_str := s->substr_n(start, len) out->type = json.NUMBER - out->value = num_str->parse_i64() + out->value = num_str->parse_i64() as opaque num_str->free() else if s[mem.read64(i)] == 'n' && s[mem.read64(i)+1] == 'u' && s[mem.read64(i)+2] == 'l' && s[mem.read64(i)+3] == 'l' out->type = json.NULL mem.write64(i, mem.read64(i) + 4) else if s[mem.read64(i)] == 't' && s[mem.read64(i)+1] == 'r' && s[mem.read64(i)+2] == 'u' && s[mem.read64(i)+3] == 'e' out->type = json.BOOL - out->value = true + out->value = true as opaque mem.write64(i, mem.read64(i) + 4) else if s[mem.read64(i)] == 'f' && s[mem.read64(i)+1] == 'a' && s[mem.read64(i)+2] == 'l' && s[mem.read64(i)+3] == 's' && s[mem.read64(i)+4] == 'e' out->type = json.BOOL - out->value = false + out->value = false as opaque mem.write64(i, mem.read64(i) + 5) else panic("json.parse: unexpected character") diff --git a/std/mem.zr b/std/mem.zr index 866e357..9ac6c87 100644 --- a/std/mem.zr +++ b/std/mem.zr @@ -23,7 +23,7 @@ func mem.Block._split[blk: mem.Block, needed: i64] : void if blk->next as ptr blk->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 @@ -46,7 +46,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 @@ -66,7 +66,7 @@ func mem.alloc[size: i64] : ptr blk := mem._request_space(size) if !mem.read64(_builtin_heap_head()) - mem.write64(_builtin_heap_head(), blk) + mem.write64(_builtin_heap_head(), blk as ptr as i64) blk->_split(size) @@ -91,7 +91,7 @@ func ptr.free[x: ptr] : void if next->next as ptr next->next->prev = blk if mem.read64(_builtin_heap_tail()) == next as ptr - mem.write64(_builtin_heap_tail(), blk) + mem.write64(_builtin_heap_tail(), blk as ptr as i64) prev := blk->prev if prev as ptr && prev->free @@ -102,7 +102,7 @@ func ptr.free[x: ptr] : void if blk->next as ptr blk->next->prev = prev if mem.read64(_builtin_heap_tail()) == blk as ptr - mem.write64(_builtin_heap_tail(), prev) + mem.write64(_builtin_heap_tail(), prev as ptr as i64) blk = prev block_total := blk->size + MEM_BLOCK_SIZE @@ -110,11 +110,11 @@ func ptr.free[x: ptr] : void if blk->prev as ptr blk->prev->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 as ptr blk->next->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 ptr.realloc[x: ptr, new_size: i64] : ptr @@ -146,7 +146,7 @@ func ptr.realloc[x: ptr, new_size: i64] : ptr if next->next as ptr next->next->prev = blk if mem.read64(_builtin_heap_tail()) == next as ptr - mem.write64(_builtin_heap_tail(), blk) + mem.write64(_builtin_heap_tail(), blk as ptr as i64) blk->_split(new_size) return x @@ -219,7 +219,7 @@ func mem.write16be[x: ptr, d: i64] : void x[0] = (d >> 8) & 0xff x[1] = d & 0xff -func mem.write64[x: ptr, d: any] : void +func mem.write64[x: ptr, d: i64] : void _builtin_set64(x, d) struct Blob diff --git a/std/net.zr b/std/net.zr index 520f279..76deead 100644 --- a/std/net.zr +++ b/std/net.zr @@ -149,7 +149,7 @@ func net.resolve[domain: str] : i64, bool if parts->size == 4 valid := true for i in 0..4 - p := parts->nth(i) as str + p := parts->nth(i) if p->len() < 1 || p->len() > 3 valid = false break @@ -160,10 +160,10 @@ func net.resolve[domain: str] : i64, bool if !valid break if valid - a := (parts->nth(0) as str)->parse_i64() - b := (parts->nth(1) as str)->parse_i64() - c := (parts->nth(2) as str)->parse_i64() - d := (parts->nth(3) as str)->parse_i64() + a := parts->nth(0)->parse_i64() + b := parts->nth(1)->parse_i64() + c := parts->nth(2)->parse_i64() + d := parts->nth(3)->parse_i64() if a >= 0 && a <= 255 && b >= 0 && b <= 255 && c >= 0 && c <= 255 && d >= 0 && d <= 255 parts->free_with_items() return net.pack_addr(a, b, c, d), true diff --git a/std/os.zr b/std/os.zr index e5efdac..8297dbe 100644 --- a/std/os.zr +++ b/std/os.zr @@ -117,19 +117,19 @@ func os.is_a_directory[path: str] : bool return (mem.read32(st + 24) & S_IFMT) == S_IFDIR -func os.list_directory[path: str] : Array, bool +func os.list_directory[path: str] : Array, bool fd := _builtin_syscall(SYS_openat, AT_FDCWD, path, O_RDONLY, 0) if fd < 0 - return 0 as Array, false + return 0 as Array, false - files := [] + files := [] as Array buf := _stackalloc(1024) while true n := _builtin_syscall(SYS_getdents64, fd, buf, 1024) if n < 0 files->free_with_items() _builtin_syscall(SYS_close, fd) - return 0 as Array, false + return 0 as Array, false else if n == 0 break diff --git a/std/str.zr b/std/str.zr index 4e81f71..e4be7a2 100644 --- a/std/str.zr +++ b/std/str.zr @@ -104,7 +104,7 @@ func str.format_into[..] : i64 i += 1 else if s[0] == 'f' // TODO: use arrow when we implement f64 params - f64.to_str_buf(_var_arg(i), tmp as ptr) + f64.to_str_buf(_var_arg(i) as i64, tmp as ptr) tmp_len := tmp->len() remaining := size - n - 1 mem.copy(tmp as ptr, buf + n, math.min(tmp_len, remaining)) diff --git a/test.zr b/test.zr index 8ced788..7064c19 100644 --- a/test.zr +++ b/test.zr @@ -2,10 +2,10 @@ include "$/io.zr" include "$/os.zr" struct TestRunner - build_blacklist: Array - run_blacklist: Array - custom_build_flags: HashMap - custom_run_args: HashMap + build_blacklist: Array + run_blacklist: Array + custom_build_flags: HashMap + custom_run_args: HashMap func TestRunner.run_directory[tr: TestRunner, dir: str] : void files, ok := os.list_directory(dir) @@ -66,14 +66,14 @@ func main[] : i64 os.exit(1) tr := new TestRunner - tr->build_blacklist = [] + tr->build_blacklist = [] as Array tr->run_blacklist = ["raylib.zr", "sqlite_todo.zr", "guess_number.zr", "udp_server.zr", "chip8.zr", "serve.zr"] - tr->custom_build_flags = HashMap.new() + tr->custom_build_flags = HashMap.new() as HashMap tr->custom_build_flags->insert("raylib.zr", "-m -C \"-lraylib\"") tr->custom_build_flags->insert("chip8.zr", "-m -C \"-lraylib\"") tr->custom_build_flags->insert("sqlite_todo.zr", "-m -C \"-lsqlite3\"") tr->custom_build_flags->insert("serve.zr", "-m -C \"-lpthread\"") - tr->custom_run_args = HashMap.new() + tr->custom_run_args = HashMap.new() as HashMap tr->custom_run_args->insert("curl.zr", "http://example.com") tr->run_directory("examples")