Compare commits

...

5 Commits

Author SHA1 Message Date
3efbb3ecd1 io.is_a_directory, octal literal constants 2026-03-20 15:35:35 +01:00
efe9dfe238 parse cli on our own 2026-03-19 13:42:12 +01:00
8758295641 Analyzer -> SymbolTable 2026-03-19 13:18:24 +01:00
e24763dfb6 gut analyzer by moving the arity check to the typechecker 2026-03-19 13:16:34 +01:00
2d2a2c667f LICENSE 2026-03-19 13:14:50 +01:00
16 changed files with 364 additions and 641 deletions

237
Cargo.lock generated
View File

@@ -2,243 +2,6 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "anstream"
version = "0.6.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys",
]
[[package]]
name = "clap"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "once_cell_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "zern"
version = "0.3.0"
dependencies = [
"clap",
]

View File

@@ -2,6 +2,4 @@
name = "zern"
version = "0.3.0"
edition = "2024"
[dependencies]
clap = { version = "4.5.40", features = ["derive"] }
license = "BSD-2-Clause"

24
LICENSE Normal file
View File

@@ -0,0 +1,24 @@
BSD 2-Clause License
Copyright (c) 2025-2026, Antoni Piasecki
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,4 +1,4 @@
// needs to be compiled with -m -C="-lraylib"
// needs to be compiled with -m -C "-lraylib"
extern InitWindow
extern SetTargetFPS
extern WindowShouldClose
@@ -15,8 +15,8 @@ struct CHIP8
sp: i64
reg: ptr
I: i64
delay_timer: i64
sound_timer: i64
delay_timer: u8
sound_timer: u8
keyboard: Array
display: ptr
keyboard_map: Array
@@ -45,12 +45,21 @@ func chip8_create[] : CHIP8
return c
func chip8_free[c: CHIP8] : void
mem.free(c->memory)
array.free(c->stack)
mem.free(c->reg)
array.free(c->keyboard)
mem.free(c->display)
array.free(c->keyboard_map)
mem.free(c)
func chip8_disassemble[c: CHIP8, ins_count: i64] : void
for i in 0..ins_count
io.printf("0x%x: ", c->pc)
let high: i64 = c->memory[c->pc]
let low: i64 = c->memory[c->pc + 1]
let high: i64 = c->memory[c->pc] as i64
let low: i64 = c->memory[c->pc + 1] as i64
c->pc = c->pc + 2
let ins: i64 = (high << 8) | low
@@ -145,8 +154,8 @@ func chip8_disassemble[c: CHIP8, ins_count: i64] : void
io.printf("??? (%x)\n", ins)
func chip8_step[c: CHIP8] : void
let high: i64 = c->memory[c->pc]
let low: i64 = c->memory[c->pc + 1]
let high: i64 = c->memory[c->pc] as i64
let low: i64 = c->memory[c->pc + 1] as i64
c->pc = c->pc + 2
let ins: i64 = (high << 8) | low
@@ -192,17 +201,17 @@ func chip8_step[c: CHIP8] : void
else if n == 0x3
c->reg[x] = c->reg[x] ^ c->reg[y]
else if n == 0x4
let res: i64 = c->reg[x] + c->reg[y]
c->reg[0xf] = res > 0xff
let res: u8 = c->reg[x] + c->reg[y]
c->reg[0xf] = (res > 0xff) as u8
c->reg[x] = res
else if n == 0x5
c->reg[0xf] = c->reg[x] > c->reg[y]
c->reg[0xf] = (c->reg[x] > c->reg[y]) as u8
c->reg[x] = c->reg[x] - c->reg[y]
else if n == 0x6
c->reg[0xf] = c->reg[x] & 0x1
c->reg[x] = c->reg[x] >> 1
else if n == 0x7
c->reg[0xf] = c->reg[y] > c->reg[x]
c->reg[0xf] = (c->reg[y] > c->reg[x]) as u8
c->reg[x] = c->reg[y] - c->reg[x]
else if n == 0xE
c->reg[0xf] = (c->reg[x] & 0x80) >> 7
@@ -221,19 +230,19 @@ func chip8_step[c: CHIP8] : void
for row in 0..n
for col in 0..8
if (c->memory[c->I + row] & (0x80 >> col)) != 0
let pixel_x: i64 = (c->reg[x] + col) % 64
let pixel_y: i64 = (c->reg[y] + row) % 32
let offset: i64 = pixel_x + (pixel_y * 64)
let pixel_x: u8 = (c->reg[x] + col) % 64
let pixel_y: u8 = (c->reg[y] + row) % 32
let offset: i64 = pixel_x as i64 + (pixel_y * 64)
if c->display[offset] == 1
c->reg[0xf] = 1
c->display[offset] = c->display[offset] ^ 1
else if op == 0xe
if kk == 0x9e
if IsKeyDown(array.nth(c->keyboard_map, c->reg[x]))
if IsKeyDown(array.nth(c->keyboard_map, c->reg[x] as i64))
c->pc = c->pc + 2
else if kk == 0xa1
if !IsKeyDown(array.nth(c->keyboard_map, c->reg[x]))
if !IsKeyDown(array.nth(c->keyboard_map, c->reg[x] as i64))
c->pc = c->pc + 2
else if op == 0xf
if kk == 0x07
@@ -252,7 +261,7 @@ func chip8_step[c: CHIP8] : void
else if kk == 0x1E
c->I = c->I + c->reg[x]
else if kk == 0x29
c->I = c->reg[x] * 5
c->I = c->reg[x] as i64 * 5
else if kk == 0x33
c->memory[c->I] = c->reg[x] / 100
c->memory[c->I + 1] = (c->reg[x] / 10) % 10
@@ -264,27 +273,18 @@ func chip8_step[c: CHIP8] : void
for i in 0..x+1
c->reg[i] = c->memory[c->I + i]
func chip8_free[c: CHIP8] : void
mem.free(c->memory)
array.free(c->stack)
mem.free(c->reg)
array.free(c->keyboard)
mem.free(c->display)
array.free(c->keyboard_map)
mem.free(c)
func main[argc: i64, argv: ptr] : i64
let path: str = 0
let disassemble: bool = 0
let path: str = 0 as str
let disassemble: bool = false
for i in 1..argc
let arg: str = mem.read64(argv + i * 8)
let arg: str = mem.read64(argv + i * 8) as str
if str.equal(arg, "-d")
disassemble = 1
disassemble = true
else
path = arg
if path == 0
if path as i64 == 0
io.println("Usage: chip8 -d <path>")
return 1

