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. # It is not intended for manual editing.
version = 4 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]] [[package]]
name = "zern" name = "zern"
version = "0.3.0" version = "0.3.0"
dependencies = [
"clap",
]

View File

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

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

View File

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

View File

@@ -43,7 +43,7 @@ func part2[lines: Array] : void
for x in 1..lines->size-1 for x in 1..lines->size-1
for y in 1..str.len(array.nth(lines, x))-1 for y in 1..str.len(array.nth(lines, x))-1
if array.nth(lines, x)[y] == 'A' 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[0] = array.nth(lines, x - 1)[y - 1]
s[1] = 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] s[2] = array.nth(lines, x + 1)[y + 1]

View File

@@ -7,7 +7,7 @@ func part1[lines: Array] : void
let largest = 0 let largest = 0
for j in 0..str.len(line) for j in 0..str.len(line)
for k in (j+1)..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[0] = line[j]
s[1] = line[k] s[1] = line[k]
s[2] = 0 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 InitWindow
extern SetTargetFPS extern SetTargetFPS
extern WindowShouldClose 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_open
extern sqlite3_exec extern sqlite3_exec
extern sqlite3_prepare_v2 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 std::{collections::HashMap, fmt::Write};
use crate::{ use crate::{
analyzer::Analyzer,
parser::{Expr, Stmt}, parser::{Expr, Stmt},
symbol_table::SymbolTable,
tokenizer::{Token, TokenType, ZernError, error}, tokenizer::{Token, TokenType, ZernError, error},
}; };
@@ -74,17 +74,17 @@ pub struct CodegenX86_64<'a> {
data_section: String, data_section: String,
label_counter: usize, label_counter: usize,
data_counter: usize, data_counter: usize,
pub analyzer: &'a Analyzer, pub symbol_table: &'a SymbolTable,
} }
impl<'a> CodegenX86_64<'a> { 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 { CodegenX86_64 {
output: String::new(), output: String::new(),
data_section: String::new(), data_section: String::new(),
label_counter: 0, label_counter: 0,
data_counter: 1, data_counter: 1,
analyzer, symbol_table,
} }
} }
@@ -223,7 +223,7 @@ _builtin_environ:
var_type, var_type,
initializer, initializer,
} => { } => {
// TODO: move to analyzer // TODO: move to typechecker?
if env.get_var(&name.lexeme).is_some() { if env.get_var(&name.lexeme).is_some() {
return error!( return error!(
name.loc, name.loc,
@@ -250,7 +250,7 @@ _builtin_environ:
emit!(&mut self.output, " mov QWORD [rbp-{}], rax", offset); emit!(&mut self.output, " mov QWORD [rbp-{}], rax", offset);
} }
Stmt::Const { name: _, value: _ } => { Stmt::Const { name: _, value: _ } => {
// handled in the analyzer // handled in SymbolTable
} }
Stmt::Block(statements) => { Stmt::Block(statements) => {
env.push_scope(); env.push_scope();
@@ -315,7 +315,7 @@ _builtin_environ:
emit!(&mut self.output, "{}:", name.lexeme); emit!(&mut self.output, "{}:", name.lexeme);
emit!(&mut self.output, " push rbp"); emit!(&mut self.output, " push rbp");
emit!(&mut self.output, " mov rbp, rsp"); 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() { for (i, param) in params.iter().enumerate() {
let offset = env let offset = env
@@ -398,7 +398,7 @@ _builtin_environ:
emit!(&mut self.output, "extern {}", name.lexeme); emit!(&mut self.output, "extern {}", name.lexeme);
} }
Stmt::Struct { name: _, fields: _ } => { Stmt::Struct { name: _, fields: _ } => {
// handled in the analyzer // handled in SymbolTable
} }
} }
Ok(()) Ok(())
@@ -555,11 +555,11 @@ _builtin_environ:
} }
} }
Expr::Variable(name) => { Expr::Variable(name) => {
if self.analyzer.constants.contains_key(&name.lexeme) { if self.symbol_table.constants.contains_key(&name.lexeme) {
emit!( emit!(
&mut self.output, &mut self.output,
" mov rax, {}", " mov rax, {}",
self.analyzer.constants[&name.lexeme] self.symbol_table.constants[&name.lexeme]
); );
} else { } else {
let var = match env.get_var(&name.lexeme) { let var = match env.get_var(&name.lexeme) {
@@ -639,6 +639,8 @@ _builtin_environ:
offset 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; let num_stack = arg_count - 6;
for i in 0..num_stack { for i in 0..num_stack {
let arg_idx = arg_count - 1 - i; let arg_idx = arg_count - 1 - i;
@@ -653,7 +655,11 @@ _builtin_environ:
} }
if let Expr::Variable(callee_name) = &**callee { 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) // its a function (defined/builtin/extern)
emit!(&mut self.output, " call {}", callee_name.lexeme); emit!(&mut self.output, " call {}", callee_name.lexeme);
} else { } else {
@@ -700,7 +706,7 @@ _builtin_environ:
} }
Expr::AddrOf { op, expr } => match *expr.clone() { Expr::AddrOf { op, expr } => match *expr.clone() {
Expr::Variable(name) => { 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); emit!(&mut self.output, " mov rax, {}", name.lexeme);
} else { } else {
let var = match env.get_var(&name.lexeme) { let var = match env.get_var(&name.lexeme) {
@@ -724,7 +730,7 @@ _builtin_environ:
} }
}, },
Expr::New(struct_name) => { 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 // TODO: panic on mem.alloc error
let memory_size = struct_fields.len() * 8; 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, Some(f) => f,
None => { None => {
return error!(&field.loc, format!("unknown struct type: {}", struct_name)); return error!(&field.loc, format!("unknown struct type: {}", struct_name));

View File

@@ -1,6 +1,6 @@
mod analyzer;
mod codegen_x86_64; mod codegen_x86_64;
mod parser; mod parser;
mod symbol_table;
mod tokenizer; mod tokenizer;
mod typechecker; mod typechecker;
@@ -12,8 +12,6 @@ use std::{
use tokenizer::ZernError; use tokenizer::ZernError;
use clap::Parser;
macro_rules! parse_std_file { macro_rules! parse_std_file {
($statements:expr, $filename:expr) => { ($statements:expr, $filename:expr) => {
let source: String = include_str!($filename).into(); 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()?); let parser = parser::Parser::new(tokenizer.tokenize()?);
statements.extend(parser.parse()?); statements.extend(parser.parse()?);
let mut analyzer = analyzer::Analyzer::new(); let mut symbol_table = symbol_table::SymbolTable::new();
for stmt in &statements { for stmt in &statements {
analyzer.register_function(stmt)?; symbol_table.register_declaration(stmt)?;
}
for stmt in &statements {
analyzer.analyze_stmt(stmt)?;
} }
let mut typechecker = typechecker::TypeChecker::new(&analyzer); let mut typechecker = typechecker::TypeChecker::new(&symbol_table);
for stmt in &statements { for stmt in &statements {
typechecker.typecheck_stmt(&mut typechecker::Env::new(), stmt)?; 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)?; codegen.emit_prologue(args.use_gcc)?;
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)?;
@@ -112,35 +107,83 @@ fn run_command(cmd: String) {
} }
} }
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args { struct Args {
path: String, path: String,
#[arg(short, help = "Output path")]
out: Option<String>, out: Option<String>,
#[arg(short = 'S', help = "Only generate assembly")]
output_asm: bool, output_asm: bool,
#[arg(short = 'r', help = "Run the compiled executable")]
run_exe: bool, run_exe: bool,
#[arg(short = 'm', help = "Use gcc")]
use_gcc: bool, use_gcc: bool,
#[arg(short = 'C', default_value = "", help = "Extra flags to pass to gcc")]
cflags: String, cflags: String,
} }
fn main() { impl Args {
let args = Args::parse(); 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() { 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."); eprintln!("You can't set CFLAGS if you're not using gcc. Add the -m flag.");
process::exit(1); 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) { if let Err(err) = compile_file(args) {
eprintln!("{}", err); eprintln!("{}", err);
process::exit(1); process::exit(1);

View File

@@ -137,7 +137,7 @@ func net.udp_receive[s: net.UDPSocket, size: i64] : net.UDPPacket
pkt->source_addr = mem.alloc(16) pkt->source_addr = mem.alloc(16)
mem.zero(pkt->source_addr, 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) pkt->size = _builtin_syscall(SYS_recvfrom, s->fd, pkt->data, size, 0, pkt->source_addr, ^addrlen)
return pkt return pkt
@@ -158,9 +158,9 @@ const DNS_RECURSION_DESIRED = 256
func net.encode_dns_name[domain: str] : io.Buffer func net.encode_dns_name[domain: str] : io.Buffer
let domain_len: i64 = str.len(domain) let domain_len: i64 = str.len(domain)
let buf: io.Buffer = must(io.Buffer.alloc?(domain_len + 2)) let buf: io.Buffer = must(io.Buffer.alloc?(domain_len + 2))
let out_pos: i64 = 0 let out_pos = 0
let part_start: i64 = 0 let part_start = 0
let i: i64 = 0 let i = 0
while i <= domain_len while i <= domain_len
if i == domain_len || domain[i] == '.' if i == domain_len || domain[i] == '.'
let part_len: i64 = i - part_start let part_len: i64 = i - part_start
@@ -212,7 +212,7 @@ func net.resolve?[domain: str] : i64
// TODO: do actual parsing // TODO: do actual parsing
let pos: i64 = 12 // skip header (12 bytes) let pos = 12 // skip header (12 bytes)
while pkt->data[pos] != 0 while pkt->data[pos] != 0
pos = pos + pkt->data[pos] + 1 // skip question 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) mem.write64(_builtin_heap_tail(), blk)
return blk return blk
func mem.alloc?[size: i64] : any func mem.alloc?[size: i64] : ptr
err.clear() err.clear()
if size <= 0 if size <= 0
err.set(ERR_ALLOC_FAILED, "mem.alloc? called with non-positive size") err.set(ERR_ALLOC_FAILED, "mem.alloc? called with non-positive size")
return 0 return 0 as ptr
size = mem._align(size) size = mem._align(size)
@@ -108,7 +108,7 @@ func mem.alloc?[size: i64] : any
let blk: mem.Block = mem._request_space?(size) let blk: mem.Block = mem._request_space?(size)
if err.check() if err.check()
return 0 return 0 as ptr
if !mem.read64(_builtin_heap_head()) if !mem.read64(_builtin_heap_head())
mem.write64(_builtin_heap_head(), blk) mem.write64(_builtin_heap_head(), blk)
@@ -117,7 +117,7 @@ func mem.alloc?[size: i64] : any
return blk as ptr + MEM_BLOCK_SIZE return blk as ptr + MEM_BLOCK_SIZE
func mem.alloc[size: i64] : any func mem.alloc[size: i64] : ptr
return must(mem.alloc?(size)) return must(mem.alloc?(size))
func mem.free[x: any] : void func mem.free[x: any] : void
@@ -330,7 +330,7 @@ func io.read_char[] : u8
func io.read_line[]: str func io.read_line[]: str
let MAX_SIZE = 60000 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) let n: i64 = _builtin_syscall(SYS_read, 0, buffer, MAX_SIZE)
if n < 0 if n < 0
n = 0 n = 0
@@ -359,6 +359,22 @@ func io.Buffer.free[buf: io.Buffer] : void
func io.file_exists[path: str] : bool func io.file_exists[path: str] : bool
return _builtin_syscall(SYS_faccessat, -100, path, 0, 0) == 0 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 func io.read_text_file?[path: str] : str
err.clear() err.clear()
let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0, 0) 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 func str.make_copy[s: str] : str
let size: i64 = str.len(s) + 1 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) mem.copy(s as ptr, dup as ptr, size)
return dup return dup
@@ -472,7 +488,7 @@ func str.is_alphanumeric[x: u8] : bool
func str.concat[a: str, b: str] : str func str.concat[a: str, b: str] : str
let a_len: i64 = str.len(a) let a_len: i64 = str.len(a)
let b_len: i64 = str.len(b) 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(a as ptr, out as ptr, a_len)
mem.copy(b as ptr, out as ptr + a_len, b_len) mem.copy(b as ptr, out as ptr + a_len, b_len)
out[a_len + b_len] = 0 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) if start < 0 || length < 0 || start + length > str.len(s)
panic("str.substr out of bounds") 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) mem.copy(s as ptr + start, out as ptr, length)
out[length] = 0 out[length] = 0
return out return out
@@ -513,7 +529,7 @@ func str.substr[s: str, start: i64, length: i64] : str
func str.trim[s: str] : str func str.trim[s: str] : str
let len: i64 = str.len(s) let len: i64 = str.len(s)
if len == 0 if len == 0
let out: str = mem.alloc(1) let out: str = mem.alloc(1) as str
out[0] = 0 out[0] = 0
return out return out
@@ -562,7 +578,7 @@ func str.split[haystack: str, needle: str]: Array
func str.reverse[s: str] : str func str.reverse[s: str] : str
let len: i64 = str.len(s) 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 for i in 0..len
out[i] = s[len - i - 1] out[i] = s[len - i - 1]
@@ -571,7 +587,7 @@ func str.reverse[s: str] : str
func str.from_i64[n: i64] : str func str.from_i64[n: i64] : str
if n == 0 if n == 0
let out: str = mem.alloc(2) let out: str = mem.alloc(2) as str
out[0] = '0' out[0] = '0'
out[1] = 0 out[1] = 0
return out return out
@@ -582,7 +598,7 @@ func str.from_i64[n: i64] : str
if n == -9223372036854775808 if n == -9223372036854775808
return str.make_copy("-9223372036854775808") return str.make_copy("-9223372036854775808")
n = -n 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 let end = 20
buf[end] = 0 buf[end] = 0
end = end - 1 end = end - 1
@@ -601,13 +617,13 @@ func str.hex_from_i64[n: i64] : str
let hex_chars: str = "0123456789abcdef" let hex_chars: str = "0123456789abcdef"
if n == 0 if n == 0
let out: str = mem.alloc(2) let out: str = mem.alloc(2) as str
out[0] = '0' out[0] = '0'
out[1] = 0 out[1] = 0
return out return out
let mask: i64 = (1 << 60) - 1 let mask: i64 = (1 << 60) - 1
let buf: str = mem.alloc(17) let buf: str = mem.alloc(17) as str
let len = 0 let len = 0
while n != 0 while n != 0
@@ -615,7 +631,7 @@ func str.hex_from_i64[n: i64] : str
n = (n >> 4) & mask n = (n >> 4) & mask
len = len + 1 len = len + 1
let out: str = mem.alloc(len + 1) let out: str = mem.alloc(len + 1) as str
let j = 0 let j = 0
while j < len while j < len
out[j] = buf[len - 1 - j] out[j] = buf[len - 1 - j]
@@ -626,7 +642,7 @@ func str.hex_from_i64[n: i64] : str
return out return out
func str.from_char[c: u8] : str 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[0] = c
s[1] = 0 s[1] = 0
return s return s
@@ -652,7 +668,7 @@ 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 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 for i in 0..s_len
let high: u8 = (s[i] >> 4) & 15 let high: u8 = (s[i] >> 4) & 15
@@ -678,7 +694,7 @@ func str.hex_decode[s: str] : str
let i = 0 let i = 0
let j = 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 while i < s_len
out[j] = str._hex_digit_to_int(s[i]) * 16 + str._hex_digit_to_int(s[i + 1]) 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 std::collections::HashMap;
use crate::{ use crate::{
analyzer::{Analyzer, Type},
parser::{Expr, Stmt}, parser::{Expr, Stmt},
symbol_table::{SymbolTable, Type},
tokenizer::{TokenType, ZernError, error}, tokenizer::{TokenType, ZernError, error},
}; };
@@ -12,7 +12,7 @@ macro_rules! expect_type {
if $expected != "any" && actual != "any" && actual != $expected { if $expected != "any" && actual != "any" && actual != $expected {
return error!( return error!(
$loc, $loc,
format!("expected type '{}', got '{}'", $expected, actual) format!("expected type '{}', got {}", $expected, actual)
); );
} }
}}; }};
@@ -24,7 +24,7 @@ macro_rules! expect_types {
return error!( return error!(
$loc, $loc,
format!( format!(
"expected one of [{}], got '{}'", "expected one of [{}], got {}",
[$( $expected ),+].join(", "), [$( $expected ),+].join(", "),
$expr_type $expr_type
) )
@@ -70,14 +70,14 @@ impl Env {
} }
pub struct TypeChecker<'a> { pub struct TypeChecker<'a> {
analyzer: &'a Analyzer, symbol_table: &'a SymbolTable,
current_function_return_type: String, current_function_return_type: String,
} }
impl<'a> TypeChecker<'a> { impl<'a> TypeChecker<'a> {
pub fn new(analyzer: &'a Analyzer) -> TypeChecker<'a> { pub fn new(symbol_table: &'a SymbolTable) -> TypeChecker<'a> {
TypeChecker { TypeChecker {
analyzer, symbol_table,
current_function_return_type: String::new(), current_function_return_type: String::new(),
} }
} }
@@ -110,7 +110,7 @@ impl<'a> TypeChecker<'a> {
env.define_var(name.lexeme.clone(), actual_type); env.define_var(name.lexeme.clone(), actual_type);
} }
Stmt::Const { name: _, value: _ } => { Stmt::Const { name: _, value: _ } => {
// handled in the analyzer // handled in SymbolTable
} }
Stmt::Block(stmts) => { Stmt::Block(stmts) => {
env.push_scope(); env.push_scope();
@@ -160,12 +160,16 @@ impl<'a> TypeChecker<'a> {
env.pop_scope(); env.pop_scope();
} }
Stmt::Function { Stmt::Function {
name: _, name,
params, params,
return_type, return_type,
body, body,
exported: _, 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) { if !self.is_valid_type_name(&return_type.lexeme) {
return error!( return error!(
&return_type.loc, &return_type.loc,
@@ -203,7 +207,7 @@ impl<'a> TypeChecker<'a> {
Stmt::Break => {} Stmt::Break => {}
Stmt::Continue => {} Stmt::Continue => {}
Stmt::Extern(_) => { Stmt::Extern(_) => {
// handled in the analyzer // handled in the SymbolTable
} }
Stmt::Struct { name: _, fields } => { Stmt::Struct { name: _, fields } => {
for field in fields { for field in fields {
@@ -300,7 +304,7 @@ impl<'a> TypeChecker<'a> {
} }
} }
Expr::Variable(name) => { Expr::Variable(name) => {
if self.analyzer.constants.contains_key(&name.lexeme) { if self.symbol_table.constants.contains_key(&name.lexeme) {
Ok("i64".into()) Ok("i64".into())
} else { } else {
match env.get_var_type(&name.lexeme) { match env.get_var_type(&name.lexeme) {
@@ -337,7 +341,7 @@ impl<'a> TypeChecker<'a> {
Expr::MemberAccess { left, field } => { Expr::MemberAccess { left, field } => {
let left_type = self.typecheck_expr(env, left)?; 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, Some(f) => f,
None => { None => {
return error!( return error!(
@@ -369,12 +373,20 @@ impl<'a> TypeChecker<'a> {
args, args,
} => { } => {
if let Expr::Variable(callee_name) = &**callee { if let Expr::Variable(callee_name) = &**callee {
if self.analyzer.functions.contains_key(&callee_name.lexeme) { if let Some(fn_type) = self.symbol_table.functions.get(&callee_name.lexeme) {
let fn_type = &self.analyzer.functions[&callee_name.lexeme];
// its a function (defined/builtin/extern) // its a function (defined/builtin/extern)
if let Some(params) = fn_type.params.clone() { 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() { for (i, arg) in args.iter().enumerate() {
// arity is checked in the analyzer
expect_type!(self.typecheck_expr(env, arg)?, params[i], paren.loc); expect_type!(self.typecheck_expr(env, arg)?, params[i], paren.loc);
} }
} else { } else {
@@ -420,7 +432,7 @@ impl<'a> TypeChecker<'a> {
} }
Expr::AddrOf { op, expr } => match expr.as_ref() { Expr::AddrOf { op, expr } => match expr.as_ref() {
Expr::Variable(name) => { Expr::Variable(name) => {
if self.analyzer.functions.contains_key(&name.lexeme) { if self.symbol_table.functions.contains_key(&name.lexeme) {
Ok("fnptr".into()) Ok("fnptr".into())
} else { } else {
Ok("ptr".into()) Ok("ptr".into())
@@ -431,7 +443,7 @@ impl<'a> TypeChecker<'a> {
} }
}, },
Expr::New(struct_name) => { 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!( return error!(
&struct_name.loc, &struct_name.loc,
format!("unknown struct name: {}", &struct_name.lexeme) format!("unknown struct name: {}", &struct_name.lexeme)
@@ -442,7 +454,7 @@ impl<'a> TypeChecker<'a> {
Expr::MemberAccess { left, field } => { Expr::MemberAccess { left, field } => {
let left_type = self.typecheck_expr(env, left)?; 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, Some(f) => f,
None => { None => {
return error!(&field.loc, format!("unknown struct type: {}", left_type)); return error!(&field.loc, format!("unknown struct type: {}", left_type));
@@ -473,7 +485,7 @@ impl<'a> TypeChecker<'a> {
if BUILTIN_TYPES.contains(&name) { if BUILTIN_TYPES.contains(&name) {
return true; return true;
} }
if self.analyzer.structs.contains_key(name) { if self.symbol_table.structs.contains_key(name) {
return true; return true;
} }
false false