Merge branch 'typechecker'

This commit is contained in:
2026-03-18 19:10:10 +01:00
12 changed files with 783 additions and 233 deletions

View File

@@ -3,7 +3,7 @@ func main[argc: i64, argv: ptr] : i64
io.println("ERROR: url is missing")
return 1
let url: str = mem.read64(argv + 8)
let url: str = mem.read64(argv + 8) as str
if str.len(url) <= 7
io.println("ERROR: invalid url (Did you forget \"http://\"?)")
@@ -36,7 +36,7 @@ func main[argc: i64, argv: ptr] : i64
req = str.concat(req, " HTTP/1.0\r\nHost: ")
req = str.concat(req, host)
req = str.concat(req, "\r\nConnection: close\r\n\r\n")
net.send(s, req, str.len(req))
net.send(s, req as ptr, str.len(req))
mem.free(req)
let header_buf: str = mem.alloc(8192)
@@ -45,7 +45,7 @@ func main[argc: i64, argv: ptr] : i64
let end_index: i64 = -1
while !found && header_size < 8192
let n: i64 = net.read(s, header_buf + header_size, 8192 - header_size)
let n: i64 = net.read(s, header_buf as ptr + header_size, 8192 - header_size)
if n <= 0
break
let current_size: i64 = header_size + n
@@ -59,7 +59,7 @@ func main[argc: i64, argv: ptr] : i64
header_size = current_size
if end_index < header_size
io.print_sized(header_buf + end_index, header_size - end_index)
io.print_sized(header_buf as ptr + end_index, header_size as i64 - end_index)
mem.free(header_buf)
let buffer: ptr = mem.alloc(4096)

View File

@@ -25,7 +25,7 @@ func part2_rec[bank: str, start: i64, remaining: i64] : i64
let len: i64 = str.len(bank)
for i in (start)..(len-remaining+1)
let v: i64 = bank[i] - '0'
let v: i64 = (bank[i] - '0') as i64
if v > largest
largest = v
largest_idx = i

View File

@@ -2,7 +2,7 @@ func main[] : i64
let s: i64 = must(net.listen?(net.pack_addr(127, 0, 0, 1), 8000))
io.println("Listening on port 8000...")
let resp: str = mem.alloc(60000)
let resp: ptr = mem.alloc(60000)
while true
let conn: i64 = net.accept(s)
if conn < 0

View File

@@ -1,5 +1,5 @@
func main[] : i64
let s: i64 = must(net.udp_listen?(net.pack_addr(127, 0, 0, 1), 8000))
let s: net.UDPSocket = must(net.create_udp_server?(net.pack_addr(127, 0, 0, 1), 8000))
io.println("Listening on port 8000...")
while true

View File