View File

@@ -39,7 +39,7 @@ func main[argc: i64, argv: ptr] : i64
net.send(s, req as ptr, str.len(req))
mem.free(req)
let header_buf: str = mem.alloc(8192)
let header_buf: str = mem.alloc(8192) as str
let header_size = 0
let found: bool = false
let end_index: i64 = -1

View File

@@ -43,7 +43,7 @@ func part2[lines: Array] : void
for x in 1..lines->size-1
for y in 1..str.len(array.nth(lines, x))-1
if array.nth(lines, x)[y] == 'A'
let s: str = mem.alloc(5)
let s: str = mem.alloc(5) as str
s[0] = array.nth(lines, x - 1)[y - 1]
s[1] = array.nth(lines, x + 1)[y - 1]
s[2] = array.nth(lines, x + 1)[y + 1]

View File

@@ -7,7 +7,7 @@ func part1[lines: Array] : void
let largest = 0
for j in 0..str.len(line)
for k in (j+1)..str.len(line)
let s: str = mem.alloc(3)
let s: str = mem.alloc(3) as str
s[0] = line[j]
s[1] = line[k]
s[2] = 0

View File

@@ -1,4 +1,4 @@
// needs to be compiled with -m -C="-lraylib"
// needs to be compiled with -m -C "-lraylib"
extern InitWindow
extern SetTargetFPS
extern WindowShouldClose

View File

@@ -1,4 +1,4 @@
// needs to be compiled with -m -C="-lsqlite3"
// needs to be compiled with -m -C "-lsqlite3"
extern sqlite3_open
extern sqlite3_exec
extern sqlite3_prepare_v2

View File

