Compare commits

..

2 Commits

Author SHA1 Message Date
3c5a29633b float literals 2026-04-11 23:12:25 +02:00
3fd4fcd062 hex improvements 2026-04-09 15:01:36 +02:00
10 changed files with 104 additions and 69 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, variadics, dynamic arrays, hashmaps, etc. * Has the pipe operator, variadics, dynamic arrays, hashmaps, DNS resolver, etc.
## Syntax ## Syntax
```rust ```rust
@@ -45,5 +45,5 @@ func main[] : i64
## Quickstart ## Quickstart
``` ```
cargo install --git https://github.com/antpiasecki/zern cargo install --git https://github.com/antpiasecki/zern
zern -m -r hello.zr zern -r hello.zr
``` ```

View File

@@ -97,7 +97,7 @@ impl<'a> CodegenX86_64<'a> {
format!("section .data\n{}{}", self.data_section, self.output) format!("section .data\n{}{}", self.data_section, self.output)
} }
pub fn emit_prologue(&mut self, use_gcc: bool) -> Result<(), ZernError> { pub fn emit_prologue(&mut self, use_crt: bool) -> Result<(), ZernError> {
emit!( emit!(
&mut self.output, &mut self.output,
"section .note.GNU-stack "section .note.GNU-stack
@@ -151,10 +151,16 @@ _builtin_syscall:
mov r9, [rsp+8] mov r9, [rsp+8]
syscall syscall
ret ret
section .text._builtin_cvttsd2si
_builtin_cvttsd2si:
movq xmm0, rdi
cvttsd2si rax, xmm0
ret
" "
); );
if !use_gcc { if !use_crt {
emit!( emit!(
&mut self.output, &mut self.output,
" "
@@ -220,7 +226,7 @@ _builtin_environ:
Some(t) => t.lexeme.clone(), Some(t) => t.lexeme.clone(),
None => match &initializer { None => match &initializer {
Expr::Literal(token) => { Expr::Literal(token) => {
if token.token_type == TokenType::Number { if token.token_type == TokenType::IntLiteral {
"i64".into() "i64".into()
} else { } else {
return error!(&name.loc, "unable to infer variable type"); return error!(&name.loc, "unable to infer variable type");
@@ -504,17 +510,27 @@ _builtin_environ:
} }
Expr::Grouping(expr) => self.compile_expr(env, expr)?, Expr::Grouping(expr) => self.compile_expr(env, expr)?,
Expr::Literal(token) => match token.token_type { Expr::Literal(token) => match token.token_type {
TokenType::Number => { TokenType::IntLiteral => {
emit!(&mut self.output, " mov rax, {}", token.lexeme); emit!(&mut self.output, " mov rax, {}", token.lexeme);
} }
TokenType::Char => { TokenType::FloatLiteral => {
let value = token.lexeme.parse::<f64>().unwrap();
let bits = value.to_bits();
let high = (bits >> 32) as u32;
let low = bits as u32;
emit!(&mut self.output, " mov eax, {}", high);
emit!(&mut self.output, " shl rax, 32");
emit!(&mut self.output, " mov ebx, {}", low);
emit!(&mut self.output, " or rax, rbx");
}
TokenType::CharLiteral => {
emit!( emit!(
&mut self.output, &mut self.output,
" mov rax, {}", " mov rax, {}",
token.lexeme.chars().nth(1).unwrap() as u8 token.lexeme.chars().nth(1).unwrap() as u8
); );
} }
TokenType::String => { TokenType::StringLiteral => {
// TODO: actual string parsing in the tokenizer // TODO: actual string parsing in the tokenizer
let value = &token.lexeme[1..token.lexeme.len() - 1]; let value = &token.lexeme[1..token.lexeme.len() - 1];

View File

@@ -34,9 +34,11 @@ fn compile_file(args: Args) -> Result<(), ZernError> {
let mut statements = Vec::new(); let mut statements = Vec::new();
if args.include_stdlib {
parse_std_file!(statements, "std/syscalls.zr"); parse_std_file!(statements, "std/syscalls.zr");
parse_std_file!(statements, "std/std.zr"); parse_std_file!(statements, "std/std.zr");
parse_std_file!(statements, "std/net.zr"); parse_std_file!(statements, "std/net.zr");
}
let tokenizer = tokenizer::Tokenizer::new(filename.to_owned(), source); let tokenizer = tokenizer::Tokenizer::new(filename.to_owned(), source);
let parser = parser::Parser::new(tokenizer.tokenize()?); let parser = parser::Parser::new(tokenizer.tokenize()?);
@@ -53,21 +55,21 @@ fn compile_file(args: Args) -> Result<(), ZernError> {
} }
let mut codegen = codegen_x86_64::CodegenX86_64::new(&symbol_table); let mut codegen = codegen_x86_64::CodegenX86_64::new(&symbol_table);
codegen.emit_prologue(args.use_gcc)?; codegen.emit_prologue(args.use_crt)?;
for stmt in statements { for stmt in statements {
codegen.compile_stmt(&mut codegen_x86_64::Env::new(), &stmt)?; codegen.compile_stmt(&mut codegen_x86_64::Env::new(), &stmt)?;
} }
if !args.output_asm { if !args.emit_only {
let out = args.out.unwrap_or_else(|| "out".into()); let out = args.out.unwrap_or_else(|| "out".into());
fs::write(format!("{}.s", out), codegen.get_output()).unwrap(); fs::write(format!("{}.s", out), codegen.get_output()).unwrap();
run_command(format!("nasm -f elf64 -o {}.o {}.s", out, out)); run_command(format!("nasm -f elf64 -o {}.o {}.s", out, out));
if args.use_gcc { if args.use_crt {
run_command(format!( run_command(format!(
"gcc -no-pie -o {} {}.o -flto -Wl,--gc-sections {}", "cc -no-pie -o {} {}.o -flto -Wl,--gc-sections {}",
out, out, args.cflags out, out, args.cflags
)); ));
} else { } else {
@@ -110,9 +112,10 @@ fn run_command(cmd: String) {
struct Args { struct Args {
path: String, path: String,
out: Option<String>, out: Option<String>,
output_asm: bool, emit_only: bool,
include_stdlib: bool,
run_exe: bool, run_exe: bool,
use_gcc: bool, use_crt: bool,
cflags: String, cflags: String,
} }
@@ -121,9 +124,10 @@ impl Args {
let mut out = Args { let mut out = Args {
path: String::new(), path: String::new(),
out: None, out: None,
output_asm: false, emit_only: false,
include_stdlib: true,
run_exe: false, run_exe: false,
use_gcc: false, use_crt: false,
cflags: String::new(), cflags: String::new(),
}; };
@@ -132,26 +136,30 @@ impl Args {
match args.next() { match args.next() {
Some(s) => out.out = Some(s), Some(s) => out.out = Some(s),
None => { None => {
eprintln!("\x1b[91mERROR\x1b[0m: -o option requires a name"); eprintln!("\x1b[91mERROR\x1b[0m: -o option requires a path");
process::exit(1); process::exit(1);
} }
} }
} else if arg == "-S" { } else if arg == "--emit-only" {
out.output_asm = true; out.emit_only = true;
} else if arg == "--no-stdlib" {
out.include_stdlib = false;
} else if arg == "-r" { } else if arg == "-r" {
out.run_exe = true; out.run_exe = true;
} else if arg == "-m" { } else if arg == "-m" {
out.use_gcc = true; out.use_crt = true;
} else if arg == "-C" { } else if arg == "-C" {
match args.next() { match args.next() {
Some(s) => out.cflags = s, Some(s) => out.cflags = s,
None => { None => {
eprintln!("\x1b[91mERROR\x1b[0m: -C option requires a name"); eprintln!("\x1b[91mERROR\x1b[0m: -C option requires a value");
process::exit(1); process::exit(1);
} }
} }
} else if arg == "-h" || arg == "--help" { } else if arg == "-h" || arg == "--help" {
println!("Usage: zern [-o path] [-S] [-r] [-m] [-C cflags] path"); println!(
"Usage: zern [-o path] [-r] [-m] [-C cflags] [--emit-only] [--no-stdlib] path"
);
process::exit(0); process::exit(0);
} else if arg.starts_with('-') { } else if arg.starts_with('-') {
eprintln!("\x1b[91mERROR\x1b[0m: unrecognized option: {}", arg); eprintln!("\x1b[91mERROR\x1b[0m: unrecognized option: {}", arg);
@@ -169,9 +177,8 @@ impl Args {
process::exit(1); process::exit(1);
} }
if !out.use_gcc && !out.cflags.is_empty() { if !out.use_crt && !out.cflags.is_empty() {
// no "ERROR:" since its not an error eprintln!("You can't set CFLAGS if you're not using the C runtime. Add the -m flag.");
eprintln!("You can't set CFLAGS if you're not using gcc. Add the -m flag.");
process::exit(1); process::exit(1);
} }

View File

@@ -255,7 +255,7 @@ impl Parser {
fn const_declaration(&mut self) -> Result<Stmt, ZernError> { fn const_declaration(&mut self) -> Result<Stmt, ZernError> {
let name = self.consume(TokenType::Identifier, "expected const name")?; let name = self.consume(TokenType::Identifier, "expected const name")?;
self.consume(TokenType::Equal, "expected '=' after const name")?; self.consume(TokenType::Equal, "expected '=' after const name")?;
let value = self.consume(TokenType::Number, "expected a number after '='")?; let value = self.consume(TokenType::IntLiteral, "expected a number after '='")?;
Ok(Stmt::Const { name, value }) Ok(Stmt::Const { name, value })
} }
@@ -576,9 +576,10 @@ impl Parser {
fn primary(&mut self) -> Result<Expr, ZernError> { fn primary(&mut self) -> Result<Expr, ZernError> {
if self.match_token(&[ if self.match_token(&[
TokenType::Number, TokenType::IntLiteral,
TokenType::Char, TokenType::CharLiteral,
TokenType::String, TokenType::StringLiteral,
TokenType::FloatLiteral,
TokenType::True, TokenType::True,
TokenType::False, TokenType::False,
]) { ]) {

View File

@@ -201,7 +201,7 @@ func net.resolve?[domain: str] : i64
let query: io.Buffer = net.build_dns_query(domain, DNS_TYPE_A) let query: io.Buffer = net.build_dns_query(domain, DNS_TYPE_A)
// TODO: this probably shouldnt be hardcoded // TODO: this probably shouldnt be hardcoded
let s: net.UDPSocket = net.create_udp_client?(net.pack_addr(8, 8, 8, 8), 53) let s: net.UDPSocket = net.create_udp_client?(net.pack_addr(1, 1, 1, 1), 53)
if err.check() if err.check()
return -1 return -1

View File

@@ -273,10 +273,10 @@ func io.printf[..] : void
s = s + 1 s = s + 1
if s[0] == 'd' if s[0] == 'd'
io.print_i64(_var_arg(i)) io.print_i64(_var_arg(i) as i64)
i = i + 1 i = i + 1
else if s[0] == 'x' else if s[0] == 'x'
io.print_i64_hex(_var_arg(i)) io.print_i64_hex(_var_arg(i) as i64)
i = i + 1 i = i + 1
else if s[0] == 's' else if s[0] == 's'
io.print(_var_arg(i) as str) io.print(_var_arg(i) as str)
@@ -670,41 +670,38 @@ func str.parse_i64[s: str] : i64
func str.hex_encode[s: str, s_len: i64] : str func str.hex_encode[s: str, s_len: i64] : str
let hex_chars: str = "0123456789abcdef" let hex_chars: str = "0123456789abcdef"
let j = 0
let out: str = mem.alloc(s_len * 2 + 1) as str let out: str = mem.alloc(s_len * 2 + 1) as str
for i in 0..s_len for i in 0..s_len
let high: u8 = (s[i] >> 4) & 15 let b: u8 = s[i]
let low: u8 = s[i] & 15 out[i * 2] = hex_chars[(b >> 4) & 15]
out[j] = hex_chars[high] out[i * 2 + 1] = hex_chars[b & 15]
out[j + 1] = hex_chars[low]
j = j + 2
out[j] = 0 out[s_len * 2] = 0
return out return out
func str._hex_digit_to_int[d: u8] : u8 func str._hex_digit_to_int[d: u8] : u8
if d >= 'a' && d <= 'f' if d >= '0' && d <= '9'
return d - 'a' + 10
if d >= 'A' && d <= 'F'
return d - 'A' + 10
return d - '0' return d - '0'
let lower: u8 = d | 32
if lower >= 'a' && lower <= 'f'
return lower - 'a' + 10
panic("invalid hex digit passed to str._hex_digit_to_int")
func str.hex_decode[s: str] : str func str.hex_decode[s: str] : str
let s_len: i64 = str.len(s) let s_len: i64 = str.len(s)
if s_len % 2 != 0 if s_len % 2 != 0
panic("invalid hex string passed to str.hex_decode") panic("invalid hex string passed to str.hex_decode")
let i = 0 let out_len: i64 = s_len / 2
let j = 0 let out: str = mem.alloc(out_len + 1) as str
let out: str = mem.alloc(s_len / 2 + 1) as str
while i < s_len for i in 0..out_len
out[j] = str._hex_digit_to_int(s[i]) * 16 + str._hex_digit_to_int(s[i + 1]) let high: u8 = str._hex_digit_to_int(s[i * 2])
i = i + 2 let low: u8 = str._hex_digit_to_int(s[i * 2 + 1])
j = j + 1 out[i] = (high << 4) | low
out[j] = 0 out[out_len] = 0
return out return out
func math.gcd[a: i64, b: i64] : i64 func math.gcd[a: i64, b: i64] : i64

View File

@@ -53,6 +53,7 @@ impl SymbolTable {
"_builtin_set64".into(), "_builtin_set64".into(),
FnType::new("void", vec!["ptr", "i64"]), FnType::new("void", vec!["ptr", "i64"]),
), ),
("_builtin_cvttsd2si".into(), FnType::new("i64", vec!["f64"])),
("_builtin_syscall".into(), FnType::new_variadic("i64")), ("_builtin_syscall".into(), FnType::new_variadic("i64")),
("_builtin_environ".into(), FnType::new("ptr", vec![])), ("_builtin_environ".into(), FnType::new("ptr", vec![])),
("_var_arg".into(), FnType::new("any", vec!["i64"])), ("_var_arg".into(), FnType::new("any", vec!["i64"])),

View File

@@ -34,9 +34,10 @@ pub enum TokenType {
LessEqual, LessEqual,
Identifier, Identifier,
String, StringLiteral,
Char, CharLiteral,
Number, IntLiteral,
FloatLiteral,
True, True,
False, False,
@@ -240,7 +241,7 @@ impl Tokenizer {
if !self.match_char('\'') { if !self.match_char('\'') {
return error!(self.loc, "expected ' after char literal"); return error!(self.loc, "expected ' after char literal");
} }
self.add_token(TokenType::Char)? self.add_token(TokenType::CharLiteral)?
} }
'"' => { '"' => {
let start_loc = self.loc.clone(); let start_loc = self.loc.clone();
@@ -265,7 +266,7 @@ impl Tokenizer {
} }
self.advance(); self.advance();
self.add_token(TokenType::String)? self.add_token(TokenType::StringLiteral)?
} }
' ' | '\r' => {} ' ' | '\r' => {}
'\n' => { '\n' => {
@@ -333,17 +334,28 @@ impl Tokenizer {
while self.peek().is_ascii_hexdigit() { while self.peek().is_ascii_hexdigit() {
self.advance(); self.advance();
} }
self.add_token(TokenType::IntLiteral)
} else if self.match_char('o') { } else if self.match_char('o') {
while matches!(self.peek(), '0'..='7') { while matches!(self.peek(), '0'..='7') {
self.advance(); self.advance();
} }
self.add_token(TokenType::IntLiteral)
} else { } else {
while self.peek().is_ascii_digit() { while self.peek().is_ascii_digit() {
self.advance(); self.advance();
} }
if self.current + 2 < self.source.len()
&& self.source[self.current + 1].is_ascii_digit()
&& self.match_char('.')
{
while self.peek().is_ascii_digit() {
self.advance();
}
self.add_token(TokenType::FloatLiteral)
} else {
self.add_token(TokenType::IntLiteral)
}
} }
self.add_token(TokenType::Number)
} }
fn scan_identifier(&mut self) -> Result<(), ZernError> { fn scan_identifier(&mut self) -> Result<(), ZernError> {
@@ -392,7 +404,7 @@ impl Tokenizer {
fn add_token(&mut self, token_type: TokenType) -> Result<(), ZernError> { fn add_token(&mut self, token_type: TokenType) -> Result<(), ZernError> {
let mut lexeme: String = self.source[self.start..self.current].iter().collect(); let mut lexeme: String = self.source[self.start..self.current].iter().collect();
if token_type == TokenType::Char || token_type == TokenType::String { if token_type == TokenType::CharLiteral || token_type == TokenType::StringLiteral {
lexeme = self.unescape(&lexeme)?; lexeme = self.unescape(&lexeme)?;
} }

View File

@@ -33,7 +33,9 @@ macro_rules! expect_types {
}; };
} }
static BUILTIN_TYPES: [&str; 8] = ["void", "u8", "i64", "str", "bool", "ptr", "fnptr", "any"]; static BUILTIN_TYPES: [&str; 9] = [
"void", "u8", "i64", "f64", "str", "bool", "ptr", "fnptr", "any",
];
pub struct Env { pub struct Env {
scopes: Vec<HashMap<String, Type>>, scopes: Vec<HashMap<String, Type>>,
@@ -321,9 +323,10 @@ impl<'a> TypeChecker<'a> {
} }
Expr::Grouping(expr) => self.typecheck_expr(env, expr), Expr::Grouping(expr) => self.typecheck_expr(env, expr),
Expr::Literal(token) => match token.token_type { Expr::Literal(token) => match token.token_type {
TokenType::Number => Ok("i64".into()), TokenType::IntLiteral => Ok("i64".into()),
TokenType::Char => Ok("u8".into()), TokenType::CharLiteral => Ok("u8".into()),
TokenType::String => Ok("str".into()), TokenType::StringLiteral => Ok("str".into()),
TokenType::FloatLiteral => Ok("f64".into()),
TokenType::True => Ok("bool".into()), TokenType::True => Ok("bool".into()),
TokenType::False => Ok("bool".into()), TokenType::False => Ok("bool".into()),
_ => unreachable!(), _ => unreachable!(),

View File

@@ -28,8 +28,6 @@ func run_test[x: str] : void
let run_cmd: str = "./out" let run_cmd: str = "./out"
if str.equal(x, "examples/curl.zr") if str.equal(x, "examples/curl.zr")
run_cmd = str.concat(run_cmd, " http://example.com") run_cmd = str.concat(run_cmd, " http://example.com")
else if str.equal(x, "examples/tokenizer.zr")
run_cmd = str.concat(run_cmd, " test.zr")
let run_start_time: i64 = os.time() let run_start_time: i64 = os.time()
if must(os.run_shell_command?(run_cmd)) != 0 if must(os.run_shell_command?(run_cmd)) != 0