@@ -5,25 +5,57 @@ use crate::{
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, i32>,
pub functions: HashMap<String, FnType>,
pub constants: HashMap<String, u64>,
pub structs: HashMap<String, HashMap<String, usize>>,
pub structs: HashMap<String, HashMap<String, StructField>>,
}
impl Analyzer {
pub fn new() -> Analyzer {
Analyzer {
functions: HashMap::from([
("_builtin_heap_head".into(), 0),
("_builtin_heap_tail".into(), 0),
("_builtin_err_code".into(), 0),
("_builtin_err_msg".into(), 0),
("_builtin_read64".into(), 1),
("_builtin_set64".into(), 2),
("_builtin_syscall".into(), -1),
("io.printf".into(), -1),
("_builtin_environ".into(), 0),
("_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(),
@@ -34,7 +66,7 @@ impl Analyzer {
if let Stmt::Function {
name,
params,
return_type: _,
return_type,
body: _,
exported: _,
} = stmt
@@ -42,8 +74,13 @@ impl Analyzer {
if self.functions.contains_key(&name.lexeme) {
return error!(name.loc, format!("tried to redefine '{}'", name.lexeme));
}
self.functions
.insert(name.lexeme.clone(), params.len() as i32);
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(())
}
@@ -83,6 +120,7 @@ impl Analyzer {
}
}
Stmt::If {
keyword: _,
condition,
then_branch,
else_branch,
@@ -91,7 +129,11 @@ impl Analyzer {
self.analyze_stmt(then_branch)?;
self.analyze_stmt(else_branch)?;
}
Stmt::While { condition, body } => {
Stmt::While {
keyword: _,
condition,
body,
} => {
self.analyze_expr(condition)?;
self.analyze_stmt(body)?;
}
@@ -108,7 +150,7 @@ impl Analyzer {
self.analyze_stmt(body)?;
}
Stmt::Return(expr) => {
Stmt::Return { expr, keyword: _ } => {
self.analyze_expr(expr)?;
}
Stmt::For {
@@ -127,14 +169,21 @@ impl Analyzer {
if self.functions.contains_key(&name.lexeme) {
return error!(name.loc, format!("tried to redefine '{}'", name.lexeme));
}
self.functions.insert(name.lexeme.clone(), -1);
self.functions
.insert(name.lexeme.clone(), FnType::new_variadic("any"));
}
Stmt::Struct { name, fields } => {
let mut fields_map: HashMap<String, usize> = HashMap::new();
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(), offset);
fields_map.insert(
field.var_name.lexeme.clone(),
StructField {
offset,
field_type: field.var_type.lexeme.clone(),
},
);
offset += 8;
}
@@ -172,11 +221,18 @@ impl Analyzer {
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(arity) = self.functions.get(&callee_name.lexeme) {
if *arity >= 0 && *arity != args.len() as i32 {
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 {}", arity, args.len())
format!(
"expected {} arguments, got {}",
params.len(),
args.len()
)
);
}
} else {
@@ -203,7 +259,11 @@ impl Analyzer {
self.analyze_expr(expr)?;
}
}
Expr::Index { expr, index } => {
Expr::Index {
expr,
bracket: _,
index,
} => {
self.analyze_expr(expr)?;
self.analyze_expr(index)?;
}
@@ -214,6 +274,9 @@ impl Analyzer {
Expr::MemberAccess { left, field: _ } => {
self.analyze_expr(left)?;
}
Expr::Cast { expr, type_name: _ } => {
self.analyze_expr(expr)?;
}
}
Ok(())
}

View File

@@ -69,25 +69,22 @@ macro_rules! emit {
static REGISTERS: [&str; 6] = ["rdi", "rsi", "rdx", "rcx", "r8", "r9"];
// TODO: currently they are all just 64 bit values
static BUILTIN_TYPES: [&str; 7] = ["void", "u8", "i64", "str", "bool", "ptr", "any"];
pub struct CodegenX86_64 {
pub struct CodegenX86_64<'a> {
output: String,
data_section: String,
label_counter: usize,
data_counter: usize,
pub analyzer: Analyzer,
pub analyzer: &'a Analyzer,
}
impl CodegenX86_64 {
pub fn new() -> CodegenX86_64 {
impl<'a> CodegenX86_64<'a> {
pub fn new(analyzer: &'a Analyzer) -> CodegenX86_64<'a> {
CodegenX86_64 {
output: String::new(),
data_section: String::new(),
label_counter: 0,
data_counter: 1,
analyzer: Analyzer::new(),
analyzer,
}
}
@@ -248,10 +245,6 @@ _builtin_environ:
},
};
if !self.is_valid_type_name(&var_type) {
return error!(&name.loc, "unrecognized type: ".to_owned() + &var_type);
}
self.compile_expr(env, initializer)?;
let offset = env.define_var(name.lexeme.clone(), var_type);
emit!(&mut self.output, " mov QWORD [rbp-{}], rax", offset);
@@ -267,6 +260,7 @@ _builtin_environ:
env.pop_scope();
}
Stmt::If {
keyword: _,
condition,
then_branch,
else_branch,
@@ -283,7 +277,11 @@ _builtin_environ:
self.compile_stmt(env, else_branch)?;
emit!(&mut self.output, "{}:", end_label);
}
Stmt::While { condition, body } => {
Stmt::While {
keyword: _,
condition,
body,
} => {
let old_loop_begin_label = env.loop_begin_label.clone();
let old_loop_end_label = env.loop_end_label.clone();
let old_loop_continue_label = env.loop_continue_label.clone();
@@ -306,7 +304,7 @@ _builtin_environ:
Stmt::Function {
name,
params,
return_type,
return_type: _,
body,
exported,
} => {
@@ -319,21 +317,7 @@ _builtin_environ:
emit!(&mut self.output, " mov rbp, rsp");
emit!(&mut self.output, " sub rsp, 256"); // TODO
if !self.is_valid_type_name(&return_type.lexeme) {
return error!(
&return_type.loc,
"unrecognized type: ".to_owned() + &return_type.lexeme
);
}
for (i, param) in params.iter().enumerate() {
if !self.is_valid_type_name(&param.var_type.lexeme) {
return error!(
&param.var_name.loc,
"unrecognized type: ".to_owned() + &param.var_type.lexeme
);
}
let offset = env
.define_var(param.var_name.lexeme.clone(), param.var_type.lexeme.clone());
if let Some(reg) = REGISTERS.get(i) {
@@ -360,7 +344,7 @@ _builtin_environ:
emit!(&mut self.output, " ret");
}
}
Stmt::Return(expr) => {
Stmt::Return { expr, keyword: _ } => {
self.compile_expr(env, expr)?;
emit!(&mut self.output, " mov rsp, rbp");
emit!(&mut self.output, " pop rbp");
@@ -578,15 +562,9 @@ _builtin_environ:
self.analyzer.constants[&name.lexeme]
);
} else {
// TODO: move to analyzer
let var = match env.get_var(&name.lexeme) {
Some(x) => x,
None => {
return error!(
name.loc,
format!("undefined variable: {}", &name.lexeme)
);
}
None => unreachable!("this should be caught in the typechecker"),
};
emit!(
&mut self.output,
@@ -600,15 +578,9 @@ _builtin_environ:
match left.as_ref() {
Expr::Variable(name) => {
// TODO: move to analyzer
let var = match env.get_var(&name.lexeme) {
Some(x) => x,
None => {
return error!(
name.loc,
format!("undefined variable: {}", &name.lexeme)
);
}
None => unreachable!(),
};
emit!(
&mut self.output,
@@ -616,7 +588,11 @@ _builtin_environ:
var.stack_offset,
);
}
Expr::Index { expr, index } => {
Expr::Index {
expr,
bracket: _,
index,
} => {
emit!(&mut self.output, " push rax");
self.compile_expr(env, expr)?;
emit!(&mut self.output, " push rax");
@@ -710,7 +686,11 @@ _builtin_environ:
}
emit!(&mut self.output, " pop rax");
}
Expr::Index { expr, index } => {
Expr::Index {
expr,
bracket: _,
index,
} => {
self.compile_expr(env, expr)?;
emit!(&mut self.output, " push rax");
self.compile_expr(env, index)?;
@@ -761,20 +741,13 @@ _builtin_environ:
self.compile_expr(env, left)?;
emit!(&mut self.output, " mov rax, QWORD [rax+{}]", offset);
}
Expr::Cast { expr, type_name: _ } => {
self.compile_expr(env, expr)?;
}
}
Ok(())
}
fn is_valid_type_name(&self, name: &str) -> bool {
if BUILTIN_TYPES.contains(&name) {
return true;
}
if self.analyzer.structs.contains_key(name) {
return true;
}
false
}
fn get_field_offset(
&self,
env: &mut Env,
@@ -803,11 +776,11 @@ _builtin_environ:
}
};
let offset = match fields.get(&field.lexeme) {
Some(o) => *o,
let field = match fields.get(&field.lexeme) {
Some(o) => o,
None => return error!(&field.loc, format!("unknown field: {}", &field.lexeme)),
};
Ok(offset)
Ok(field.offset)
}
}

View File

@@ -2,6 +2,7 @@ mod analyzer;
mod codegen_x86_64;
mod parser;
mod tokenizer;
mod typechecker;
use std::{
fs,
@@ -13,55 +14,15 @@ use tokenizer::ZernError;
use clap::Parser;
macro_rules! compile_std {
($codegen:expr, $( $name:literal ),* $(,)? ) => {
$(
compile_file_to(
$codegen,
$name,
include_str!(concat!("std/", $name)).into()
)?;
)*
macro_rules! parse_std_file {
($statements:expr, $filename:expr) => {
let source: String = include_str!($filename).into();
let tokenizer = tokenizer::Tokenizer::new($filename.to_owned(), source);
let parser = parser::Parser::new(tokenizer.tokenize()?);
$statements.extend(parser.parse()?);
};
}
fn compile_file_to(
codegen: &mut codegen_x86_64::CodegenX86_64,
filename: &str,
source: String,
) -> Result<(), ZernError> {
let tokenizer = tokenizer::Tokenizer::new(filename.to_owned(), source);
let tokens = tokenizer.tokenize()?;
let parser = parser::Parser::new(tokens);
let statements = parser.parse()?;
for stmt in &statements {
codegen.analyzer.register_function(stmt)?;
}
for stmt in &statements {
codegen.analyzer.analyze_stmt(stmt)?;
}
for stmt in statements {
// top level statements are all function/const/extern declarations so a new env for each
codegen.compile_stmt(&mut codegen_x86_64::Env::new(), &stmt)?;
}
Ok(())
}
fn run_command(cmd: String) {
if !Command::new("sh")
.args(["-c", &cmd])
.status()
.unwrap()
.success()
{
process::exit(1);
}
}
fn compile_file(args: Args) -> Result<(), ZernError> {
let source = match fs::read_to_string(&args.path) {
Ok(x) => x,
@@ -73,10 +34,34 @@ fn compile_file(args: Args) -> Result<(), ZernError> {
let filename = Path::new(&args.path).file_name().unwrap().to_str().unwrap();
let mut codegen = codegen_x86_64::CodegenX86_64::new();
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");
let tokenizer = tokenizer::Tokenizer::new(filename.to_owned(), source);
let parser = parser::Parser::new(tokenizer.tokenize()?);
statements.extend(parser.parse()?);
let mut analyzer = analyzer::Analyzer::new();
for stmt in &statements {
analyzer.register_function(stmt)?;
}
for stmt in &statements {
analyzer.analyze_stmt(stmt)?;
}
let mut typechecker = typechecker::TypeChecker::new(&analyzer);
for stmt in &statements {
typechecker.typecheck_stmt(&mut typechecker::Env::new(), stmt)?;
}
let mut codegen = codegen_x86_64::CodegenX86_64::new(&analyzer);
codegen.emit_prologue(args.use_gcc)?;
compile_std!(&mut codegen, "syscalls.zr", "std.zr", "net.zr");
compile_file_to(&mut codegen, filename, source)?;
for stmt in statements {
codegen.compile_stmt(&mut codegen_x86_64::Env::new(), &stmt)?;
}
if !args.output_asm {
let out = args.out.unwrap_or_else(|| "out".into());
@@ -116,6 +101,17 @@ fn compile_file(args: Args) -> Result<(), ZernError> {
Ok(())
}
fn run_command(cmd: String) {
if !Command::new("sh")
.args(["-c", &cmd])
.status()
.unwrap()
.success()
{
process::exit(1);
}
}
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {

View File

@@ -20,11 +20,13 @@ pub enum Stmt {
},
Block(Vec<Stmt>),
If {
keyword: Token,
condition: Expr,
then_branch: Box<Stmt>,
else_branch: Box<Stmt>,
},
While {
keyword: Token,
condition: Expr,
body: Box<Stmt>,
},
@@ -41,7 +43,10 @@ pub enum Stmt {
body: Box<Stmt>,
exported: bool,
},
Return(Expr),
Return {
expr: Expr,
keyword: Token,
},
Break,
Continue,
Extern(Token),
@@ -83,6 +88,7 @@ pub enum Expr {
ArrayLiteral(Vec<Expr>),
Index {
expr: Box<Expr>,
bracket: Token,
index: Box<Expr>,
},
AddrOf {
@@ -94,6 +100,10 @@ pub enum Expr {
left: Box<Expr>,
field: Token,
},
Cast {
expr: Box<Expr>,
type_name: Token,
},
}
pub struct Parser {
@@ -257,7 +267,11 @@ impl Parser {
} else if self.match_token(&[TokenType::KeywordFor]) {
self.for_statement()
} else if self.match_token(&[TokenType::KeywordReturn]) {
Ok(Stmt::Return(self.expression()?))
let keyword = self.previous().clone();
Ok(Stmt::Return {
expr: self.expression()?,
keyword,
})
} else if self.match_token(&[TokenType::KeywordBreak]) {
Ok(Stmt::Break)
} else if self.match_token(&[TokenType::KeywordContinue]) {
@@ -268,6 +282,7 @@ impl Parser {
}
fn if_statement(&mut self) -> Result<Stmt, ZernError> {
let keyword = self.previous().clone();
let condition = self.expression()?;
let then_branch = self.block()?;
let else_branch = if self.match_token(&[TokenType::KeywordElse]) {
@@ -280,6 +295,7 @@ impl Parser {
Box::new(Stmt::Block(vec![]))
};
Ok(Stmt::If {
keyword,
condition,
then_branch: Box::new(then_branch),
else_branch,
@@ -287,9 +303,11 @@ impl Parser {
}
fn while_statement(&mut self) -> Result<Stmt, ZernError> {
let keyword = self.previous().clone();
let condition = self.expression()?;
let body = self.block()?;
Ok(Stmt::While {
keyword,
condition,
body: Box::new(body),
})
@@ -438,7 +456,7 @@ impl Parser {
}
fn factor(&mut self) -> Result<Expr, ZernError> {
let mut expr = self.unary()?;
let mut expr = self.cast()?;
while self.match_token(&[
TokenType::Star,
@@ -459,6 +477,20 @@ impl Parser {
Ok(expr)
}
fn cast(&mut self) -> Result<Expr, ZernError> {
let mut expr = self.unary()?;
while self.match_token(&[TokenType::KeywordAs]) {
let type_name = self.consume(TokenType::Identifier, "expected type after 'as'")?;
expr = Expr::Cast {
expr: Box::new(expr),
type_name,
};
}
Ok(expr)
}
fn unary(&mut self) -> Result<Expr, ZernError> {
if self.match_token(&[TokenType::Xor]) {
let op = self.previous().clone();
@@ -504,9 +536,10 @@ impl Parser {
};
} else if self.match_token(&[TokenType::LeftBracket]) {
let index = self.expression()?;
self.consume(TokenType::RightBracket, "expected ']' after index")?;
let bracket = self.consume(TokenType::RightBracket, "expected ']' after index")?;
expr = Expr::Index {
expr: Box::new(expr),
bracket,
index: Box::new(index),
}
} else if self.match_token(&[TokenType::Arrow]) {

View File

@@ -101,7 +101,7 @@ func net.create_udp_client?[packed_host: i64, port: i64] : net.UDPSocket
if s->fd < 0
mem.free(s)
err.set(ERR_SYSCALL_FAILED, "net.create_udp_client?: failed to create a socket")
return 0
return 0 as net.UDPSocket
s->addr = mem.alloc(16)
mem.zero(s->addr, 16)
@@ -115,16 +115,16 @@ func net.create_udp_client?[packed_host: i64, port: i64] : net.UDPSocket
s->addr[7] = packed_host & 255
return s
func net.udp_listen?[packed_host: i64, port: i64] : net.UDPSocket
func net.create_udp_server?[packed_host: i64, port: i64] : net.UDPSocket
err.clear()
let s: net.UDPSocket = net.create_udp_client?(packed_host, port)
if err.check()
return 0
return 0 as net.UDPSocket
if _builtin_syscall(SYS_bind, s->fd, s->addr, 16) < 0
net.UDPSocket.close_and_free(s)
err.set(ERR_SYSCALL_FAILED, "net.udp_listen?: failed to bind")
return 0
err.set(ERR_SYSCALL_FAILED, "net.create_udp_server?: failed to bind")
return 0 as net.UDPSocket
return s
@@ -166,7 +166,7 @@ func net.encode_dns_name[domain: str] : io.Buffer
let part_len: i64 = i - part_start
buf->data[out_pos] = part_len & 0xff
out_pos = out_pos + 1
mem.copy(domain + part_start, buf->data + out_pos, part_len)
mem.copy(domain as ptr + part_start, buf->data + out_pos, part_len)
out_pos = out_pos + part_len
part_start = i + 1
i = i + 1
@@ -221,6 +221,6 @@ func net.resolve?[domain: str] : i64
pos = pos + 12 // skip name(2), type(2), class(2), ttl(4), rdlength(2)
let out: i64 = net.pack_addr(pkt->data[pos], pkt->data[pos+1], pkt->data[pos+2], pkt->data[pos+3])
let out: i64 = net.pack_addr(pkt->data[pos] as i64, pkt->data[pos+1] as i64, pkt->data[pos+2] as i64, pkt->data[pos+3] as i64)
net.UDPPacket.free(pkt)
return out

View File

@@ -10,13 +10,13 @@ func err.code[] : i64
return mem.read64(_builtin_err_code())
func err.msg[] : str
return mem.read64(_builtin_err_msg())
return mem.read64(_builtin_err_msg()) as str
func err.check[] : bool
return err.code() != 0
func err.clear[] : void
err.set(0, 0)
err.set(0, 0 as str)
func err.set[code: i64, msg: str] : void
mem.write64(_builtin_err_code(), code)
@@ -47,13 +47,13 @@ func mem._align[x: i64] : i64
func mem._split_block[blk: mem.Block, needed: i64] : void
if blk->size >= needed + MEM_BLOCK_SIZE + 8
let new_blk: mem.Block = blk + MEM_BLOCK_SIZE + needed
let new_blk: mem.Block = (blk as ptr + MEM_BLOCK_SIZE + needed) as mem.Block
new_blk->size = blk->size - needed - MEM_BLOCK_SIZE
new_blk->free = true
new_blk->next = blk->next
new_blk->prev = blk
if blk->next
if blk->next as ptr
let next: mem.Block = blk->next
next->prev = new_blk
else
@@ -70,25 +70,27 @@ func mem._request_space?[size: i64] : mem.Block
// PROT_READ | PROT_WRITE = 3
// MAP_PRIVATE | MAP_ANONYMOUS = 34
// fd = -1, offset = 0
let blk: mem.Block = _builtin_syscall(SYS_mmap, 0, alloc_size, 3, 34, -1, 0)
if blk == -1
let blk_ptr: ptr = _builtin_syscall(SYS_mmap, 0, alloc_size, 3, 34, -1, 0) as ptr
if blk_ptr == -1
err.set(ERR_ALLOC_FAILED, "mem._request_space: mmap failed")
return 0
return 0 as mem.Block
let blk: mem.Block = blk_ptr as mem.Block
blk->size = alloc_size - MEM_BLOCK_SIZE
blk->free = false
blk->next = 0
blk->prev = 0
blk->next = 0 as mem.Block
blk->prev = 0 as mem.Block
let tail: mem.Block = mem.read64(_builtin_heap_tail())
if tail
let tail: mem.Block = mem.read64(_builtin_heap_tail()) as mem.Block
if tail as ptr
tail->next = blk
blk->prev = tail
mem.write64(_builtin_heap_tail(), blk)
return blk
func mem.alloc?[size: i64] : ptr
func mem.alloc?[size: i64] : any
err.clear()
if size <= 0
err.set(ERR_ALLOC_FAILED, "mem.alloc? called with non-positive size")
@@ -96,12 +98,12 @@ func mem.alloc?[size: i64] : ptr
size = mem._align(size)
let cur: mem.Block = mem.read64(_builtin_heap_head())
while cur
let cur: mem.Block = mem.read64(_builtin_heap_head()) as mem.Block
while cur as ptr
if cur->free && cur->size >= size
mem._split_block(cur, size)
cur->free = false
return cur + MEM_BLOCK_SIZE
return cur as ptr + MEM_BLOCK_SIZE
cur = cur->next
let blk: mem.Block = mem._request_space?(size)
@@ -113,51 +115,51 @@ func mem.alloc?[size: i64] : ptr
mem._split_block(blk, size)
return blk + MEM_BLOCK_SIZE
return blk as ptr + MEM_BLOCK_SIZE
func mem.alloc[size: i64] : ptr
func mem.alloc[size: i64] : any
return must(mem.alloc?(size))
func mem.free[x: ptr] : void
if !x
func mem.free[x: any] : void
if !(x as ptr)
return 0
let blk: mem.Block = x - MEM_BLOCK_SIZE
let blk: mem.Block = (x as ptr - MEM_BLOCK_SIZE) as mem.Block
blk->free = true
let next: mem.Block = blk->next
if next && next->free
let expected_next: mem.Block = blk + MEM_BLOCK_SIZE + blk->size
if expected_next == next
if next as ptr && next->free
let expected_next: mem.Block = (blk as ptr + MEM_BLOCK_SIZE + blk->size) as mem.Block
if expected_next as ptr == next as ptr
blk->size = blk->size + MEM_BLOCK_SIZE + next->size
blk->next = next->next
if next->next
if next->next as ptr
let b: mem.Block = next->next
b->prev = blk
if mem.read64(_builtin_heap_tail()) == next
if mem.read64(_builtin_heap_tail()) == next as ptr
mem.write64(_builtin_heap_tail(), blk)
let prev: mem.Block = blk->prev
if prev && prev->free
let expected_blk: mem.Block = prev + MEM_BLOCK_SIZE + prev->size
if expected_blk == blk
if prev as ptr && prev->free
let expected_blk: mem.Block = (prev as ptr + MEM_BLOCK_SIZE + prev->size) as mem.Block
if expected_blk as ptr == blk as ptr
prev->size = prev->size + MEM_BLOCK_SIZE + blk->size
prev->next = blk->next
if blk->next
if blk->next as ptr
let b: mem.Block = blk->next
b->prev = prev
if mem.read64(_builtin_heap_tail()) == blk
if mem.read64(_builtin_heap_tail()) == blk as ptr
mem.write64(_builtin_heap_tail(), prev)
blk = prev
let block_total: i64 = blk->size + MEM_BLOCK_SIZE
if (blk & 4095) == 0 && (block_total & 4095) == 0
if blk->prev
if (blk as i64 & 4095) == 0 && (block_total & 4095) == 0
if blk->prev as ptr
let b: mem.Block = blk->prev
b->next = blk->next
else
mem.write64(_builtin_heap_head(), blk->next)
if blk->next
if blk->next as ptr
let b: mem.Block = blk->next
b->prev = blk->prev
else
@@ -171,38 +173,38 @@ func mem.realloc?[x: ptr, new_size: i64] : ptr
if new_size < 0
err.set(ERR_ALLOC_FAILED, "mem.realloc? called with negative size")
return 0
return 0 as ptr
if new_size == 0
mem.free(x)
return 0
return 0 as ptr
new_size = mem._align(new_size)
let blk: mem.Block = x - MEM_BLOCK_SIZE
let blk: mem.Block = (x - MEM_BLOCK_SIZE) as mem.Block
if blk->size >= new_size
mem._split_block(blk, new_size)
return x
let next: mem.Block = blk->next
let expected_next: mem.Block = blk + MEM_BLOCK_SIZE + blk->size
let expected_next: mem.Block = (blk as ptr + MEM_BLOCK_SIZE + blk->size) as mem.Block
if next && next->free && expected_next == next
if next as ptr && next->free && expected_next as ptr == next as ptr
let combined: i64 = blk->size + MEM_BLOCK_SIZE + next->size
if combined >= new_size
blk->size = combined
blk->next = next->next
if next->next
if next->next as ptr
let b: mem.Block = next->next
b->prev = blk
if mem.read64(_builtin_heap_tail()) == next
if mem.read64(_builtin_heap_tail()) == next as ptr
mem.write64(_builtin_heap_tail(), blk)
mem._split_block(blk, new_size)
return x
let new_ptr: ptr = mem.alloc?(new_size)
if err.check()
return 0
return 0 as ptr
mem.copy(x, new_ptr, math.min(blk->size, new_size))
mem.free(x)
@@ -214,7 +216,7 @@ func mem.zero_and_free[x: ptr, size: i64] : void
func mem.zero[x: ptr, size: i64] : void
for i in 0..size
x[i] = 0
x[i] = 0 as u8
func mem.copy[src: ptr, dst: ptr, n: i64] : void
if dst > src
@@ -230,16 +232,16 @@ func mem.read8[x: ptr] : u8
return x[0]
func mem.read16[x: ptr] : i64
return x[0] | (x[1] << 8)
return x[0] as i64 | (x[1] << 8)
func mem.read16be[x: ptr] : i64
return (x[0] << 8) | x[1]
return (x[0] << 8) as i64 | x[1]
func mem.read32[x: ptr] : i64
return x[0] | (x[1] << 8) | (x[2] << 16) | (x[3] << 24)
return x[0] as i64 | (x[1] << 8) | (x[2] << 16) | (x[3] << 24)
func mem.read32be[x: ptr] : i64
return (x[0] << 24) | (x[1] << 16) | (x[2] << 8) | x[3]
return (x[0] as i64 << 24) | (x[1] << 16) | (x[2] << 8) | x[3]
func mem.read64[x: ptr] : i64
return _builtin_read64(x)
@@ -258,11 +260,11 @@ func mem.write16be[x: ptr, d: i64] : void
x[0] = (d >> 8) & 0xff
x[1] = d & 0xff
func mem.write64[x: ptr, d: i64] : void
func mem.write64[x: ptr, d: any] : void
_builtin_set64(x, d)
// see emit_prologue for the io.printf wrapper
func io._printf_impl[s: str, args: ptr] : void
func io._printf_impl[s: ptr, args: ptr] : void
while s[0]
if s[0] == '%'
s = s + 1
@@ -274,10 +276,10 @@ func io._printf_impl[s: str, args: ptr] : void
io.print_i64_hex(mem.read64(args))
args = args + 8
else if s[0] == 's'
io.print(mem.read64(args))
io.print(mem.read64(args) as str)
args = args + 8
else if s[0] == 'c'
io.print_char(mem.read64(args))
io.print_char(mem.read64(args) as u8)
args = args + 8
else if s[0] == '%'
io.print_char('%')
@@ -287,11 +289,11 @@ func io._printf_impl[s: str, args: ptr] : void
io.print_char(s[0])
s = s + 1
func io.print_sized[x: str, size: i64] : void
func io.print_sized[x: ptr, size: i64] : void
_builtin_syscall(SYS_write, 1, x, size)
func io.print[x: str] : void
io.print_sized(x, str.len(x))
io.print_sized(x as ptr, str.len(x))
func io.println[x: str] : void
io.print(x)
@@ -322,7 +324,7 @@ func io.println_i64[x: i64] : void
mem.free(s)
func io.read_char[] : u8
let c: u8 = 0
let c: u8 = 0 as u8
_builtin_syscall(SYS_read, 0, ^c, 1)
return c
@@ -333,7 +335,7 @@ func io.read_line[]: str
if n < 0
n = 0
buffer[n] = 0
buffer = must(mem.realloc?(buffer, n + 1))
buffer = must(mem.realloc?(buffer as ptr, n + 1))
return buffer
struct io.Buffer
@@ -347,7 +349,7 @@ func io.Buffer.alloc?[size: i64] : io.Buffer
buffer->data = mem.alloc?(size)
if err.check()
mem.free(buffer)
return 0
return 0 as io.Buffer
return buffer
func io.Buffer.free[buf: io.Buffer] : void
@@ -362,15 +364,15 @@ func io.read_text_file?[path: str] : str
let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0, 0)
if fd < 0
err.set(ERR_OPEN_FAILED, "io.read_text_file?: failed to open file")
return 0
return 0 as str
let size: i64 = _builtin_syscall(SYS_lseek, fd, 0, 2)
_builtin_syscall(SYS_lseek, fd, 0, 0)
let buffer: str = mem.alloc?(size + 1)
let buffer: str = mem.alloc?(size + 1) as str
if err.check()
_builtin_syscall(SYS_close, fd)
return 0
return 0 as str
let n: i64 = _builtin_syscall(SYS_read, fd, buffer, size)
_builtin_syscall(SYS_close, fd)
@@ -378,7 +380,7 @@ func io.read_text_file?[path: str] : str
if n != size
mem.free(buffer)
err.set(ERR_READ_FAILED, "io.read_text_file?: failed to read file")
return 0
return 0 as str
buffer[n] = 0
return buffer
@@ -388,7 +390,7 @@ func io.read_binary_file?[path: str] : io.Buffer
let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0, 0)
if fd < 0
err.set(ERR_OPEN_FAILED, "io.read_binary_file?: failed to open file")
return 0
return 0 as io.Buffer
let buf: io.Buffer = new io.Buffer
buf->size = _builtin_syscall(SYS_lseek, fd, 0, 2)
@@ -398,7 +400,7 @@ func io.read_binary_file?[path: str] : io.Buffer
if err.check()
io.Buffer.free(buf)
_builtin_syscall(SYS_close, fd)
return 0
return 0 as io.Buffer
let n: i64 = _builtin_syscall(SYS_read, fd, buf->data, buf->size)
_builtin_syscall(SYS_close, fd)
@@ -406,12 +408,12 @@ func io.read_binary_file?[path: str] : io.Buffer
if n != buf->size
io.Buffer.free(buf)
err.set(ERR_READ_FAILED, "io.read_binary_file?: failed to read file")
return 0
return 0 as io.Buffer
return buf
func io.write_file?[path: str, content: str] : void
io.write_binary_file?(path, content, str.len(content))
io.write_binary_file?(path, content as ptr, str.len(content))
func io.write_binary_file?[path: str, content: ptr, size: i64] : void
err.clear()
@@ -435,7 +437,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)
mem.copy(s, dup, size)
mem.copy(s as ptr, dup as ptr, size)
return dup
func str.equal[a: str, b: str] : bool
@@ -471,8 +473,8 @@ 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)
mem.copy(a, out, a_len)
mem.copy(b, out + a_len, b_len)
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
return out
@@ -504,7 +506,7 @@ func str.substr[s: str, start: i64, length: i64] : str
panic("str.substr out of bounds")
let out: str = mem.alloc(length + 1)
mem.copy(s + start, out, length)
mem.copy(s as ptr + start, out as ptr, length)
out[length] = 0
return out
@@ -591,7 +593,7 @@ func str.from_i64[n: i64] : str
if neg
buf[end] = '-'
end = end - 1
let s: str = str.make_copy(buf + end + 1)
let s: str = str.make_copy((buf as ptr + end + 1) as str)
mem.free(buf)
return s
@@ -662,7 +664,7 @@ func str.hex_encode[s: str, s_len: i64] : str
out[j] = 0
return out
func str._hex_digit_to_int[d: u8] : i64
func str._hex_digit_to_int[d: u8] : u8
if d >= 'a' && d <= 'f'
return d - 'a' + 10
if d >= 'A' && d <= 'F'
@@ -769,10 +771,10 @@ struct Array
func array.new[] : Array
return new Array
func array.new_with_size_zeroed[size: i64] : Array
func array.new_preallocated_and_zeroed[size: i64] : Array
let xs: Array = new Array
if size > 0
xs->data = must(mem.realloc?(xs->data, size * 8))
xs->data = must(mem.realloc?(xs->data, size * 8)) as ptr
mem.zero(xs->data, size * 8)
xs->size = size
xs->capacity = size
@@ -815,13 +817,13 @@ func array.slice[xs: Array, start: i64, length: i64] : Array
if start < 0 || length < 0 || start + length > xs->size
panic("array.slice out of bounds")
let new_array: Array = array.new_with_size_zeroed(length)
let new_array: Array = array.new_preallocated_and_zeroed(length)
for i in 0..length
array.set(new_array, i, array.nth(xs, start + i))
return new_array
func array.concat[a: Array, b: Array] : Array
let new_array: Array = array.new_with_size_zeroed(a->size + b->size)
let new_array: Array = array.new_preallocated_and_zeroed(a->size + b->size)
for i in 0..a->size
array.set(new_array, i, array.nth(a, i))
for i in 0..b->size
@@ -841,7 +843,7 @@ func alg._partition[arr: Array, low: i64, high: i64] : i64
let pivot: i64 = array.nth(arr, high)
let i: i64 = low - 1
for j in (low)..high
if array.nth(arr, j) <= pivot
if array.nth(arr, j) as i64 <= pivot
i = i + 1
let temp: i64 = array.nth(arr ,i)
array.set(arr, i, array.nth(arr, j))
@@ -858,20 +860,20 @@ func alg.count[arr: Array, item: any] : i64
count = count + 1
return count
func alg.map[arr: Array, fn: ptr] : Array
let out: Array = array.new_with_size_zeroed(arr->size)
func alg.map[arr: Array, fn: fnptr] : Array
let out: Array = array.new_preallocated_and_zeroed(arr->size)
for i in 0..arr->size
array.set(out, i, fn(array.nth(arr, i)))
return out
func alg.filter[arr: Array, fn: ptr] : Array
func alg.filter[arr: Array, fn: fnptr] : Array
let out: Array = []
for i in 0..arr->size
if fn(array.nth(arr, i))
array.push(out, array.nth(arr, i))
return out
func alg.reduce[arr: Array, fn: ptr, acc: any] : any
func alg.reduce[arr: Array, fn: fnptr, acc: any] : any
for i in 0..arr->size
acc = fn(acc, array.nth(arr, i))
return acc
@@ -900,13 +902,13 @@ func os.urandom?[n: i64]: ptr
err.clear()
let buffer: ptr = mem.alloc(n)
if err.check()
return 0
return 0 as ptr
let fd: i64 = _builtin_syscall(SYS_openat, -100, "/dev/urandom", 0, 0)
if fd < 0
mem.free(buffer)
err.set(ERR_READ_FAILED, "os.urandom?: failed to open /dev/urandom")
return 0
return 0 as ptr
let bytes_read: i64 = _builtin_syscall(SYS_read, fd, buffer, n)
_builtin_syscall(SYS_close, fd)
@@ -914,7 +916,7 @@ func os.urandom?[n: i64]: ptr
if n != bytes_read
mem.free(buffer)
err.set(ERR_READ_FAILED, "os.urandom?: failed to read /dev/urandom")
return 0
return 0 as ptr
return buffer
@@ -966,7 +968,7 @@ func os.list_directory?[path: str] : Array
let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0, 0)
if fd < 0
err.set(ERR_OPEN_FAILED, "os.list_directory?: failed to open dir")
return 0
return 0 as Array
let files: Array = []
let buf: ptr = mem.alloc(1024)
@@ -981,14 +983,14 @@ func os.list_directory?[path: str] : Array
_builtin_syscall(SYS_close, fd)
err.set(ERR_SYSCALL_FAILED, "os.list_directory?: getdents64() failed")
return 0
return 0 as Array
else if n == 0
break
let pos = 0
while pos < n
let len: i64 = mem.read16(buf + pos + 16)
let name: str = buf + pos + 19
let name: ptr = buf + pos + 19
if name[0]
let skip: bool = false
// skip if name is exactly '.' or '..'
@@ -999,7 +1001,7 @@ func os.list_directory?[path: str] : Array
if name[2] == 0
skip = true
if !skip
array.push(files, str.make_copy(name))
array.push(files, str.make_copy(name as str))
pos = pos + len
mem.free(buf)

View File

@@ -55,6 +55,7 @@ pub enum TokenType {
KeywordExport,
KeywordStruct,
KeywordNew,
KeywordAs,
Indent,
Dedent,
@@ -371,6 +372,7 @@ impl Tokenizer {
"export" => TokenType::KeywordExport,
"struct" => TokenType::KeywordStruct,
"new" => TokenType::KeywordNew,
"as" => TokenType::KeywordAs,
"true" => TokenType::True,
"false" => TokenType::False,
_ => TokenType::Identifier,

481
src/typechecker.rs Normal file
View File

@@ -0,0 +1,481 @@
use std::collections::HashMap;
use crate::{
analyzer::{Analyzer, Type},
parser::{Expr, Stmt},
tokenizer::{TokenType, ZernError, error},
};
macro_rules! expect_type {
($expr_type:expr, $expected:expr, $loc:expr) => {{
let actual = $expr_type;
if $expected != "any" && actual != "any" && actual != $expected {
return error!(
$loc,
format!("expected type '{}', got '{}'", $expected, actual)
);
}
}};
}
macro_rules! expect_types {
($expr_type:expr, [$( $expected:expr ),+], $loc:expr) => {
if $expr_type != "any" && $( $expr_type != $expected )&&+ {
return error!(
$loc,
format!(
"expected one of [{}], got '{}'",
[$( $expected ),+].join(", "),
$expr_type
)
);
}
};
}
static BUILTIN_TYPES: [&str; 8] = ["void", "u8", "i64", "str", "bool", "ptr", "fnptr", "any"];
pub struct Env {
scopes: Vec<HashMap<String, Type>>,
}
impl Env {
pub fn new() -> Env {
Env {
scopes: vec![HashMap::new()],
}
}
pub fn push_scope(&mut self) {
self.scopes.push(HashMap::new());
}
pub fn pop_scope(&mut self) {
assert!(!self.scopes.is_empty());
self.scopes.pop();
}
pub fn define_var(&mut self, name: String, var_type: String) {
self.scopes.last_mut().unwrap().insert(name, var_type);
}
fn get_var_type(&self, name: &str) -> Option<&Type> {
for scope in self.scopes.iter().rev() {
if let Some(var) = scope.get(name) {
return Some(var);
}
}
None
}
}
pub struct TypeChecker<'a> {
analyzer: &'a Analyzer,
current_function_return_type: String,
}
impl<'a> TypeChecker<'a> {
pub fn new(analyzer: &'a Analyzer) -> TypeChecker<'a> {
TypeChecker {
analyzer,
current_function_return_type: String::new(),
}
}
pub fn typecheck_stmt(&mut self, env: &mut Env, stmt: &Stmt) -> Result<(), ZernError> {
match stmt {
Stmt::Expression(expr) => {
self.typecheck_expr(env, expr)?;
}
Stmt::Let {
name,
var_type,
initializer,
} => {
let mut actual_type = self.typecheck_expr(env, initializer)?;
if let Some(var_type) = var_type {
if !self.is_valid_type_name(&var_type.lexeme) {
return error!(
&name.loc,
"unrecognized type: ".to_owned() + &var_type.lexeme
);
}
expect_type!(actual_type.clone(), var_type.lexeme, var_type.loc);
if actual_type == "any" {
actual_type = var_type.lexeme.clone();
}
}
env.define_var(name.lexeme.clone(), actual_type);
}
Stmt::Const { name: _, value: _ } => {
// handled in the analyzer
}
Stmt::Block(stmts) => {
env.push_scope();
for stmt in stmts {
self.typecheck_stmt(env, stmt)?;
}
env.pop_scope();
}
Stmt::If {
keyword,
condition,
then_branch,
else_branch,
} => {
expect_types!(
self.typecheck_expr(env, condition)?,
["i64", "u8", "ptr", "bool"],
keyword.loc
);
self.typecheck_stmt(env, then_branch)?;
self.typecheck_stmt(env, else_branch)?;
}
Stmt::While {
keyword,
condition,
body,
} => {
expect_types!(
self.typecheck_expr(env, condition)?,
["i64", "u8", "ptr", "bool"],
keyword.loc
);
self.typecheck_stmt(env, body)?;
}
Stmt::For {
var,
start,
end,
body,
} => {
expect_type!(self.typecheck_expr(env, start)?, "i64", var.loc);
expect_type!(self.typecheck_expr(env, end)?, "i64", var.loc);
env.push_scope();
env.define_var(var.lexeme.clone(), "i64".into());
self.typecheck_stmt(env, body)?;
env.pop_scope();
}
Stmt::Function {
name: _,
params,
return_type,
body,
exported: _,
} => {
if !self.is_valid_type_name(&return_type.lexeme) {
return error!(
&return_type.loc,
"unrecognized type: ".to_owned() + &return_type.lexeme
);
}
self.current_function_return_type = return_type.lexeme.clone();
env.push_scope();
for param in params {
if !self.is_valid_type_name(&param.var_type.lexeme) {
return error!(
&param.var_name.loc,
"unrecognized type: ".to_owned() + &param.var_type.lexeme
);
}
env.define_var(param.var_name.lexeme.clone(), param.var_type.lexeme.clone());
}
self.typecheck_stmt(env, body)?;
env.pop_scope();
}
Stmt::Return { expr, keyword } => {
let expected = if self.current_function_return_type == "void" {
"i64".into()
} else {
self.current_function_return_type.clone()
};
expect_type!(self.typecheck_expr(env, expr)?, expected, keyword.loc);
}
Stmt::Break => {}
Stmt::Continue => {}
Stmt::Extern(_) => {
// handled in the analyzer
}
Stmt::Struct { name: _, fields } => {
for field in fields {
if !self.is_valid_type_name(&field.var_type.lexeme) {
return error!(
&field.var_type.loc,
format!("unknown type: {}", &field.var_type.lexeme)
);
}
}
}
}
Ok(())
}
pub fn typecheck_expr(&self, env: &mut Env, expr: &Expr) -> Result<Type, ZernError> {
match expr {
Expr::Binary { left, op, right } => {
let left_type = self.typecheck_expr(env, left)?;
match op.token_type {
TokenType::Plus | TokenType::Minus => {
expect_types!(left_type, ["i64", "ptr", "u8"], op.loc);
expect_types!(
self.typecheck_expr(env, right)?,
["i64", "ptr", "u8"],
op.loc
);
Ok(left_type)
}
TokenType::Star
| TokenType::Slash
| TokenType::Mod
| TokenType::Xor
| TokenType::BitAnd
| TokenType::BitOr
| TokenType::ShiftLeft
| TokenType::ShiftRight => {
expect_types!(left_type, ["i64", "u8"], op.loc);
expect_types!(self.typecheck_expr(env, right)?, ["i64", "u8"], op.loc);
Ok(left_type)
}
TokenType::DoubleEqual
| TokenType::NotEqual
| TokenType::Greater
| TokenType::GreaterEqual
| TokenType::Less
| TokenType::LessEqual => {
expect_types!(left_type, ["i64", "ptr", "u8"], op.loc);
expect_types!(
self.typecheck_expr(env, right)?,
["i64", "ptr", "u8"],
op.loc
);
Ok("bool".into())
}
_ => unreachable!(),
}
}
Expr::Logical { left, op, right } => {
expect_types!(
self.typecheck_expr(env, left)?,
["bool", "i64", "ptr"],
op.loc
);
expect_types!(
self.typecheck_expr(env, right)?,
["bool", "i64", "ptr"],
op.loc
);
Ok("bool".into())
}
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::True => Ok("bool".into()),
TokenType::False => Ok("bool".into()),
_ => unreachable!(),
},
Expr::Unary { op, right } => {
let right_type = self.typecheck_expr(env, right)?;
match op.token_type {
TokenType::Minus => {
expect_type!(right_type, "i64", op.loc);
Ok("i64".into())
}
TokenType::Bang => {
expect_types!(right_type, ["bool", "i64", "ptr", "u8"], op.loc);
Ok("bool".into())
}
_ => unreachable!(),
}
}
Expr::Variable(name) => {
if self.analyzer.constants.contains_key(&name.lexeme) {
Ok("i64".into())
} else {
match env.get_var_type(&name.lexeme) {
Some(x) => Ok(x.clone()),
None => error!(name.loc, format!("undefined variable: {}", &name.lexeme)),
}
}
}
Expr::Assign { left, op, value } => {
let value_type = self.typecheck_expr(env, value)?;
match left.as_ref() {
Expr::Variable(name) => {
let existing_var_type = match env.get_var_type(&name.lexeme) {
Some(x) => x,
None => {
return error!(
name.loc,
format!("undefined variable: {}", &name.lexeme)
);
}
};
expect_type!(value_type.clone(), *existing_var_type, name.loc);
}
Expr::Index {
expr,
bracket,
index,
} => {
expect_types!(self.typecheck_expr(env, expr)?, ["ptr", "str"], bracket.loc);
expect_types!(self.typecheck_expr(env, index)?, ["i64", "u8"], bracket.loc);
expect_types!(value_type.clone(), ["u8", "i64"], bracket.loc);
}
Expr::MemberAccess { left, field } => {
let left_type = self.typecheck_expr(env, left)?;
let fields = match self.analyzer.structs.get(&left_type) {
Some(f) => f,
None => {
return error!(
&field.loc,
format!("unknown struct type: {}", left_type)
);
}
};
let f = match fields.get(&field.lexeme) {
Some(o) => o,
None => {
return error!(
&field.loc,
format!("unknown field: {}", &field.lexeme)
);
}
};
expect_type!(value_type.clone(), f.field_type, field.loc);
}
_ => return error!(&op.loc, "invalid assignment target"),
}
Ok(value_type)
}
Expr::Call {
callee,
paren,
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];
// its a function (defined/builtin/extern)
if let Some(params) = fn_type.params.clone() {
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 {
// its a variadic function, cant check arg types
for arg in args {
self.typecheck_expr(env, arg)?;
}
}
Ok(fn_type.return_type.clone())
} else {
// its a variable containing function address
expect_type!(self.typecheck_expr(env, callee)?, "fnptr", paren.loc);
for arg in args {
self.typecheck_expr(env, arg)?;
}
Ok("any".into())
}
} else {
// its an expression that evalutes to function address
expect_type!(self.typecheck_expr(env, callee)?, "fnptr", paren.loc);
for arg in args {
self.typecheck_expr(env, arg)?;
}
Ok("any".into())
}
}
Expr::ArrayLiteral(exprs) => {
for expr in exprs {
self.typecheck_expr(env, expr)?;
}
Ok("Array".into())
}
Expr::Index {
expr,
bracket,
index,
} => {
expect_types!(self.typecheck_expr(env, expr)?, ["ptr", "str"], bracket.loc);
expect_types!(self.typecheck_expr(env, index)?, ["i64", "u8"], bracket.loc);
Ok("u8".into())
}
Expr::AddrOf { op, expr } => match expr.as_ref() {
Expr::Variable(name) => {
if self.analyzer.functions.contains_key(&name.lexeme) {
Ok("fnptr".into())
} else {
Ok("ptr".into())
}
}
_ => {
error!(&op.loc, "can only take address of variables and functions")
}
},
Expr::New(struct_name) => {
if !self.analyzer.structs.contains_key(&struct_name.lexeme) {
return error!(
&struct_name.loc,
format!("unknown struct name: {}", &struct_name.lexeme)
);
}
Ok(struct_name.lexeme.clone())
}
Expr::MemberAccess { left, field } => {
let left_type = self.typecheck_expr(env, left)?;
let fields = match self.analyzer.structs.get(&left_type) {
Some(f) => f,
None => {
return error!(&field.loc, format!("unknown struct type: {}", left_type));
}
};
let field = match fields.get(&field.lexeme) {
Some(o) => o,
None => return error!(&field.loc, format!("unknown field: {}", &field.lexeme)),
};
Ok(field.field_type.clone())
}
Expr::Cast { expr, type_name } => {
self.typecheck_expr(env, expr)?;
if !self.is_valid_type_name(&type_name.lexeme) {
return error!(
&type_name.loc,
format!("unknown type: {}", &type_name.lexeme)
);
}
Ok(type_name.lexeme.clone())
}
}
}
fn is_valid_type_name(&self, name: &str) -> bool {
if BUILTIN_TYPES.contains(&name) {
return true;
}
if self.analyzer.structs.contains_key(name) {
return true;
}
false
}
}