@@ -1,283 +0,0 @@
use std::collections::HashMap;
use crate::{
parser::{Expr, Stmt},
tokenizer::{ZernError, error},
};
pub type Type = String;
pub struct StructField {
pub offset: usize,
pub field_type: Type,
}
#[derive(Clone)]
pub struct FnType {
pub return_type: Type,
pub params: Option<Vec<Type>>,
}
impl FnType {
fn new(return_type: &str, params: Vec<&str>) -> FnType {
FnType {
return_type: return_type.to_string(),
params: Some(params.iter().map(|x| x.to_string()).collect()),
}
}
fn new_variadic(return_type: &str) -> FnType {
FnType {
return_type: return_type.to_string(),
params: None,
}
}
}
pub struct Analyzer {
pub functions: HashMap<String, FnType>,
pub constants: HashMap<String, u64>,
pub structs: HashMap<String, HashMap<String, StructField>>,
}
impl Analyzer {
pub fn new() -> Analyzer {
Analyzer {
functions: HashMap::from([
("_builtin_heap_head".into(), FnType::new("ptr", vec![])),
("_builtin_heap_tail".into(), FnType::new("ptr", vec![])),
("_builtin_err_code".into(), FnType::new("ptr", vec![])),
("_builtin_err_msg".into(), FnType::new("ptr", vec![])),
("_builtin_read64".into(), FnType::new("i64", vec!["ptr"])),
(
"_builtin_set64".into(),
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![])),
]),
constants: HashMap::new(),
structs: HashMap::new(),
}
}
pub fn register_function(&mut self, stmt: &Stmt) -> Result<(), ZernError> {
if let Stmt::Function {
name,
params,
return_type,
body: _,
exported: _,
} = stmt
{
if self.functions.contains_key(&name.lexeme) {
return error!(name.loc, format!("tried to redefine '{}'", name.lexeme));
}
self.functions.insert(
name.lexeme.clone(),
FnType {
return_type: return_type.lexeme.clone(),
params: Some(params.iter().map(|x| x.var_type.lexeme.clone()).collect()),
},
);
}
Ok(())
}
pub fn analyze_stmt(&mut self, stmt: &Stmt) -> Result<(), ZernError> {
match stmt {
Stmt::Expression(expr) => self.analyze_expr(expr)?,
Stmt::Let {
name: _,
var_type: _,
initializer,
} => {
self.analyze_expr(initializer)?;
}
Stmt::Const { name, value } => {
if self.constants.contains_key(&name.lexeme)
|| self.functions.contains_key(&name.lexeme)
{
return error!(
name.loc,
format!("tried to redefine constant '{}'", name.lexeme)
);
}
if value.lexeme.starts_with("0x") {
self.constants.insert(
name.lexeme.clone(),
u64::from_str_radix(&value.lexeme[2..], 16).unwrap(),
);
} else {
self.constants
.insert(name.lexeme.clone(), value.lexeme.parse().unwrap());
}
}
Stmt::Block(statements) => {
for stmt in statements {
self.analyze_stmt(stmt)?;
}
}
Stmt::If {
keyword: _,
condition,
then_branch,
else_branch,
} => {
self.analyze_expr(condition)?;
self.analyze_stmt(then_branch)?;
self.analyze_stmt(else_branch)?;
}
Stmt::While {
keyword: _,
condition,
body,
} => {
self.analyze_expr(condition)?;
self.analyze_stmt(body)?;
}
Stmt::Function {
name,
params: _,
return_type,
body,
exported: _,
} => {
if name.lexeme == "main" && return_type.lexeme != "i64" {
return error!(&name.loc, "main must return i64");
}
self.analyze_stmt(body)?;
}
Stmt::Return { expr, keyword: _ } => {
self.analyze_expr(expr)?;
}
Stmt::For {
var: _,
start,
end,
body,
} => {
self.analyze_expr(start)?;
self.analyze_expr(end)?;
self.analyze_stmt(body)?;
}
Stmt::Break => {}
Stmt::Continue => {}
Stmt::Extern(name) => {
if self.functions.contains_key(&name.lexeme) {
return error!(name.loc, format!("tried to redefine '{}'", name.lexeme));
}
self.functions
.insert(name.lexeme.clone(), FnType::new_variadic("any"));
}
Stmt::Struct { name, fields } => {
let mut fields_map: HashMap<String, StructField> = HashMap::new();
let mut offset: usize = 0;
for field in fields {
fields_map.insert(
field.var_name.lexeme.clone(),
StructField {
offset,
field_type: field.var_type.lexeme.clone(),
},
);
offset += 8;
}
self.structs.insert(name.lexeme.clone(), fields_map);
}
}
Ok(())
}
pub fn analyze_expr(&mut self, expr: &Expr) -> Result<(), ZernError> {
match expr {
Expr::Binary { left, op: _, right } => {
self.analyze_expr(left)?;
self.analyze_expr(right)?;
}
Expr::Logical { left, op: _, right } => {
self.analyze_expr(left)?;
self.analyze_expr(right)?;
}
Expr::Grouping(expr) => self.analyze_expr(expr)?,
Expr::Literal(_) => {}
Expr::Unary { op: _, right } => {
self.analyze_expr(right)?;
}
Expr::Variable(_) => {}
Expr::Assign { left, op: _, value } => {
self.analyze_expr(left)?;
self.analyze_expr(value)?;
}
Expr::Call {
callee,
paren,
args,
} => {
if let Expr::Variable(callee_name) = *callee.clone() {
if self.functions.contains_key(&callee_name.lexeme) {
// its a function (defined/builtin/extern)
if let Some(fn_type) = self.functions.get(&callee_name.lexeme) {
// if its None, its variadic
if let Some(params) = &fn_type.params
&& params.len() != args.len()
{
return error!(
&paren.loc,
format!(
"expected {} arguments, got {}",
params.len(),
args.len()
)
);
}
} else {
return error!(
&paren.loc,
format!("undefined function: {}", callee_name.lexeme)
);
}
} else {
// its a variable containing function address
self.analyze_expr(callee)?;
}
} else {
// its an expression that evalutes to function address
self.analyze_expr(callee)?;
}
for arg in args {
self.analyze_expr(arg)?;
}
}
Expr::ArrayLiteral(exprs) => {
for expr in exprs {
self.analyze_expr(expr)?;
}
}
Expr::Index {
expr,
bracket: _,
index,
} => {
self.analyze_expr(expr)?;
self.analyze_expr(index)?;
}
Expr::AddrOf { op: _, expr } => {
self.analyze_expr(expr)?;
}
Expr::New(_) => {}
Expr::MemberAccess { left, field: _ } => {
self.analyze_expr(left)?;
}
Expr::Cast { expr, type_name: _ } => {
self.analyze_expr(expr)?;
}
}
Ok(())
}
}

