Compare commits

..

3 Commits

Author SHA1 Message Date
a17ffa184a _var_arg 2026-04-04 13:14:58 +02:00
bb42e1fcf7 parse variadic functions 2026-04-04 12:17:44 +02:00
86de997be3 hashmaps, enforce main function type 2026-03-20 19:27:51 +01:00
7 changed files with 265 additions and 91 deletions

View File

@@ -8,7 +8,7 @@ A very cool language
* Growing [standard library](https://github.com/antpiasecki/zern/tree/main/src/std) * Growing [standard library](https://github.com/antpiasecki/zern/tree/main/src/std)
* Produces tiny static executables (11KB for `hello.zr`) * Produces tiny static executables (11KB for `hello.zr`)
* No libc required! * No libc required!
* Has the pipe operator * Has the pipe operator, variadics, dynamic arrays, hashmaps, etc.
## Syntax ## Syntax
```rust ```rust

View File

@@ -1,7 +1,7 @@
use std::{collections::HashMap, fmt::Write}; use std::{collections::HashMap, fmt::Write};
use crate::{ use crate::{
parser::{Expr, Stmt}, parser::{Expr, Params, Stmt},
symbol_table::SymbolTable, symbol_table::SymbolTable,
tokenizer::{Token, TokenType, ZernError, error}, tokenizer::{Token, TokenType, ZernError, error},
}; };
@@ -151,21 +151,6 @@ _builtin_syscall:
mov r9, [rsp+8] mov r9, [rsp+8]
syscall syscall
ret ret
section .text.io.printf
io.printf:
push rbp
mov rbp, rsp
sub rsp, 48
mov [rsp], rsi
mov [rsp + 8], rdx
mov [rsp + 16], rcx
mov [rsp + 24], r8
mov [rsp + 32], r9
lea rsi, [rsp]
call io._printf_impl
leave
ret
" "
); );
@@ -317,9 +302,13 @@ _builtin_environ:
emit!(&mut self.output, " mov rbp, rsp"); emit!(&mut self.output, " mov rbp, rsp");
emit!(&mut self.output, " sub rsp, 256"); // TODO: eww emit!(&mut self.output, " sub rsp, 256"); // TODO: eww
match params {
Params::Normal(params) => {
for (i, param) in params.iter().enumerate() { for (i, param) in params.iter().enumerate() {
let offset = env let offset = env.define_var(
.define_var(param.var_name.lexeme.clone(), param.var_type.lexeme.clone()); param.var_name.lexeme.clone(),
param.var_type.lexeme.clone(),
);
if let Some(reg) = REGISTERS.get(i) { if let Some(reg) = REGISTERS.get(i) {
emit!(&mut self.output, " mov QWORD [rbp-{}], {}", offset, reg); emit!(&mut self.output, " mov QWORD [rbp-{}], {}", offset, reg);
} else { } else {
@@ -332,6 +321,18 @@ _builtin_environ:
emit!(&mut self.output, " mov QWORD [rbp-{}], rax", offset); emit!(&mut self.output, " mov QWORD [rbp-{}], rax", offset);
} }
} }
}
Params::Variadic => {
emit!(&mut self.output, " sub rsp, 48");
emit!(&mut self.output, " mov [rbp - 8], rdi");
emit!(&mut self.output, " mov [rbp - 16], rsi");
emit!(&mut self.output, " mov [rbp - 24], rdx");
emit!(&mut self.output, " mov [rbp - 32], rcx");
emit!(&mut self.output, " mov [rbp - 40], r8");
emit!(&mut self.output, " mov [rbp - 48], r9");
env.next_offset += 48;
}
}
self.compile_stmt(env, body)?; self.compile_stmt(env, body)?;
@@ -619,6 +620,12 @@ _builtin_environ:
paren: _, paren: _,
args, args,
} => { } => {
if let Expr::Variable(callee_name) = &**callee
&& callee_name.lexeme == "_var_arg"
{
return self.emit_var_arg(env, &args[0]);
}
for arg in args { for arg in args {
self.compile_expr(env, arg)?; self.compile_expr(env, arg)?;
emit!(&mut self.output, " push rax"); emit!(&mut self.output, " push rax");
@@ -732,7 +739,6 @@ _builtin_environ:
Expr::New(struct_name) => { Expr::New(struct_name) => {
let struct_fields = &self.symbol_table.structs[&struct_name.lexeme]; let struct_fields = &self.symbol_table.structs[&struct_name.lexeme];
// TODO: panic on mem.alloc error
let memory_size = struct_fields.len() * 8; let memory_size = struct_fields.len() * 8;
emit!(&mut self.output, " mov rdi, {}", memory_size); emit!(&mut self.output, " mov rdi, {}", memory_size);
emit!(&mut self.output, " call mem.alloc"); emit!(&mut self.output, " call mem.alloc");
@@ -789,4 +795,33 @@ _builtin_environ:
Ok(field.offset) Ok(field.offset)
} }
fn emit_var_arg(&mut self, env: &mut Env, index_expr: &Expr) -> Result<(), ZernError> {
self.compile_expr(env, index_expr)?;
emit!(&mut self.output, " mov r10, rax");
let stack_label = self.label();
let done_label = self.label();
emit!(&mut self.output, " cmp r10, 6");
emit!(&mut self.output, " jge {}", stack_label);
// < 6
emit!(&mut self.output, " mov rax, r10");
emit!(&mut self.output, " inc rax");
emit!(&mut self.output, " shl rax, 3");
emit!(&mut self.output, " neg rax");
emit!(&mut self.output, " mov rax, [rbp + rax]");
emit!(&mut self.output, " jmp {}", done_label);
// >= 6
emit!(&mut self.output, "{}:", stack_label);
emit!(&mut self.output, " mov rax, r10");
emit!(&mut self.output, " sub rax, 6");
emit!(&mut self.output, " shl rax, 3");
emit!(&mut self.output, " mov rax, [rbp + 16 + rax]");
emit!(&mut self.output, "{}:", done_label);
Ok(())
}
} }

View File

@@ -6,6 +6,12 @@ pub struct Param {
pub var_name: Token, pub var_name: Token,
} }
#[derive(Debug, Clone)]
pub enum Params {
Normal(Vec<Param>),
Variadic,
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Stmt { pub enum Stmt {
Expression(Expr), Expression(Expr),
@@ -38,7 +44,7 @@ pub enum Stmt {
}, },
Function { Function {
name: Token, name: Token,
params: Vec<Param>, params: Params,
return_type: Token, return_type: Token,
body: Box<Stmt>, body: Box<Stmt>,
exported: bool, exported: bool,
@@ -164,13 +170,19 @@ impl Parser {
let name = self.consume(TokenType::Identifier, "expected function name")?; let name = self.consume(TokenType::Identifier, "expected function name")?;
self.consume(TokenType::LeftBracket, "expected '[' after function name")?; self.consume(TokenType::LeftBracket, "expected '[' after function name")?;
let mut is_variadic = false;
let mut params = vec![]; let mut params = vec![];
if !self.check(&TokenType::RightBracket) { if !self.check(&TokenType::RightBracket) {
if self.match_token(&[TokenType::DoubleDot]) {
is_variadic = true;
} else {
loop { loop {
let var_name = self.consume(TokenType::Identifier, "expected parameter name")?; let var_name =
self.consume(TokenType::Identifier, "expected parameter name")?;
self.consume(TokenType::Colon, "expected ':' after parameter name")?; self.consume(TokenType::Colon, "expected ':' after parameter name")?;
let var_type = self.consume(TokenType::Identifier, "expected parameter type")?; let var_type =
self.consume(TokenType::Identifier, "expected parameter type")?;
params.push(Param { var_type, var_name }); params.push(Param { var_type, var_name });
if !self.match_token(&[TokenType::Comma]) { if !self.match_token(&[TokenType::Comma]) {
@@ -178,6 +190,7 @@ impl Parser {
} }
} }
} }
}
self.consume(TokenType::RightBracket, "expected ']' after arguments")?; self.consume(TokenType::RightBracket, "expected ']' after arguments")?;
self.consume(TokenType::Colon, "expected ':' after '['")?; self.consume(TokenType::Colon, "expected ':' after '['")?;
@@ -189,7 +202,11 @@ impl Parser {
Ok(Stmt::Function { Ok(Stmt::Function {
name, name,
params, params: if is_variadic {
Params::Variadic
} else {
Params::Normal(params)
},
return_type, return_type,
body, body,
exported, exported,

View File

@@ -5,6 +5,7 @@ const ERR_READ_FAILED = 3
const ERR_WRITE_FAILED = 4 const ERR_WRITE_FAILED = 4
const ERR_ALLOC_FAILED = 5 const ERR_ALLOC_FAILED = 5
const ERR_CONNECTION_FAILED = 6 const ERR_CONNECTION_FAILED = 6
const ERR_KEY_ERROR = 7
func err.code[] : i64 func err.code[] : i64
return mem.read64(_builtin_err_code()) return mem.read64(_builtin_err_code())
@@ -263,24 +264,26 @@ func mem.write16be[x: ptr, d: i64] : void
func mem.write64[x: ptr, d: any] : void func mem.write64[x: ptr, d: any] : void
_builtin_set64(x, d) _builtin_set64(x, d)
// see emit_prologue for the io.printf wrapper func io.printf[..] : void
func io._printf_impl[s: ptr, args: ptr] : void let s: ptr = _var_arg(0) as ptr
let i: i64 = 1
while s[0] while s[0]
if s[0] == '%' if s[0] == '%'
s = s + 1 s = s + 1
if s[0] == 'd' if s[0] == 'd'
io.print_i64(mem.read64(args)) io.print_i64(_var_arg(i))
args = args + 8 i = i + 1
else if s[0] == 'x' else if s[0] == 'x'
io.print_i64_hex(mem.read64(args)) io.print_i64_hex(_var_arg(i))
args = args + 8 i = i + 1
else if s[0] == 's' else if s[0] == 's'
io.print(mem.read64(args) as str) io.print(_var_arg(i) as str)
args = args + 8 i = i + 1
else if s[0] == 'c' else if s[0] == 'c'
io.print_char(mem.read64(args) as u8) io.print_char(_var_arg(i) as u8)
args = args + 8 i = i + 1
else if s[0] == '%' else if s[0] == '%'
io.print_char('%') io.print_char('%')
else if s[0] == 0 else if s[0] == 0
@@ -846,6 +849,89 @@ func array.concat[a: Array, b: Array] : Array
array.set(new_array, a->size + i, array.nth(b, i)) array.set(new_array, a->size + i, array.nth(b, i))
return new_array return new_array
const HashMap._TABLE_SIZE = 100
struct HashMap
table: Array
struct HashMap._Node
key: str
value: any
next: HashMap._Node
func HashMap.new[] : HashMap
let map: HashMap = new HashMap
map->table = array.new_preallocated_and_zeroed(HashMap._TABLE_SIZE)
return map
func HashMap.insert[map: HashMap, key: str, value: any] : void
let index: i64 = HashMap._djb2(key)
let current: HashMap._Node = array.nth(map->table, index)
while current as ptr
if str.equal(current->key, key)
current->value = value
return 0
current = current->next
let new_node: HashMap._Node = new HashMap._Node
new_node->key = str.make_copy(key)
new_node->value = value
new_node->next = array.nth(map->table, index)
array.set(map->table, index, new_node)
func HashMap.get?[map: HashMap, key: str] : any
err.clear()
let index: i64 = HashMap._djb2(key)
let current: HashMap._Node = array.nth(map->table, index)
while current as ptr
if str.equal(current->key, key)
return current->value
current = current->next
err.set(ERR_KEY_ERROR, "key not found")
return 0
func HashMap.delete[map: HashMap, key: str] : void
let index: i64 = HashMap._djb2(key)
let current: HashMap._Node = array.nth(map->table, index)
let prev: HashMap._Node = 0 as HashMap._Node
while current as ptr
if str.equal(current->key, key)
if prev as ptr
prev->next = current->next
else
array.set(map->table, index, current->next)
mem.free(current->key)
mem.free(current)
return 0
prev = current
current = current->next
func HashMap._djb2[key: str] : i64
let hash: i64 = 5381
let key_len: i64 = str.len(key)
for i in 0..key_len
hash = ((hash << 5) + hash) + key[i]
hash = (hash & 0x7fffffffffffffff) // prevent negative
return hash % HashMap._TABLE_SIZE
func HashMap.free[map: HashMap] : void
for i in 0..HashMap._TABLE_SIZE
let current: HashMap._Node = array.nth(map->table, i)
while current as ptr
let tmp: HashMap._Node = current
current = current->next
mem.free(tmp->key)
mem.free(tmp)
array.free(map->table)
mem.free(map)
func alg.quicksort[arr: Array] : void func alg.quicksort[arr: Array] : void
alg._do_quicksort(arr, 0, arr->size - 1) alg._do_quicksort(arr, 0, arr->size - 1)
@@ -923,7 +1009,7 @@ func os.urandom?[n: i64]: ptr
let fd: i64 = _builtin_syscall(SYS_openat, -100, "/dev/urandom", 0, 0) let fd: i64 = _builtin_syscall(SYS_openat, -100, "/dev/urandom", 0, 0)
if fd < 0 if fd < 0
mem.free(buffer) mem.free(buffer)
err.set(ERR_READ_FAILED, "os.urandom?: failed to open /dev/urandom") err.set(ERR_OPEN_FAILED, "os.urandom?: failed to open /dev/urandom")
return 0 as ptr return 0 as ptr
let bytes_read: i64 = _builtin_syscall(SYS_read, fd, buffer, n) let bytes_read: i64 = _builtin_syscall(SYS_read, fd, buffer, n)

View File

@@ -1,7 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::{ use crate::{
parser::Stmt, parser::{Params, Stmt},
tokenizer::{ZernError, error}, tokenizer::{ZernError, error},
}; };
@@ -54,8 +54,8 @@ impl SymbolTable {
FnType::new("void", vec!["ptr", "i64"]), FnType::new("void", vec!["ptr", "i64"]),
), ),
("_builtin_syscall".into(), FnType::new_variadic("i64")), ("_builtin_syscall".into(), FnType::new_variadic("i64")),
("io.printf".into(), FnType::new_variadic("void")),
("_builtin_environ".into(), FnType::new("ptr", vec![])), ("_builtin_environ".into(), FnType::new("ptr", vec![])),
("_var_arg".into(), FnType::new("any", vec!["i64"])),
]), ]),
constants: HashMap::new(), constants: HashMap::new(),
structs: HashMap::new(), structs: HashMap::new(),
@@ -66,10 +66,7 @@ impl SymbolTable {
match stmt { match stmt {
Stmt::Const { name, value } => { Stmt::Const { name, value } => {
if self.is_name_defined(&name.lexeme) { if self.is_name_defined(&name.lexeme) {
return error!( return error!(name.loc, format!("tried to redefine '{}'", name.lexeme));
name.loc,
format!("tried to redefine constant '{}'", name.lexeme)
);
} }
if value.lexeme.starts_with("0x") { if value.lexeme.starts_with("0x") {
self.constants.insert( self.constants.insert(
@@ -103,13 +100,24 @@ impl SymbolTable {
if self.is_name_defined(&name.lexeme) { if self.is_name_defined(&name.lexeme) {
return error!(name.loc, format!("tried to redefine '{}'", name.lexeme)); return error!(name.loc, format!("tried to redefine '{}'", name.lexeme));
} }
self.functions.insert( match params {
Params::Normal(params) => self.functions.insert(
name.lexeme.clone(), name.lexeme.clone(),
FnType { FnType {
return_type: return_type.lexeme.clone(), return_type: return_type.lexeme.clone(),
params: Some(params.iter().map(|x| x.var_type.lexeme.clone()).collect()), params: Some(
params.iter().map(|x| x.var_type.lexeme.clone()).collect(),
),
}, },
); ),
Params::Variadic => self.functions.insert(
name.lexeme.clone(),
FnType {
return_type: return_type.lexeme.clone(),
params: None,
},
),
};
} }
Stmt::Struct { name, fields } => { Stmt::Struct { name, fields } => {
if self.is_name_defined(&name.lexeme) { if self.is_name_defined(&name.lexeme) {

View File

@@ -1,7 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::{ use crate::{
parser::{Expr, Stmt}, parser::{Expr, Params, Stmt},
symbol_table::{SymbolTable, Type}, symbol_table::{SymbolTable, Type},
tokenizer::{TokenType, ZernError, error}, tokenizer::{TokenType, ZernError, error},
}; };
@@ -166,8 +166,39 @@ impl<'a> TypeChecker<'a> {
body, body,
exported: _, exported: _,
} => { } => {
if name.lexeme == "main" && return_type.lexeme != "i64" { if name.lexeme == "main" {
return error!(&name.loc, "main must return i64"); if return_type.lexeme != "i64" {
return error!(&name.loc, "main function must return i64");
}
match params {
Params::Normal(params) => {
if !params.is_empty() && params.len() != 2 {
return error!(
&name.loc,
"main function must accept 0 or 2 parameters"
);
}
if params.len() == 2 {
if params[0].var_type.lexeme != "i64" {
return error!(
&name.loc,
"first parameter of the main function must be a i64"
);
}
if params[1].var_type.lexeme != "ptr" {
return error!(
&name.loc,
"second parameter of the main function must be a ptr"
);
}
}
}
Params::Variadic => {
return error!(&name.loc, "main function cannot be variadic");
}
}
} }
if !self.is_valid_type_name(&return_type.lexeme) { if !self.is_valid_type_name(&return_type.lexeme) {
@@ -181,6 +212,8 @@ impl<'a> TypeChecker<'a> {
env.push_scope(); env.push_scope();
match params {
Params::Normal(params) => {
for param in params { for param in params {
if !self.is_valid_type_name(&param.var_type.lexeme) { if !self.is_valid_type_name(&param.var_type.lexeme) {
return error!( return error!(
@@ -189,7 +222,13 @@ impl<'a> TypeChecker<'a> {
); );
} }
env.define_var(param.var_name.lexeme.clone(), param.var_type.lexeme.clone()); env.define_var(
param.var_name.lexeme.clone(),
param.var_type.lexeme.clone(),
);
}
}
Params::Variadic => {}
} }
self.typecheck_stmt(env, body)?; self.typecheck_stmt(env, body)?;

21
test.zr
View File

@@ -4,22 +4,17 @@ func run_test[x: str] : void
for i in 0..build_blacklist->size for i in 0..build_blacklist->size
if str.equal(x, array.nth(build_blacklist, i)) if str.equal(x, array.nth(build_blacklist, i))
io.print("Skipping ") io.printf("Skipping %s...\n", x)
io.print(x)
io.println("...")
return 0 return 0
io.print("Building ") io.printf("Building %s...\n", x)
io.print(x)
io.print("... ")
let build_start_time: i64 = os.time() let build_start_time: i64 = os.time()
if must(os.run_shell_command?(str.concat("./target/release/zern ", x))) != 0 if must(os.run_shell_command?(str.concat("./target/release/zern ", x))) != 0
os.exit(1) os.exit(1)
let build_end_time: i64 = os.time() let build_end_time: i64 = os.time()
io.print_i64(build_end_time - build_start_time) io.printf("%dms\n", build_end_time - build_start_time)
io.println("ms")
for i in 0..run_blacklist->size for i in 0..run_blacklist->size
if str.find(x, array.nth(run_blacklist, i)) != -1 if str.find(x, array.nth(run_blacklist, i)) != -1
@@ -28,9 +23,7 @@ func run_test[x: str] : void
io.println("...") io.println("...")
return 0 return 0
io.print("Running ") io.printf("Running %s...\n", x)
io.print(x)
io.println("...")
let run_cmd: str = "./out" let run_cmd: str = "./out"
if str.equal(x, "examples/curl.zr") if str.equal(x, "examples/curl.zr")
@@ -43,11 +36,7 @@ func run_test[x: str] : void
os.exit(1) os.exit(1)
let run_end_time: i64 = os.time() let run_end_time: i64 = os.time()
io.print("Running ") io.printf("Running %s took %dms\n", x, run_end_time - run_start_time)
io.print(x)
io.print(" took ")
io.print_i64(run_end_time - run_start_time)
io.println("ms")
func run_directory[dir: str] : void func run_directory[dir: str] : void
let files: Array = must(os.list_directory?(dir)) let files: Array = must(os.list_directory?(dir))