diff --git a/README.md b/README.md
index 27427fc..34ea29d 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ A very cool language
* Growing [standard library](https://github.com/antpiasecki/zern/tree/main/src/std)
* Produces tiny static executables (11KB for `hello.zr`)
* No libc required!
-* Has the pipe operator, dynamic arrays, hashmaps
+* Has the pipe operator, variadics, dynamic arrays, hashmaps, etc.
## Syntax
```rust
diff --git a/src/codegen_x86_64.rs b/src/codegen_x86_64.rs
index 1e9d601..21de96e 100644
--- a/src/codegen_x86_64.rs
+++ b/src/codegen_x86_64.rs
@@ -151,21 +151,6 @@ _builtin_syscall:
mov r9, [rsp+8]
syscall
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
"
);
@@ -337,7 +322,16 @@ _builtin_environ:
}
}
}
- Params::Variadic(_name) => todo!(),
+ 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)?;
@@ -626,6 +620,12 @@ _builtin_environ:
paren: _,
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 {
self.compile_expr(env, arg)?;
emit!(&mut self.output, " push rax");
@@ -795,4 +795,33 @@ _builtin_environ:
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(())
+ }
}
diff --git a/src/parser.rs b/src/parser.rs
index 14b0001..3c96b89 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -9,7 +9,7 @@ pub struct Param {
#[derive(Debug, Clone)]
pub enum Params {
Normal(Vec),
- Variadic(Token),
+ Variadic,
}
#[derive(Debug, Clone)]
@@ -170,12 +170,11 @@ impl Parser {
let name = self.consume(TokenType::Identifier, "expected function name")?;
self.consume(TokenType::LeftBracket, "expected '[' after function name")?;
- let mut variadic_param: Option = None;
+ let mut is_variadic = false;
let mut params = vec![];
if !self.check(&TokenType::RightBracket) {
if self.match_token(&[TokenType::DoubleDot]) {
- variadic_param =
- Some(self.consume(TokenType::Identifier, "expected variadic parameter name")?);
+ is_variadic = true;
} else {
loop {
let var_name =
@@ -201,13 +200,13 @@ impl Parser {
let body = Box::new(self.block()?);
self.is_inside_function = false;
- let params = match variadic_param {
- Some(name) => Params::Variadic(name),
- None => Params::Normal(params),
- };
Ok(Stmt::Function {
name,
- params,
+ params: if is_variadic {
+ Params::Variadic
+ } else {
+ Params::Normal(params)
+ },
return_type,
body,
exported,
diff --git a/src/std/std.zr b/src/std/std.zr
index 18b2088..3ce9cdf 100644
--- a/src/std/std.zr
+++ b/src/std/std.zr
@@ -264,24 +264,26 @@ func mem.write16be[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: ptr, args: ptr] : void
+func io.printf[..] : void
+ let s: ptr = _var_arg(0) as ptr
+ let i: i64 = 1
+
while s[0]
if s[0] == '%'
s = s + 1
if s[0] == 'd'
- io.print_i64(mem.read64(args))
- args = args + 8
+ io.print_i64(_var_arg(i))
+ i = i + 1
else if s[0] == 'x'
- io.print_i64_hex(mem.read64(args))
- args = args + 8
+ io.print_i64_hex(_var_arg(i))
+ i = i + 1
else if s[0] == 's'
- io.print(mem.read64(args) as str)
- args = args + 8
+ io.print(_var_arg(i) as str)
+ i = i + 1
else if s[0] == 'c'
- io.print_char(mem.read64(args) as u8)
- args = args + 8
+ io.print_char(_var_arg(i) as u8)
+ i = i + 1
else if s[0] == '%'
io.print_char('%')
else if s[0] == 0
diff --git a/src/symbol_table.rs b/src/symbol_table.rs
index 8887146..2f1abd0 100644
--- a/src/symbol_table.rs
+++ b/src/symbol_table.rs
@@ -54,8 +54,8 @@ impl SymbolTable {
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![])),
+ ("_var_arg".into(), FnType::new("any", vec!["i64"])),
]),
constants: HashMap::new(),
structs: HashMap::new(),
@@ -110,7 +110,7 @@ impl SymbolTable {
),
},
),
- Params::Variadic(name) => self.functions.insert(
+ Params::Variadic => self.functions.insert(
name.lexeme.clone(),
FnType {
return_type: return_type.lexeme.clone(),
diff --git a/src/typechecker.rs b/src/typechecker.rs
index f785b23..e9f9494 100644
--- a/src/typechecker.rs
+++ b/src/typechecker.rs
@@ -195,7 +195,7 @@ impl<'a> TypeChecker<'a> {
}
}
}
- Params::Variadic(_) => {
+ Params::Variadic => {
return error!(&name.loc, "main function cannot be variadic");
}
}
@@ -228,9 +228,7 @@ impl<'a> TypeChecker<'a> {
);
}
}
- Params::Variadic(name) => {
- env.define_var(name.lexeme.clone(), "ptr".into());
- }
+ Params::Variadic => {}
}
self.typecheck_stmt(env, body)?;
diff --git a/test.zr b/test.zr
index 9e8d7a2..de7f608 100644
--- a/test.zr
+++ b/test.zr
@@ -4,22 +4,17 @@ func run_test[x: str] : void
for i in 0..build_blacklist->size
if str.equal(x, array.nth(build_blacklist, i))
- io.print("Skipping ")
- io.print(x)
- io.println("...")
+ io.printf("Skipping %s...\n", x)
return 0
- io.print("Building ")
- io.print(x)
- io.print("... ")
+ io.printf("Building %s...\n", x)
let build_start_time: i64 = os.time()
if must(os.run_shell_command?(str.concat("./target/release/zern ", x))) != 0
os.exit(1)
let build_end_time: i64 = os.time()
- io.print_i64(build_end_time - build_start_time)
- io.println("ms")
+ io.printf("%dms\n", build_end_time - build_start_time)
for i in 0..run_blacklist->size
if str.find(x, array.nth(run_blacklist, i)) != -1
@@ -28,9 +23,7 @@ func run_test[x: str] : void
io.println("...")
return 0
- io.print("Running ")
- io.print(x)
- io.println("...")
+ io.printf("Running %s...\n", x)
let run_cmd: str = "./out"
if str.equal(x, "examples/curl.zr")
@@ -43,11 +36,7 @@ func run_test[x: str] : void
os.exit(1)
let run_end_time: i64 = os.time()
- io.print("Running ")
- io.print(x)
- io.print(" took ")
- io.print_i64(run_end_time - run_start_time)
- io.println("ms")
+ io.printf("Running %s took %dms\n", x, run_end_time - run_start_time)
func run_directory[dir: str] : void
let files: Array = must(os.list_directory?(dir))