View File

@@ -1,8 +1,8 @@
use std::{collections::HashMap, fmt::Write};
use crate::{
analyzer::Analyzer,
parser::{Expr, Stmt},
symbol_table::SymbolTable,
tokenizer::{Token, TokenType, ZernError, error},
};
@@ -74,17 +74,17 @@ pub struct CodegenX86_64<'a> {
data_section: String,
label_counter: usize,
data_counter: usize,
pub analyzer: &'a Analyzer,
pub symbol_table: &'a SymbolTable,
}
impl<'a> CodegenX86_64<'a> {
pub fn new(analyzer: &'a Analyzer) -> CodegenX86_64<'a> {
pub fn new(symbol_table: &'a SymbolTable) -> CodegenX86_64<'a> {
CodegenX86_64 {
output: String::new(),
data_section: String::new(),
label_counter: 0,
data_counter: 1,
analyzer,
symbol_table,
}
}
@@ -223,7 +223,7 @@ _builtin_environ:
var_type,
initializer,
} => {
// TODO: move to analyzer
// TODO: move to typechecker?
if env.get_var(&name.lexeme).is_some() {
return error!(
name.loc,
@@ -250,7 +250,7 @@ _builtin_environ:
emit!(&mut self.output, " mov QWORD [rbp-{}], rax", offset);
}
Stmt::Const { name: _, value: _ } => {
// handled in the analyzer
// handled in SymbolTable
}
Stmt::Block(statements) => {
env.push_scope();
@@ -315,7 +315,7 @@ _builtin_environ:
emit!(&mut self.output, "{}:", name.lexeme);
emit!(&mut self.output, " push rbp");
emit!(&mut self.output, " mov rbp, rsp");
emit!(&mut self.output, " sub rsp, 256"); // TODO
emit!(&mut self.output, " sub rsp, 256"); // TODO: eww
for (i, param) in params.iter().enumerate() {
let offset = env
@@ -398,7 +398,7 @@ _builtin_environ:
emit!(&mut self.output, "extern {}", name.lexeme);
}
Stmt::Struct { name: _, fields: _ } => {
// handled in the analyzer
// handled in SymbolTable
}
}
Ok(())
@@ -555,11 +555,11 @@ _builtin_environ:
}
}
Expr::Variable(name) => {
if self.analyzer.constants.contains_key(&name.lexeme) {
if self.symbol_table.constants.contains_key(&name.lexeme) {
emit!(
&mut self.output,
" mov rax, {}",
self.analyzer.constants[&name.lexeme]
self.symbol_table.constants[&name.lexeme]
);
} else {
let var = match env.get_var(&name.lexeme) {
@@ -639,6 +639,8 @@ _builtin_environ:
offset
);
}
// TODO: since all zern values are 64bit large we currently cannot call
// external functions that expect a non-64bit value past the 6th argument
let num_stack = arg_count - 6;
for i in 0..num_stack {
let arg_idx = arg_count - 1 - i;
@@ -653,7 +655,11 @@ _builtin_environ:
}
if let Expr::Variable(callee_name) = &**callee {
if self.analyzer.functions.contains_key(&callee_name.lexeme) {
if self
.symbol_table
.functions
.contains_key(&callee_name.lexeme)
{
// its a function (defined/builtin/extern)
emit!(&mut self.output, " call {}", callee_name.lexeme);
} else {
@@ -700,7 +706,7 @@ _builtin_environ:
}
Expr::AddrOf { op, expr } => match *expr.clone() {
Expr::Variable(name) => {
if self.analyzer.functions.contains_key(&name.lexeme) {
if self.symbol_table.functions.contains_key(&name.lexeme) {
emit!(&mut self.output, " mov rax, {}", name.lexeme);
} else {
let var = match env.get_var(&name.lexeme) {
@@ -724,7 +730,7 @@ _builtin_environ:
}
},
Expr::New(struct_name) => {
let struct_fields = &self.analyzer.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;
@@ -769,7 +775,7 @@ _builtin_environ:
}
};
let fields = match self.analyzer.structs.get(&struct_name) {
let fields = match self.symbol_table.structs.get(&struct_name) {
Some(f) => f,
None => {
return error!(&field.loc, format!("unknown struct type: {}", struct_name));

View File

@@ -1,6 +1,6 @@
mod analyzer;
mod codegen_x86_64;
mod parser;
mod symbol_table;
mod tokenizer;
mod typechecker;
@@ -12,8 +12,6 @@ use std::{
use tokenizer::ZernError;
use clap::Parser;
macro_rules! parse_std_file {
($statements:expr, $filename:expr) => {
let source: String = include_str!($filename).into();
@@ -44,20 +42,17 @@ fn compile_file(args: Args) -> Result<(), ZernError> {
let parser = parser::Parser::new(tokenizer.tokenize()?);
statements.extend(parser.parse()?);
let mut analyzer = analyzer::Analyzer::new();
let mut symbol_table = symbol_table::SymbolTable::new();
for stmt in &statements {
analyzer.register_function(stmt)?;
}
for stmt in &statements {
analyzer.analyze_stmt(stmt)?;
symbol_table.register_declaration(stmt)?;
}
let mut typechecker = typechecker::TypeChecker::new(&analyzer);
let mut typechecker = typechecker::TypeChecker::new(&symbol_table);
for stmt in &statements {
typechecker.typecheck_stmt(&mut typechecker::Env::new(), stmt)?;
}
let mut codegen = codegen_x86_64::CodegenX86_64::new(&analyzer);
let mut codegen = codegen_x86_64::CodegenX86_64::new(&symbol_table);
codegen.emit_prologue(args.use_gcc)?;
for stmt in statements {
codegen.compile_stmt(&mut codegen_x86_64::Env::new(), &stmt)?;
@@ -112,34 +107,82 @@ fn run_command(cmd: String) {
}
}
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
path: String,
#[arg(short, help = "Output path")]
out: Option<String>,
#[arg(short = 'S', help = "Only generate assembly")]
output_asm: bool,
#[arg(short = 'r', help = "Run the compiled executable")]
run_exe: bool,
#[arg(short = 'm', help = "Use gcc")]
use_gcc: bool,
#[arg(short = 'C', default_value = "", help = "Extra flags to pass to gcc")]
cflags: String,
}
fn main() {
let args = Args::parse();
impl Args {
fn parse(mut args: std::env::Args) -> Args {
let mut out = Args {
path: String::new(),
out: None,
output_asm: false,
run_exe: false,
use_gcc: false,
cflags: String::new(),
};
if !args.use_gcc && !args.cflags.is_empty() {
eprintln!("You can't set CFLAGS if you're not using gcc. Add the -m flag.");
process::exit(1);
while let Some(arg) = args.next() {
if arg == "-o" {
match args.next() {
Some(s) => out.out = Some(s),
None => {
eprintln!("\x1b[91mERROR\x1b[0m: -o option requires a name");
process::exit(1);
}
}
} else if arg == "-S" {
out.output_asm = true;
} else if arg == "-r" {
out.run_exe = true;
} else if arg == "-m" {
out.use_gcc = true;
} else if arg == "-C" {
match args.next() {
Some(s) => out.cflags = s,
None => {
eprintln!("\x1b[91mERROR\x1b[0m: -C option requires a name");
process::exit(1);
}
}
} else if arg == "-h" || arg == "--help" {
println!("Usage: zern [-o path] [-S] [-r] [-m] [-C cflags] path");
process::exit(0);
} else if arg.starts_with('-') {
eprintln!("\x1b[91mERROR\x1b[0m: unrecognized option: {}", arg);
process::exit(1);
} else if out.path.is_empty() {
out.path = arg
} else {
eprintln!("\x1b[91mERROR\x1b[0m: unrecognized argument: {}", arg);
process::exit(1);
}
}
if out.path.is_empty() {
eprintln!("\x1b[91mERROR\x1b[0m: you must provide a path");
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.");
process::exit(1);
}
out
}
}
fn main() {
let mut raw_args = std::env::args();
_ = raw_args.next();
let args = Args::parse(raw_args);
if let Err(err) = compile_file(args) {
eprintln!("{}", err);

View File

@@ -137,7 +137,7 @@ func net.udp_receive[s: net.UDPSocket, size: i64] : net.UDPPacket
pkt->source_addr = mem.alloc(16)
mem.zero(pkt->source_addr, 16)
let addrlen: i64 = 16
let addrlen = 16
pkt->size = _builtin_syscall(SYS_recvfrom, s->fd, pkt->data, size, 0, pkt->source_addr, ^addrlen)
return pkt
@@ -158,9 +158,9 @@ const DNS_RECURSION_DESIRED = 256
func net.encode_dns_name[domain: str] : io.Buffer
let domain_len: i64 = str.len(domain)
let buf: io.Buffer = must(io.Buffer.alloc?(domain_len + 2))
let out_pos: i64 = 0
let part_start: i64 = 0
let i: i64 = 0
let out_pos = 0
let part_start = 0
let i = 0
while i <= domain_len
if i == domain_len || domain[i] == '.'
let part_len: i64 = i - part_start
@@ -212,7 +212,7 @@ func net.resolve?[domain: str] : i64
// TODO: do actual parsing
let pos: i64 = 12 // skip header (12 bytes)
let pos = 12 // skip header (12 bytes)
while pkt->data[pos] != 0
pos = pos + pkt->data[pos] + 1 // skip question

View File

@@ -90,11 +90,11 @@ func mem._request_space?[size: i64] : mem.Block
mem.write64(_builtin_heap_tail(), blk)
return blk
func mem.alloc?[size: i64] : any
func mem.alloc?[size: i64] : ptr
err.clear()
if size <= 0
err.set(ERR_ALLOC_FAILED, "mem.alloc? called with non-positive size")
return 0
return 0 as ptr
size = mem._align(size)
@@ -108,7 +108,7 @@ func mem.alloc?[size: i64] : any
let blk: mem.Block = mem._request_space?(size)
if err.check()
return 0
return 0 as ptr
if !mem.read64(_builtin_heap_head())
mem.write64(_builtin_heap_head(), blk)
@@ -117,7 +117,7 @@ func mem.alloc?[size: i64] : any
return blk as ptr + MEM_BLOCK_SIZE
func mem.alloc[size: i64] : any
func mem.alloc[size: i64] : ptr
return must(mem.alloc?(size))
func mem.free[x: any] : void
@@ -330,7 +330,7 @@ func io.read_char[] : u8
func io.read_line[]: str
let MAX_SIZE = 60000
let buffer: str = mem.alloc(MAX_SIZE + 1)
let buffer: str = mem.alloc(MAX_SIZE + 1) as str
let n: i64 = _builtin_syscall(SYS_read, 0, buffer, MAX_SIZE)
if n < 0
n = 0
@@ -359,6 +359,22 @@ func io.Buffer.free[buf: io.Buffer] : void
func io.file_exists[path: str] : bool
return _builtin_syscall(SYS_faccessat, -100, path, 0, 0) == 0
const S_IFDIR = 0o040000
const S_IFMT = 0o170000
func io.is_a_directory[path: str] : bool
let st: ptr = mem.alloc(256) // it has 21 mixed-size fields so `ptr` must do for now
let rc: i64 = _builtin_syscall(SYS_newfstatat, -100, path, st, 0)
if rc != 0
mem.free(st)
return false
let out: bool = (mem.read32(st + 24) & S_IFMT) == S_IFDIR
mem.free(st)
return out
func io.read_text_file?[path: str] : str
err.clear()
let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0, 0)
@@ -436,7 +452,7 @@ func str.len[s: str] : i64
func str.make_copy[s: str] : str
let size: i64 = str.len(s) + 1
let dup: str = mem.alloc(size)
let dup: str = mem.alloc(size) as str
mem.copy(s as ptr, dup as ptr, size)
return dup
@@ -472,7 +488,7 @@ func str.is_alphanumeric[x: u8] : bool
func str.concat[a: str, b: str] : str
let a_len: i64 = str.len(a)
let b_len: i64 = str.len(b)
let out: str = mem.alloc(a_len + b_len + 1)
let out: str = mem.alloc(a_len + b_len + 1) as str
mem.copy(a as ptr, out as ptr, a_len)
mem.copy(b as ptr, out as ptr + a_len, b_len)
out[a_len + b_len] = 0
@@ -505,7 +521,7 @@ func str.substr[s: str, start: i64, length: i64] : str
if start < 0 || length < 0 || start + length > str.len(s)
panic("str.substr out of bounds")
let out: str = mem.alloc(length + 1)
let out: str = mem.alloc(length + 1) as str
mem.copy(s as ptr + start, out as ptr, length)
out[length] = 0
return out
@@ -513,7 +529,7 @@ func str.substr[s: str, start: i64, length: i64] : str
func str.trim[s: str] : str
let len: i64 = str.len(s)
if len == 0
let out: str = mem.alloc(1)
let out: str = mem.alloc(1) as str
out[0] = 0
return out
@@ -562,7 +578,7 @@ func str.split[haystack: str, needle: str]: Array
func str.reverse[s: str] : str
let len: i64 = str.len(s)
let out: str = mem.alloc(len + 1)
let out: str = mem.alloc(len + 1) as str
for i in 0..len
out[i] = s[len - i - 1]
@@ -571,7 +587,7 @@ func str.reverse[s: str] : str
func str.from_i64[n: i64] : str
if n == 0
let out: str = mem.alloc(2)
let out: str = mem.alloc(2) as str
out[0] = '0'
out[1] = 0
return out
@@ -582,7 +598,7 @@ func str.from_i64[n: i64] : str
if n == -9223372036854775808
return str.make_copy("-9223372036854775808")
n = -n
let buf: str = mem.alloc(21) // enough to fit -MAX_I64
let buf: str = mem.alloc(21) as str // enough to fit -MAX_I64
let end = 20
buf[end] = 0
end = end - 1
@@ -601,13 +617,13 @@ func str.hex_from_i64[n: i64] : str
let hex_chars: str = "0123456789abcdef"
if n == 0
let out: str = mem.alloc(2)
let out: str = mem.alloc(2) as str
out[0] = '0'
out[1] = 0
return out
let mask: i64 = (1 << 60) - 1
let buf: str = mem.alloc(17)
let buf: str = mem.alloc(17) as str
let len = 0
while n != 0
@@ -615,7 +631,7 @@ func str.hex_from_i64[n: i64] : str
n = (n >> 4) & mask
len = len + 1
let out: str = mem.alloc(len + 1)
let out: str = mem.alloc(len + 1) as str
let j = 0
while j < len
out[j] = buf[len - 1 - j]
@@ -626,7 +642,7 @@ func str.hex_from_i64[n: i64] : str
return out
func str.from_char[c: u8] : str
let s: str = mem.alloc(2)
let s: str = mem.alloc(2) as str
s[0] = c
s[1] = 0
return s
@@ -652,7 +668,7 @@ 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)
let out: str = mem.alloc(s_len * 2 + 1) as str
for i in 0..s_len
let high: u8 = (s[i] >> 4) & 15
@@ -678,7 +694,7 @@ func str.hex_decode[s: str] : str
let i = 0
let j = 0
let out: str = mem.alloc(s_len / 2 + 1)
let out: str = mem.alloc(s_len / 2 + 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])

144
src/symbol_table.rs Normal file
View File

@@ -0,0 +1,144 @@
use std::collections::HashMap;
use crate::{
parser::Stmt,
tokenizer::{ZernError, error},
};
pub type Type = String;
pub struct StructField {
pub offset: usize,
pub field_type: Type,
}
#[derive(Clone)]
pub struct FnType {
pub return_type: Type,
pub params: Option<Vec<Type>>,
}
impl FnType {
fn new(return_type: &str, params: Vec<&str>) -> FnType {
FnType {
return_type: return_type.to_string(),
params: Some(params.iter().map(|x| x.to_string()).collect()),
}
}
fn new_variadic(return_type: &str) -> FnType {
FnType {
return_type: return_type.to_string(),
params: None,
}
}
}
pub struct SymbolTable {
pub functions: HashMap<String, FnType>,
pub constants: HashMap<String, u64>,
pub structs: HashMap<String, HashMap<String, StructField>>,
}
impl SymbolTable {
pub fn new() -> SymbolTable {
SymbolTable {
functions: HashMap::from([
("_builtin_heap_head".into(), FnType::new("ptr", vec![])),
("_builtin_heap_tail".into(), FnType::new("ptr", vec![])),
("_builtin_err_code".into(), FnType::new("ptr", vec![])),
("_builtin_err_msg".into(), FnType::new("ptr", vec![])),
("_builtin_read64".into(), FnType::new("i64", vec!["ptr"])),
(
"_builtin_set64".into(),
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![])),
]),
constants: HashMap::new(),
structs: HashMap::new(),
}
}
pub fn register_declaration(&mut self, stmt: &Stmt) -> Result<(), ZernError> {
match stmt {
Stmt::Const { name, value } => {
if self.is_name_defined(&name.lexeme) {
return error!(
name.loc,
format!("tried to redefine constant '{}'", name.lexeme)
);
}
if value.lexeme.starts_with("0x") {
self.constants.insert(
name.lexeme.clone(),
u64::from_str_radix(&value.lexeme[2..], 16).unwrap(),
);
} else if value.lexeme.starts_with("0o") {
self.constants.insert(
name.lexeme.clone(),
u64::from_str_radix(&value.lexeme[2..], 8).unwrap(),
);
} else {
self.constants
.insert(name.lexeme.clone(), value.lexeme.parse().unwrap());
}
}
Stmt::Extern(name) => {
if self.is_name_defined(&name.lexeme) {
return error!(name.loc, format!("tried to redefine '{}'", name.lexeme));
}
self.functions
.insert(name.lexeme.clone(), FnType::new_variadic("any"));
}
Stmt::Function {
name,
params,
return_type,
body: _,
exported: _,
} => {
if self.is_name_defined(&name.lexeme) {
return error!(name.loc, format!("tried to redefine '{}'", name.lexeme));
}
self.functions.insert(
name.lexeme.clone(),
FnType {
return_type: return_type.lexeme.clone(),
params: Some(params.iter().map(|x| x.var_type.lexeme.clone()).collect()),
},
);
}
Stmt::Struct { name, fields } => {
if self.is_name_defined(&name.lexeme) {
return error!(name.loc, format!("tried to redefine '{}'", name.lexeme));
}
let mut fields_map: HashMap<String, StructField> = HashMap::new();
let mut offset: usize = 0;
for field in fields {
fields_map.insert(
field.var_name.lexeme.clone(),
StructField {
offset,
field_type: field.var_type.lexeme.clone(),
},
);
offset += 8;
}
self.structs.insert(name.lexeme.clone(), fields_map);
}
_ => {}
}
Ok(())
}
fn is_name_defined(&self, s: &str) -> bool {
self.functions.contains_key(s)
|| self.constants.contains_key(s)
|| self.structs.contains_key(s)
}
}

View File

@@ -1,8 +1,8 @@
use std::collections::HashMap;
use crate::{
analyzer::{Analyzer, Type},
parser::{Expr, Stmt},
symbol_table::{SymbolTable, Type},
tokenizer::{TokenType, ZernError, error},
};
@@ -12,7 +12,7 @@ macro_rules! expect_type {
if $expected != "any" && actual != "any" && actual != $expected {
return error!(
$loc,
format!("expected type '{}', got '{}'", $expected, actual)
format!("expected type '{}', got {}", $expected, actual)
);
}
}};
@@ -24,7 +24,7 @@ macro_rules! expect_types {
return error!(
$loc,
format!(
"expected one of [{}], got '{}'",
"expected one of [{}], got {}",
[$( $expected ),+].join(", "),
$expr_type
)
@@ -70,14 +70,14 @@ impl Env {
}
pub struct TypeChecker<'a> {
analyzer: &'a Analyzer,
symbol_table: &'a SymbolTable,
current_function_return_type: String,
}
impl<'a> TypeChecker<'a> {
pub fn new(analyzer: &'a Analyzer) -> TypeChecker<'a> {
pub fn new(symbol_table: &'a SymbolTable) -> TypeChecker<'a> {
TypeChecker {
analyzer,
symbol_table,
current_function_return_type: String::new(),
}
}
@@ -110,7 +110,7 @@ impl<'a> TypeChecker<'a> {
env.define_var(name.lexeme.clone(), actual_type);
}
Stmt::Const { name: _, value: _ } => {
// handled in the analyzer
// handled in SymbolTable
}
Stmt::Block(stmts) => {
env.push_scope();
@@ -160,12 +160,16 @@ impl<'a> TypeChecker<'a> {
env.pop_scope();
}
Stmt::Function {
name: _,
name,
params,
return_type,
body,
exported: _,
} => {
if name.lexeme == "main" && return_type.lexeme != "i64" {
return error!(&name.loc, "main must return i64");
}
if !self.is_valid_type_name(&return_type.lexeme) {
return error!(
&return_type.loc,
@@ -203,7 +207,7 @@ impl<'a> TypeChecker<'a> {
Stmt::Break => {}
Stmt::Continue => {}
Stmt::Extern(_) => {
// handled in the analyzer
// handled in the SymbolTable
}
Stmt::Struct { name: _, fields } => {
for field in fields {
@@ -300,7 +304,7 @@ impl<'a> TypeChecker<'a> {
}
}
Expr::Variable(name) => {
if self.analyzer.constants.contains_key(&name.lexeme) {
if self.symbol_table.constants.contains_key(&name.lexeme) {
Ok("i64".into())
} else {
match env.get_var_type(&name.lexeme) {
@@ -337,7 +341,7 @@ impl<'a> TypeChecker<'a> {
Expr::MemberAccess { left, field } => {
let left_type = self.typecheck_expr(env, left)?;
let fields = match self.analyzer.structs.get(&left_type) {
let fields = match self.symbol_table.structs.get(&left_type) {
Some(f) => f,
None => {
return error!(
@@ -369,12 +373,20 @@ impl<'a> TypeChecker<'a> {
args,
} => {
if let Expr::Variable(callee_name) = &**callee {
if self.analyzer.functions.contains_key(&callee_name.lexeme) {
let fn_type = &self.analyzer.functions[&callee_name.lexeme];
if let Some(fn_type) = self.symbol_table.functions.get(&callee_name.lexeme) {
// its a function (defined/builtin/extern)
if let Some(params) = fn_type.params.clone() {
if params.len() != args.len() {
return error!(
&paren.loc,
format!(
"expected {} arguments, got {}",
params.len(),
args.len()
)
);
}
for (i, arg) in args.iter().enumerate() {
// arity is checked in the analyzer
expect_type!(self.typecheck_expr(env, arg)?, params[i], paren.loc);
}
} else {
@@ -420,7 +432,7 @@ impl<'a> TypeChecker<'a> {
}
Expr::AddrOf { op, expr } => match expr.as_ref() {
Expr::Variable(name) => {
if self.analyzer.functions.contains_key(&name.lexeme) {
if self.symbol_table.functions.contains_key(&name.lexeme) {
Ok("fnptr".into())
} else {
Ok("ptr".into())
@@ -431,7 +443,7 @@ impl<'a> TypeChecker<'a> {
}
},
Expr::New(struct_name) => {
if !self.analyzer.structs.contains_key(&struct_name.lexeme) {
if !self.symbol_table.structs.contains_key(&struct_name.lexeme) {
return error!(
&struct_name.loc,
format!("unknown struct name: {}", &struct_name.lexeme)
@@ -442,7 +454,7 @@ impl<'a> TypeChecker<'a> {
Expr::MemberAccess { left, field } => {
let left_type = self.typecheck_expr(env, left)?;
let fields = match self.analyzer.structs.get(&left_type) {
let fields = match self.symbol_table.structs.get(&left_type) {
Some(f) => f,
None => {
return error!(&field.loc, format!("unknown struct type: {}", left_type));
@@ -473,7 +485,7 @@ impl<'a> TypeChecker<'a> {
if BUILTIN_TYPES.contains(&name) {
return true;
}
if self.analyzer.structs.contains_key(name) {
if self.symbol_table.structs.contains_key(name) {
return true;
}
false