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)
* Produces tiny static executables (11KB for `hello.zr`)
* No libc required!
* Has the pipe operator, variadics, dynamic arrays, hashmaps, etc.
* Has the pipe operator, variadics, dynamic arrays, hashmaps, DNS resolver, etc.
## Syntax
```rust
@@ -45,5 +45,5 @@ func main[] : i64
## Quickstart
```
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)
}
pub fn emit_prologue(&mut self, use_gcc: bool) -> Result<(), ZernError> {
pub fn emit_prologue(&mut self, use_crt: bool) -> Result<(), ZernError> {
emit!(
&mut self.output,
"section .note.GNU-stack
@@ -151,10 +151,16 @@ _builtin_syscall:
mov r9, [rsp+8]
syscall
ret
section .text._builtin_cvttsd2si
_builtin_cvttsd2si:
movq xmm0, rdi
cvttsd2si rax, xmm0
ret
"
);
if !use_gcc {
if !use_crt {
emit!(
&mut self.output,
"
@@ -220,7 +226,7 @@ _builtin_environ:
Some(t) => t.lexeme.clone(),
None => match &initializer {
Expr::Literal(token) => {
if token.token_type == TokenType::Number {
if token.token_type == TokenType::IntLiteral {
"i64".into()
} else {
return error!(&name.loc, "unable to infer variable type");
@@ -504,17 +510,27 @@ _builtin_environ:
}
Expr::Grouping(expr) => self.compile_expr(env, expr)?,
Expr::Literal(token) => match token.token_type {
TokenType::Number => {
TokenType::IntLiteral => {
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!(
&mut self.output,
" mov rax, {}",
token.lexeme.chars().nth(1).unwrap() as u8
);
}
TokenType::String => {
TokenType::StringLiteral => {
// TODO: actual string parsing in the tokenizer
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();
parse_std_file!(statements, "std/syscalls.zr");
parse_std_file!(statements, "std/std.zr");
parse_std_file!(statements, "std/net.zr");
if args.include_stdlib {
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()?);
@@ -53,21 +55,21 @@ fn compile_file(args: Args) -> Result<(), ZernError> {
}
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 {
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());
fs::write(format!("{}.s", out), codegen.get_output()).unwrap();
run_command(format!("nasm -f elf64 -o {}.o {}.s", out, out));
if args.use_gcc {
if args.use_crt {
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
));
} else {
@@ -110,9 +112,10 @@ fn run_command(cmd: String) {
struct Args {
path: String,
out: Option<String>,
output_asm: bool,
emit_only: bool,
include_stdlib: bool,
run_exe: bool,
use_gcc: bool,
use_crt: bool,
cflags: String,
}
@@ -121,9 +124,10 @@ impl Args {
let mut out = Args {
path: String::new(),
out: None,
output_asm: false,
emit_only: false,
include_stdlib: true,
run_exe: false,
use_gcc: false,
use_crt: false,
cflags: String::new(),
};
@@ -132,26 +136,30 @@ impl Args {
match args.next() {
Some(s) => out.out = Some(s),
None => {
eprintln!("\x1b[91mERROR\x1b[0m: -o option requires a name");
eprintln!("\x1b[91mERROR\x1b[0m: -o option requires a path");
process::exit(1);
}
}
} else if arg == "-S" {
out.output_asm = true;
} else if arg == "--emit-only" {
out.emit_only = true;
} else if arg == "--no-stdlib" {
out.include_stdlib = false;
} else if arg == "-r" {
out.run_exe = true;
} else if arg == "-m" {
out.use_gcc = true;
out.use_crt = true;
} else if arg == "-C" {
match args.next() {
Some(s) => out.cflags = s,
None => {
eprintln!("\x1b[91mERROR\x1b[0m: -C option requires a name");
eprintln!("\x1b[91mERROR\x1b[0m: -C option requires a value");
process::exit(1);
}
}
} 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);
} else if arg.starts_with('-') {
eprintln!("\x1b[91mERROR\x1b[0m: unrecognized option: {}", arg);
@@ -169,9 +177,8 @@ impl Args {
process::exit(1);
}
if !out.use_gcc && !out.cflags.is_empty() {
// no "ERROR:" since its not an error
eprintln!("You can't set CFLAGS if you're not using gcc. Add the -m flag.");
if !out.use_crt && !out.cflags.is_empty() {
eprintln!("You can't set CFLAGS if you're not using the C runtime. Add the -m flag.");
process::exit(1);
}

View File

@@ -255,7 +255,7 @@ impl Parser {
fn const_declaration(&mut self) -> Result<Stmt, ZernError> {
let name = self.consume(TokenType::Identifier, "expected 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 })
}
@@ -576,9 +576,10 @@ impl Parser {
fn primary(&mut self) -> Result<Expr, ZernError> {
if self.match_token(&[
TokenType::Number,
TokenType::Char,
TokenType::String,
TokenType::IntLiteral,
TokenType::CharLiteral,
TokenType::StringLiteral,
TokenType::FloatLiteral,
TokenType::True,
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)
// 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()
return -1

View File

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

View File

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

View File

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

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 {
scopes: Vec<HashMap<String, Type>>,
@@ -321,9 +323,10 @@ impl<'a> TypeChecker<'a> {
}
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::IntLiteral => Ok("i64".into()),
TokenType::CharLiteral => Ok("u8".into()),
TokenType::StringLiteral => Ok("str".into()),
TokenType::FloatLiteral => Ok("f64".into()),
TokenType::True => Ok("bool".into()),
TokenType::False => Ok("bool".into()),
_ => unreachable!(),

View File

@@ -28,8 +28,6 @@ func run_test[x: str] : void
let run_cmd: str = "./out"
if str.equal(x, "examples/curl.zr")
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()
if must(os.run_shell_command?(run_cmd)) != 0