structs r a lot more sane in code, can now actually assign & stuff

This commit is contained in:
2025-04-08 20:00:16 -04:00
parent cb9a366f43
commit 26e7a4da4a
21 changed files with 405 additions and 197 deletions

19
README.md Normal file
View File

@@ -0,0 +1,19 @@
# the
my child (programming language)
everything is subject to change rn, and this probably isn't up to date
`cargo run -- data/test.lang`
currently working!!:
- functions (arguments, returning)
- assembly blocks (input, output for expression)
- kind of structs (construction, field access, but not modifying lmao)
todo:
- actually handle jumps & LIs that are too large
- iterators?
- borrow checking
- multiple var instances for struct fields / fix all that stuff
- basic optimization: use registers, remove temp var moves

View File

@@ -1,6 +1,7 @@
struct Test { struct Test {
a: 64, a: 64,
b: 64, b: 64,
c: 64,
} }
fn start() { fn start() {
@@ -26,11 +27,13 @@ fn start() {
print(tester()); print(tester());
let test = Test { let test = Test {
a: 10, a: 10,
b: 9, b: 4,
c: 0,
}; };
structer(test);
arger("a", "b", "c"); arger("a", "b", "c");
let z = sub(test.a, 10); let z = sub(test.a, 10);
// exit(add(mul(sub(add(5, test.b), 1), 3), z)); print_hex(add(mul(sub(add(10, test.b), 1), 3), z));
print("test: 0x"); print("test: 0x");
print_hex(31); print_hex(31);
println(""); println("");
@@ -38,6 +41,17 @@ fn start() {
} }
fn structer(test: Test) { fn structer(test: Test) {
print("test {\n a: ");
print_dec(test.a);
print("\n b: ");
print_dec(test.b);
print("\n c: ");
print_dec(test.c);
println("\n}");
print("update c: ");
test.c = add(test.a, test.b);
print_dec(test.c);
println("");
} }
fn thinger() { fn thinger() {
@@ -64,7 +78,7 @@ fn print(msg: slice<8>) {
} }
fn print_hex(x: 64) { fn print_hex(x: 64) {
let i = 32; let i = 64;
loop { loop {
i = sub(i, 4); i = sub(i, 4);
let c = and(shr(x, i), 15); let c = and(shr(x, i), 15);
@@ -84,6 +98,45 @@ fn print_hex(x: 64) {
} }
} }
fn print_dec(x: 64) {
let i = 1;
loop {
if gt(i, x) {
if lt(i, 2) {
print("0");
return;
};
break;
};
i = mul(i, 10);
};
let found = 0;
loop {
i = div(i, 10);
let c = rem(div(x, i), 10);
if and(lt(c, 1), not(found)) {
continue;
};
found = 1;
if gt(c, 9) {
c = add(c, 7);
};
c = add(c, 48);
asm (a1 = &c) {
li a2, 1
li a0, 1
li a7, 64
ecall
};
if lt(i, 2) {
break;
};
};
if not(found) {
print("0");
}
}
fn add(a: 64, b: 64) -> 64 { fn add(a: 64, b: 64) -> 64 {
asm (t0 = a, t1 = b, out = t0) { asm (t0 = a, t1 = b, out = t0) {
add t0, t0, t1 add t0, t0, t1

View File

@@ -78,6 +78,10 @@ pub fn compile(program: &IRLProgram) -> UnlinkedProgram<LI> {
stack_len += align(&f.ret_size); stack_len += align(&f.ret_size);
} }
v.push(LI::addi(sp, sp, -stack_len)); v.push(LI::addi(sp, sp, -stack_len));
for (id, var) in &f.subvar_map {
// TODO: ALIGN DOES NOT MAKE SENSE HERE!!! need to choose to decide in lower or asm
stack.insert(id, stack[&var.id] + align(&var.offset));
}
let has_stack = stack_len > 0; let has_stack = stack_len > 0;
if has_stack { if has_stack {
if let Some(stack_ra) = stack_ra { if let Some(stack_ra) = stack_ra {
@@ -86,6 +90,15 @@ pub fn compile(program: &IRLProgram) -> UnlinkedProgram<LI> {
} }
let mut locations = HashMap::new(); let mut locations = HashMap::new();
let mut irli = Vec::new(); let mut irli = Vec::new();
let mut ret = Vec::new();
if has_stack {
if let Some(stack_ra) = stack_ra {
ret.push(LI::ld(ra, stack_ra, sp));
}
ret.push(LI::addi(sp, sp, stack_len));
}
ret.push(LI::Ret);
for i in &f.instructions { for i in &f.instructions {
irli.push((v.len(), format!("{i:?}"))); irli.push((v.len(), format!("{i:?}")));
match i { match i {
@@ -235,12 +248,15 @@ pub fn compile(program: &IRLProgram) -> UnlinkedProgram<LI> {
} }
} }
IRI::Ret { src } => { IRI::Ret { src } => {
if let Some(src) = src {
let Some(rva) = stack_rva else { let Some(rva) = stack_rva else {
panic!("no return value address on stack!") panic!("no return value address on stack!")
}; };
v.push(LI::ld(t0, rva, sp)); v.push(LI::ld(t0, rva, sp));
mov_mem(&mut v, sp, stack[src], t0, 0, t1, align(&f.ret_size) as u32); mov_mem(&mut v, sp, stack[src], t0, 0, t1, align(&f.ret_size) as u32);
} }
v.extend(&ret);
}
IRI::Jump(location) => { IRI::Jump(location) => {
v.push(LI::J(*location)); v.push(LI::J(*location));
} }
@@ -259,13 +275,6 @@ pub fn compile(program: &IRLProgram) -> UnlinkedProgram<LI> {
} }
} }
dbg.push_fn(irli); dbg.push_fn(irli);
if has_stack {
if let Some(stack_ra) = stack_ra {
v.push(LI::ld(ra, stack_ra, sp));
}
v.push(LI::addi(sp, sp, stack_len));
}
v.push(LI::Ret);
fns.push(UnlinkedFunction { fns.push(UnlinkedFunction {
instrs: v, instrs: v,
sym: *sym, sym: *sym,

View File

@@ -51,18 +51,19 @@ pub struct SectionHeader {
} }
// this is currently specialized for riscv64; obviously add params later // this is currently specialized for riscv64; obviously add params later
pub fn create(program: Vec<u8>, start_offset: Addr) -> Vec<u8> { pub fn create(program: &[u8], start_offset: Addr) -> Vec<u8> {
let addr_start = 0x1000; let addr_start = 0x1000;
let page_size = 0x1000; let page_size = 0x1000;
let progam_size = std::mem::size_of_val(&program[..]) as u64; // I don't know if I have to add addr_start here, idk how it maps the memory
let program_size = std::mem::size_of_val(program) as u64 + addr_start;
let program_header = ProgramHeader { let program_header = ProgramHeader {
ty: 0x1, // LOAD ty: 0x1, // LOAD
flags: 0b101, // executable, readable flags: 0b101, // executable, readable
offset: 0x0, offset: 0x0,
vaddr: addr_start, vaddr: addr_start,
paddr: addr_start, paddr: addr_start,
filesz: progam_size, filesz: program_size,
memsz: progam_size, memsz: program_size,
align: page_size, align: page_size,
}; };
let header_len = (size_of::<ELF64Header>() + size_of::<ProgramHeader>()) as u64; let header_len = (size_of::<ELF64Header>() + size_of::<ProgramHeader>()) as u64;
@@ -104,7 +105,7 @@ unsafe fn as_u8_slice<T: Sized>(p: &T) -> &[u8] {
} }
impl LinkedProgram { impl LinkedProgram {
pub fn to_elf(self) -> Vec<u8> { pub fn to_elf(&self) -> Vec<u8> {
create(self.code, self.start.expect("no start found")) create(&self.code, self.start.expect("no start found"))
} }
} }

View File

@@ -2,7 +2,7 @@ use std::collections::HashMap;
use crate::{ use crate::{
ir::Symbol, ir::Symbol,
util::{Labelable, LabeledFmt, Labeler}, util::{Labelable, LabeledFmt},
}; };
use super::debug::DebugInfo; use super::debug::DebugInfo;
@@ -26,7 +26,7 @@ pub struct UnlinkedFunction<I: Instr> {
pub locations: HashMap<usize, Symbol>, pub locations: HashMap<usize, Symbol>,
} }
impl<I: Instr> UnlinkedProgram<I> { impl<I: Instr + std::fmt::Debug> UnlinkedProgram<I> {
pub fn link(self) -> LinkedProgram { pub fn link(self) -> LinkedProgram {
let mut data = Vec::new(); let mut data = Vec::new();
let mut sym_table = SymTable::new(self.sym_count); let mut sym_table = SymTable::new(self.sym_count);

View File

@@ -1,14 +1,17 @@
use std::fmt::Debug; use std::fmt::Debug;
#[derive(Clone, Copy, Eq, Hash, PartialEq)] #[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub struct TypeID(pub usize); pub struct StructID(pub usize);
#[derive(Clone, Copy, Eq, Hash, PartialEq)] #[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub struct VarID(pub usize); pub struct VarID(pub usize);
#[derive(Clone, Copy, Eq, Hash, PartialEq)] #[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub struct FnID(pub usize); pub struct FnID(pub usize);
#[derive(Clone, Copy, Eq, Hash, PartialEq)] #[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub struct DataID(pub usize); pub struct DataID(pub usize);
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub struct FieldID(pub usize);
// I had an idea for why these were different... now I don't
pub type Size = u32; pub type Size = u32;
pub type Len = u32; pub type Len = u32;
@@ -18,7 +21,7 @@ impl Debug for VarID {
} }
} }
impl Debug for TypeID { impl Debug for StructID {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ty{}", self.0) write!(f, "ty{}", self.0)
} }
@@ -35,3 +38,9 @@ impl Debug for DataID {
write!(f, "data{}", self.0) write!(f, "data{}", self.0)
} }
} }
impl Debug for FieldID {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "field{}", self.0)
}
}

View File

@@ -7,6 +7,7 @@ use std::collections::HashMap;
pub struct IRLFunction { pub struct IRLFunction {
pub instructions: Vec<IRLInstruction>, pub instructions: Vec<IRLInstruction>,
pub stack: HashMap<VarID, Size>, pub stack: HashMap<VarID, Size>,
pub subvar_map: HashMap<VarID, VarOffset>,
pub args: Vec<(VarID, Size)>, pub args: Vec<(VarID, Size)>,
pub ret_size: Size, pub ret_size: Size,
pub makes_call: bool, pub makes_call: bool,
@@ -46,7 +47,7 @@ pub enum IRLInstruction {
outputs: Vec<(Reg, VarID)>, outputs: Vec<(Reg, VarID)>,
}, },
Ret { Ret {
src: VarID, src: Option<VarID>,
}, },
// TODO I feel like this should be turned into control flow instructions, maybe... // TODO I feel like this should be turned into control flow instructions, maybe...
// not sure but LLVM has them so might be right play; seems optimal for optimization // not sure but LLVM has them so might be right play; seems optimal for optimization
@@ -58,3 +59,11 @@ pub enum IRLInstruction {
Mark(Symbol), Mark(Symbol),
} }
impl IRLInstruction {
pub fn is_ret(&self) -> bool {
match self {
Self::Ret { .. } => true,
_ => false,
}
}
}

View File

@@ -1,6 +1,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::ir::{AsmBlockArgType, IRUFunction, IRUInstrInst, Size, SymbolSpace}; use crate::ir::{AsmBlockArgType, IRUFunction, IRUInstrInst, Size, SymbolSpace, VarOffset};
use super::{ use super::{
IRLFunction, IRLInstruction, IRUInstruction, IRUProgram, Len, Symbol, SymbolSpaceBuilder, Type, IRLFunction, IRLInstruction, IRUInstruction, IRUProgram, Len, Symbol, SymbolSpaceBuilder, Type,
@@ -31,6 +31,9 @@ impl IRLProgram {
for i in &f.instructions { for i in &f.instructions {
fbuilder.insert_instr(i); fbuilder.insert_instr(i);
} }
if fbuilder.instrs.last().is_none_or(|i| !i.is_ret()) {
fbuilder.instrs.push(IRLInstruction::Ret { src: None });
}
let res = fbuilder.finish(f); let res = fbuilder.finish(f);
ssbuilder.write_fn(sym, res, Some(f.name.clone())); ssbuilder.write_fn(sym, res, Some(f.name.clone()));
} }
@@ -48,8 +51,15 @@ pub struct IRLFunctionBuilder<'a> {
builder: &'a mut SymbolSpaceBuilder, builder: &'a mut SymbolSpaceBuilder,
instrs: Vec<IRLInstruction>, instrs: Vec<IRLInstruction>,
stack: HashMap<VarID, Size>, stack: HashMap<VarID, Size>,
subvar_map: HashMap<VarID, VarOffset>,
makes_call: bool, makes_call: bool,
outer: Option<Symbol>, loopp: Option<LoopCtx>,
}
#[derive(Clone, Copy)]
pub struct LoopCtx {
top: Symbol,
bot: Symbol,
} }
impl<'a> IRLFunctionBuilder<'a> { impl<'a> IRLFunctionBuilder<'a> {
@@ -57,27 +67,36 @@ impl<'a> IRLFunctionBuilder<'a> {
Self { Self {
instrs: Vec::new(), instrs: Vec::new(),
stack: HashMap::new(), stack: HashMap::new(),
subvar_map: HashMap::new(),
makes_call: false, makes_call: false,
program, program,
builder, builder,
outer: None, loopp: None,
} }
} }
pub fn alloc_stack(&mut self, i: VarID) -> Option<()> { pub fn alloc_stack(&mut self, i: VarID) -> Option<()> {
let size = *self if self.program.size_of_var(i).expect("unsized type") == 0 {
return None;
};
self.map_subvar(i);
let var = self.program.var_offset(i).expect("var offset");
*self
.stack .stack
.entry(i) .entry(var.id)
.or_insert(self.program.size_of_var(i).expect("unsized type")); .or_insert(self.program.size_of_var(var.id).expect("unsized type"));
if size == 0 {
None
} else {
Some(()) Some(())
} }
pub fn map_subvar(&mut self, i: VarID) {
let off = self.program.var_offset(i).expect("var offset");
if off.id != i {
self.subvar_map.insert(i, off);
}
} }
pub fn insert_instr(&mut self, i: &IRUInstrInst) -> Option<Option<String>> { pub fn insert_instr(&mut self, i: &IRUInstrInst) -> Option<Option<String>> {
match &i.i { match &i.i {
IRUInstruction::Mv { dest, src } => { IRUInstruction::Mv { dest, src } => {
self.alloc_stack(dest.id)?; self.alloc_stack(dest.id)?;
self.map_subvar(src.id);
self.instrs.push(IRLInstruction::Mv { self.instrs.push(IRLInstruction::Mv {
dest: dest.id, dest: dest.id,
dest_offset: 0, dest_offset: 0,
@@ -87,6 +106,7 @@ impl<'a> IRLFunctionBuilder<'a> {
} }
IRUInstruction::Ref { dest, src } => { IRUInstruction::Ref { dest, src } => {
self.alloc_stack(dest.id)?; self.alloc_stack(dest.id)?;
self.map_subvar(src.id);
self.instrs.push(IRLInstruction::Ref { self.instrs.push(IRLInstruction::Ref {
dest: dest.id, dest: dest.id,
src: src.id, src: src.id,
@@ -151,21 +171,28 @@ impl<'a> IRLFunctionBuilder<'a> {
} else { } else {
None None
}; };
self.instrs.push(IRLInstruction::Call { let call = IRLInstruction::Call {
dest, dest,
f: sym, f: sym,
args: args args: args
.iter() .iter()
.map(|a| (a.id, self.program.size_of_var(a.id).expect("unsized type"))) .map(|a| {
self.map_subvar(a.id);
(a.id, self.program.size_of_var(a.id).expect("unsized type"))
})
.collect(), .collect(),
}); };
self.instrs.push(call);
} }
IRUInstruction::AsmBlock { instructions, args } => { IRUInstruction::AsmBlock { instructions, args } => {
let mut inputs = Vec::new(); let mut inputs = Vec::new();
let mut outputs = Vec::new(); let mut outputs = Vec::new();
for a in args { for a in args {
match a.ty { match a.ty {
AsmBlockArgType::In => inputs.push((a.reg, a.var.id)), AsmBlockArgType::In => {
self.map_subvar(a.var.id);
inputs.push((a.reg, a.var.id))
}
AsmBlockArgType::Out => { AsmBlockArgType::Out => {
self.alloc_stack(a.var.id)?; self.alloc_stack(a.var.id)?;
outputs.push((a.reg, a.var.id)); outputs.push((a.reg, a.var.id));
@@ -178,50 +205,37 @@ impl<'a> IRLFunctionBuilder<'a> {
outputs, outputs,
}) })
} }
IRUInstruction::Ret { src } => self.instrs.push(IRLInstruction::Ret { src: src.id }), IRUInstruction::Ret { src } => {
self.map_subvar(src.id);
self.instrs.push(IRLInstruction::Ret {
src: if self.program.size_of_var(src.id).expect("unsized var") == 0 {
None
} else {
Some(src.id)
},
})
}
IRUInstruction::Construct { dest, fields } => { IRUInstruction::Construct { dest, fields } => {
self.alloc_stack(dest.id)?; self.alloc_stack(dest.id)?;
let ty = &self.program.get_var(dest.id).ty; let ty = &self.program.get_var(dest.id).ty;
let Type::Concrete(id) = ty else { let &Type::Struct { id, ref args } = ty else {
return Some(Some(format!( return Some(Some(format!(
"Failed to contruct type {}", "Failed to contruct type {}",
self.program.type_name(ty) self.program.type_name(ty)
))); )));
}; };
let struc = self.program.get_struct(*id); for (&fid, var) in fields {
for (name, var) in fields { self.map_subvar(var.id);
self.instrs.push(IRLInstruction::Mv { self.instrs.push(IRLInstruction::Mv {
dest: dest.id, dest: dest.id,
src: var.id, src: var.id,
dest_offset: struc.fields[name].offset, dest_offset: self.program.field_offset(id, fid).expect("field offset"),
src_offset: 0, src_offset: 0,
}) })
} }
} }
IRUInstruction::Access { dest, src, field } => {
self.alloc_stack(dest.id)?;
let ty = &self.program.get_var(src.id).ty;
let Type::Concrete(id) = ty else {
return Some(Some(format!(
"Failed to access field of struct {}",
self.program.type_name(ty)
)));
};
let struc = self.program.get_struct(*id);
let Some(field) = struc.fields.get(field) else {
return Some(Some(format!(
"No field {field} in struct {}",
self.program.type_name(ty)
)));
};
self.instrs.push(IRLInstruction::Mv {
dest: dest.id,
src: src.id,
src_offset: field.offset,
dest_offset: 0,
})
}
IRUInstruction::If { cond, body } => { IRUInstruction::If { cond, body } => {
self.map_subvar(cond.id);
let sym = self.builder.reserve(); let sym = self.builder.reserve();
self.instrs.push(IRLInstruction::Branch { self.instrs.push(IRLInstruction::Branch {
to: *sym, to: *sym,
@@ -235,19 +249,27 @@ impl<'a> IRLFunctionBuilder<'a> {
IRUInstruction::Loop { body } => { IRUInstruction::Loop { body } => {
let top = self.builder.reserve(); let top = self.builder.reserve();
let bot = self.builder.reserve(); let bot = self.builder.reserve();
let old = self.outer; let old = self.loopp;
self.outer = Some(*bot); self.loopp = Some(LoopCtx {
bot: *bot,
top: *top,
});
self.instrs.push(IRLInstruction::Mark(*top)); self.instrs.push(IRLInstruction::Mark(*top));
for i in body { for i in body {
self.insert_instr(i); self.insert_instr(i);
} }
self.instrs.push(IRLInstruction::Jump(*top)); self.instrs.push(IRLInstruction::Jump(*top));
self.instrs.push(IRLInstruction::Mark(*bot)); self.instrs.push(IRLInstruction::Mark(*bot));
self.outer = old; self.loopp = old;
} }
IRUInstruction::Break => { IRUInstruction::Break => {
self.instrs.push(IRLInstruction::Jump( self.instrs.push(IRLInstruction::Jump(
self.outer.expect("Tried to break outside of loop"), self.loopp.expect("Tried to break outside of loop").bot,
));
}
IRUInstruction::Continue => {
self.instrs.push(IRLInstruction::Jump(
self.loopp.expect("Tried to break outside of loop").top,
)); ));
} }
}; };
@@ -265,6 +287,7 @@ impl<'a> IRLFunctionBuilder<'a> {
.collect(), .collect(),
ret_size: self.program.size_of_type(&f.ret).expect("unsized type"), ret_size: self.program.size_of_type(&f.ret).expect("unsized type"),
stack: self.stack, stack: self.stack,
subvar_map: self.subvar_map,
} }
} }
} }

View File

@@ -1,4 +1,7 @@
use crate::{common::FileSpan, ir::{Len, Size}}; use crate::{
common::FileSpan,
ir::{FieldID, Len, StructID, VarID},
};
use super::Type; use super::Type;
use std::{collections::HashMap, fmt::Debug}; use std::{collections::HashMap, fmt::Debug};
@@ -13,25 +16,41 @@ pub struct FnDef {
#[derive(Clone)] #[derive(Clone)]
pub struct StructField { pub struct StructField {
pub name: String,
pub ty: Type, pub ty: Type,
pub offset: Len,
} }
#[derive(Clone)] #[derive(Clone)]
pub struct StructDef { pub struct StructDef {
pub name: String, pub name: String,
pub fields: HashMap<String, StructField>, pub fields: Vec<StructField>,
pub size: Size, pub field_map: HashMap<String, FieldID>,
pub origin: Origin, pub origin: Origin,
} }
#[derive(Clone)] #[derive(Clone)]
pub struct VarDef { pub struct VarDef {
pub name: String, pub name: String,
pub parent: Option<FieldRef>,
pub ty: Type, pub ty: Type,
pub origin: Origin, pub origin: Origin,
} }
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
pub struct VarOffset {
pub id: VarID,
pub offset: Len,
}
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
pub struct FieldRef {
pub var: VarID,
// this is technically redundant bc you can get it from the var...
// but it makes things a lot easier, and you'd have to recheck the fields anyways
pub struc: StructID,
pub field: FieldID,
}
#[derive(Clone)] #[derive(Clone)]
pub struct DataDef { pub struct DataDef {
pub ty: Type, pub ty: Type,
@@ -49,3 +68,15 @@ impl FnDef {
} }
} }
} }
impl StructDef {
pub fn field(&self, id: FieldID) -> &StructField {
&self.fields[id.0]
}
pub fn get_field(&self, name: &str) -> Option<&StructField> {
self.field_map.get(name).map(|id| self.field(*id))
}
pub fn iter_fields(&self) -> impl Iterator<Item = (FieldID, &StructField)> {
self.fields.iter().enumerate().map(|(i, f)| (FieldID(i), f))
}
}

View File

@@ -3,7 +3,7 @@ use std::{collections::HashMap, fmt::Write};
use super::{ use super::{
arch::riscv64::RV64Instruction, inst::VarInst, DataID, FnID, IRUInstrInst, Type, VarID, arch::riscv64::RV64Instruction, inst::VarInst, DataID, FnID, IRUInstrInst, Type, VarID,
}; };
use crate::{compiler::arch::riscv::Reg, util::Padder}; use crate::{compiler::arch::riscv::Reg, ir::FieldID, util::Padder};
pub struct IRUFunction { pub struct IRUFunction {
pub name: String, pub name: String,
@@ -33,11 +33,6 @@ pub enum IRUInstruction {
dest: VarInst, dest: VarInst,
src: FnID, src: FnID,
}, },
Access {
dest: VarInst,
src: VarInst,
field: String,
},
Call { Call {
dest: VarInst, dest: VarInst,
f: VarInst, f: VarInst,
@@ -52,7 +47,7 @@ pub enum IRUInstruction {
}, },
Construct { Construct {
dest: VarInst, dest: VarInst,
fields: HashMap<String, VarInst>, fields: HashMap<FieldID, VarInst>,
}, },
If { If {
cond: VarInst, cond: VarInst,
@@ -62,6 +57,7 @@ pub enum IRUInstruction {
body: Vec<IRUInstrInst>, body: Vec<IRUInstrInst>,
}, },
Break, Break,
Continue,
} }
#[derive(Debug)] #[derive(Debug)]
@@ -95,7 +91,6 @@ impl std::fmt::Debug for IRUInstruction {
} }
Self::Ret { src } => f.debug_struct("Ret").field("src", src).finish()?, Self::Ret { src } => f.debug_struct("Ret").field("src", src).finish()?,
Self::Construct { dest, fields } => write!(f, "{dest:?} <- {fields:?}")?, Self::Construct { dest, fields } => write!(f, "{dest:?} <- {fields:?}")?,
Self::Access { dest, src, field } => write!(f, "{dest:?} <- {src:?}.{field}")?,
Self::If { cond, body } => { Self::If { cond, body } => {
write!(f, "if {cond:?}:")?; write!(f, "if {cond:?}:")?;
if !body.is_empty() { if !body.is_empty() {
@@ -125,6 +120,7 @@ impl std::fmt::Debug for IRUInstruction {
} }
} }
Self::Break => write!(f, "break")?, Self::Break => write!(f, "break")?,
Self::Continue => write!(f, "continue")?,
} }
Ok(()) Ok(())
} }

View File

@@ -1,13 +1,11 @@
use std::{collections::HashMap, fmt::Debug}; use std::{collections::HashMap, fmt::Debug};
use crate::common::FileSpan;
use super::{inst::VarInst, *}; use super::{inst::VarInst, *};
pub struct IRUProgram { pub struct IRUProgram {
pub fn_defs: Vec<FnDef>, pub fn_defs: Vec<FnDef>,
pub var_defs: Vec<VarDef>, pub var_defs: Vec<VarDef>,
pub type_defs: Vec<StructDef>, pub struct_defs: Vec<StructDef>,
pub data_defs: Vec<DataDef>, pub data_defs: Vec<DataDef>,
pub fns: Vec<Option<IRUFunction>>, pub fns: Vec<Option<IRUFunction>>,
pub data: Vec<Vec<u8>>, pub data: Vec<Vec<u8>>,
@@ -21,7 +19,7 @@ impl IRUProgram {
Self { Self {
fn_defs: Vec::new(), fn_defs: Vec::new(),
var_defs: Vec::new(), var_defs: Vec::new(),
type_defs: Vec::new(), struct_defs: Vec::new(),
data_defs: Vec::new(), data_defs: Vec::new(),
data: Vec::new(), data: Vec::new(),
fn_map: HashMap::new(), fn_map: HashMap::new(),
@@ -57,8 +55,8 @@ impl IRUProgram {
pub fn get_fn_var(&self, id: VarID) -> Option<&FnDef> { pub fn get_fn_var(&self, id: VarID) -> Option<&FnDef> {
Some(&self.fn_defs[self.fn_map.get(&id)?.0]) Some(&self.fn_defs[self.fn_map.get(&id)?.0])
} }
pub fn get_struct(&self, id: TypeID) -> &StructDef { pub fn get_struct(&self, id: StructID) -> &StructDef {
&self.type_defs[id.0] &self.struct_defs[id.0]
} }
pub fn alias_fn(&mut self, name: &str, id: FnID) { pub fn alias_fn(&mut self, name: &str, id: FnID) {
self.insert(name, Ident::Fn(id)); self.insert(name, Ident::Fn(id));
@@ -80,9 +78,11 @@ impl IRUProgram {
pub fn size_of_type(&self, ty: &Type) -> Option<Size> { pub fn size_of_type(&self, ty: &Type) -> Option<Size> {
// TODO: target matters // TODO: target matters
Some(match ty { Some(match ty {
Type::Concrete(id) => self.type_defs[id.0].size,
Type::Bits(b) => *b, Type::Bits(b) => *b,
Type::Generic { base, args } => todo!(), Type::Struct { id, args } => self.struct_defs[id.0]
.fields
.iter()
.try_fold(0, |sum, f| Some(sum + self.size_of_type(&f.ty)?))?,
Type::Fn { args, ret } => todo!(), Type::Fn { args, ret } => todo!(),
Type::Ref(_) => 64, Type::Ref(_) => 64,
Type::Array(ty, len) => self.size_of_type(ty)? * len, Type::Array(ty, len) => self.size_of_type(ty)? * len,
@@ -92,12 +92,21 @@ impl IRUProgram {
Type::Unit => 0, Type::Unit => 0,
}) })
} }
pub fn struct_layout() {}
pub fn size_of_var(&self, var: VarID) -> Option<Size> { pub fn size_of_var(&self, var: VarID) -> Option<Size> {
self.size_of_type(&self.var_defs[var.0].ty) self.size_of_type(&self.var_defs[var.0].ty)
} }
pub fn temp_subvar(&mut self, origin: Origin, ty: Type, parent: FieldRef) -> VarInst {
self.temp_var_inner(origin, ty, Some(parent))
}
pub fn temp_var(&mut self, origin: Origin, ty: Type) -> VarInst { pub fn temp_var(&mut self, origin: Origin, ty: Type) -> VarInst {
self.temp_var_inner(origin, ty, None)
}
fn temp_var_inner(&mut self, origin: Origin, ty: Type, parent: Option<FieldRef>) -> VarInst {
let v = self.def_var(VarDef { let v = self.def_var(VarDef {
name: format!("temp{}", self.temp), name: format!("temp{}", self.temp),
parent,
origin, origin,
ty, ty,
}); });
@@ -107,11 +116,13 @@ impl IRUProgram {
span: origin, span: origin,
} }
} }
pub fn def_fn(&mut self, def: FnDef) -> FnID { pub fn def_fn(&mut self, def: FnDef) -> FnID {
let i = self.fn_defs.len(); let i = self.fn_defs.len();
let id = FnID(i); let id = FnID(i);
let var_def = VarDef { let var_def = VarDef {
name: def.name.clone(), name: def.name.clone(),
parent: None,
origin: def.origin, origin: def.origin,
ty: def.ty(), ty: def.ty(),
}; };
@@ -126,11 +137,11 @@ impl IRUProgram {
id id
} }
pub fn def_type(&mut self, def: StructDef) -> TypeID { pub fn def_struct(&mut self, def: StructDef) -> StructID {
let i = self.type_defs.len(); let i = self.struct_defs.len();
let id = TypeID(i); let id = StructID(i);
self.insert(&def.name, Ident::Type(id)); self.insert(&def.name, Ident::Type(id));
self.type_defs.push(def); self.struct_defs.push(def);
id id
} }
pub fn def_data(&mut self, def: DataDef, bytes: Vec<u8>) -> DataID { pub fn def_data(&mut self, def: DataDef, bytes: Vec<u8>) -> DataID {
@@ -142,10 +153,7 @@ impl IRUProgram {
pub fn type_name(&self, ty: &Type) -> String { pub fn type_name(&self, ty: &Type) -> String {
let mut str = String::new(); let mut str = String::new();
match ty { match ty {
Type::Concrete(t) => { Type::Struct { id: base, args } => {
str += &self.get_struct(*t).name;
}
Type::Generic { base, args } => {
str += &self.get_struct(*base).name; str += &self.get_struct(*base).name;
if let Some(arg) = args.first() { if let Some(arg) = args.first() {
str = str + "<" + &self.type_name(arg); str = str + "<" + &self.type_name(arg);
@@ -201,13 +209,29 @@ impl IRUProgram {
.enumerate() .enumerate()
.flat_map(|(i, f)| Some((FnID(i), f.as_ref()?))) .flat_map(|(i, f)| Some((FnID(i), f.as_ref()?)))
} }
pub fn var_offset(&self, var: VarID) -> Option<VarOffset> {
let mut current = VarOffset { id: var, offset: 0 };
while let Some(parent) = self.var_defs[current.id.0].parent {
current.id = parent.var;
current.offset += self.field_offset(parent.struc, parent.field)?;
}
Some(current)
}
pub fn field_offset(&self, struct_id: StructID, field: FieldID) -> Option<Len> {
let struc = self.get_struct(struct_id);
let mut offset = 0;
for i in 0..field.0 {
offset += self.size_of_type(&struc.fields[i].ty)?;
}
Some(offset)
}
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum Ident { pub enum Ident {
Var(VarID), Var(VarID),
Fn(FnID), Fn(FnID),
Type(TypeID), Type(StructID),
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@@ -215,7 +239,7 @@ pub struct Idents {
pub latest: Ident, pub latest: Ident,
pub var: Option<VarID>, pub var: Option<VarID>,
pub func: Option<FnID>, pub func: Option<FnID>,
pub ty: Option<TypeID>, pub struc: Option<StructID>,
} }
impl Idents { impl Idents {
@@ -224,7 +248,7 @@ impl Idents {
latest, latest,
var: None, var: None,
func: None, func: None,
ty: None, struc: None,
}; };
s.insert(latest); s.insert(latest);
s s
@@ -238,7 +262,7 @@ impl Idents {
Ident::Fn(f) => { Ident::Fn(f) => {
self.func = Some(f); self.func = Some(f);
} }
Ident::Type(t) => self.ty = Some(t), Ident::Type(t) => self.struc = Some(t),
} }
} }
} }

View File

@@ -1,10 +1,9 @@
use super::{IRUProgram, Len, TypeID}; use super::{IRUProgram, Len, StructID};
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub enum Type { pub enum Type {
Concrete(TypeID),
Bits(u32), Bits(u32),
Generic { base: TypeID, args: Vec<Type> }, Struct { id: StructID, args: Vec<Type> },
Fn { args: Vec<Type>, ret: Box<Type> }, Fn { args: Vec<Type>, ret: Box<Type> },
Ref(Box<Type>), Ref(Box<Type>),
Slice(Box<Type>), Slice(Box<Type>),
@@ -26,6 +25,7 @@ impl Type {
} }
} }
// should impl instead
pub fn resolve_types(ns: &IRUProgram) { pub fn resolve_types(ns: &IRUProgram) {
for (i, f) in ns.iter_fns() { for (i, f) in ns.iter_fns() {
for inst in &f.instructions { for inst in &f.instructions {

View File

@@ -80,8 +80,8 @@ impl IRUProgram {
} }
IRUInstruction::Construct { dest, fields } => { IRUInstruction::Construct { dest, fields } => {
let dest_def = self.get_var(dest.id); let dest_def = self.get_var(dest.id);
let tyid = match dest_def.ty { let tyid = match &dest_def.ty {
Type::Concrete(id) => id, Type::Struct { id, args } => *id,
_ => { _ => {
output.err(CompilerMsg { output.err(CompilerMsg {
msg: "uhh type is not struct".to_string(), msg: "uhh type is not struct".to_string(),
@@ -91,44 +91,17 @@ impl IRUProgram {
} }
}; };
let def = self.get_struct(tyid); let def = self.get_struct(tyid);
for (name, field) in &def.fields { for (id, field) in def.iter_fields() {
if let Some(var) = fields.get(name) { if let Some(var) = fields.get(&id) {
let ety = &self.get_var(var.id).ty; let ety = &self.get_var(var.id).ty;
output.check_assign(self, &field.ty, ety, var.span); output.check_assign(self, &field.ty, ety, var.span);
} else { } else {
output.err(CompilerMsg { output.err(CompilerMsg {
msg: format!("field '{name}' missing from struct"), msg: format!("field '{}' missing from struct", field.name),
spans: vec![dest.span], spans: vec![dest.span],
}); });
} }
} }
for name in fields.keys() {
if !def.fields.contains_key(name) {
output.err(CompilerMsg {
msg: format!("field '{name}' not in struct"),
spans: vec![dest.span],
});
}
}
}
IRUInstruction::Access { dest, src, field } => {
let dest_def = self.get_var(dest.id);
let src_def = self.get_var(src.id);
let tyid = match src_def.ty {
Type::Concrete(id) => id,
_ => {
output.err(CompilerMsg {
msg: "uhh type is not struct".to_string(),
spans: vec![dest.span],
});
continue;
}
};
let def = self.get_struct(tyid);
let field = def.fields.get(field).expect(
"already validated during parse lowering... probably shouldn't be?",
);
output.check_assign(self, &field.ty, &dest_def.ty, i.span);
} }
IRUInstruction::If { cond, body } => { IRUInstruction::If { cond, body } => {
let cond = self.get_var(cond.id); let cond = self.get_var(cond.id);
@@ -147,6 +120,15 @@ impl IRUProgram {
} }
// TODO // TODO
} }
IRUInstruction::Continue => {
if !breakable {
output.err(CompilerMsg {
msg: "Can't continue here (outside of loop)".to_string(),
spans: vec![i.span],
});
}
// TODO
}
} }
} }
if needs_ret && no_ret && *ret != Type::Unit { if needs_ret && no_ret && *ret != Type::Unit {

View File

@@ -1,4 +1,4 @@
use crate::ir::{IRUInstruction, VarInst}; use crate::ir::{IRUInstruction, Type, VarInst};
use super::{FnLowerCtx, FnLowerable, PBlock, PStatement}; use super::{FnLowerCtx, FnLowerable, PBlock, PStatement};
@@ -9,7 +9,7 @@ impl FnLowerable for PBlock {
for statement in &self.statements { for statement in &self.statements {
statement.lower(ctx); statement.lower(ctx);
} }
let res = self.result.as_ref().map(|r| r.lower(ctx)).flatten(); let res = self.result.as_ref().and_then(|r| r.lower(ctx));
ctx.program.pop(); ctx.program.pop();
res res
} }
@@ -28,8 +28,13 @@ impl FnLowerable for PStatement {
None None
} }
super::PStatement::Return(e) => { super::PStatement::Return(e) => {
if let Some(e) = e {
let src = e.lower(ctx)?; let src = e.lower(ctx)?;
ctx.push_at(IRUInstruction::Ret { src }, src.span); ctx.push_at(IRUInstruction::Ret { src }, src.span);
} else {
let src = ctx.temp(Type::Unit);
ctx.push_at(IRUInstruction::Ret { src }, src.span);
}
None None
} }
super::PStatement::Expr(e) => e.lower(ctx), super::PStatement::Expr(e) => e.lower(ctx),

View File

@@ -13,6 +13,7 @@ impl Node<PVarDef> {
Some(VarDef { Some(VarDef {
name, name,
ty, ty,
parent: None,
origin: self.span, origin: self.span,
}) })
} }
@@ -36,18 +37,14 @@ impl PType {
let Some(name) = self.name.as_ref() else { let Some(name) = self.name.as_ref() else {
return Type::Error; return Type::Error;
}; };
match namespace.get(&name).and_then(|ids| ids.ty) { match namespace.get(&name).and_then(|ids| ids.struc) {
Some(id) => { Some(id) => {
if self.args.is_empty() {
Type::Concrete(id)
} else {
let args = self let args = self
.args .args
.iter() .iter()
.map(|n| n.lower(namespace, output)) .map(|n| n.lower(namespace, output))
.collect(); .collect();
Type::Generic { base: id, args } Type::Struct { id, args }
}
} }
None => { None => {
if let Ok(num) = name.parse::<u32>() { if let Ok(num) = name.parse::<u32>() {

View File

@@ -1,6 +1,6 @@
use super::{func::FnLowerCtx, FnLowerable, PExpr, UnaryOp}; use super::{func::FnLowerCtx, FnLowerable, PExpr, UnaryOp};
use crate::{ use crate::{
ir::{DataDef, IRUInstruction, Type, VarInst}, ir::{DataDef, FieldRef, IRUInstruction, Type, VarInst},
parser::PInfixOp, parser::PInfixOp,
}; };
@@ -61,30 +61,36 @@ impl FnLowerable for PExpr {
let res1 = e1.lower(ctx)?; let res1 = e1.lower(ctx)?;
if *op == PInfixOp::Access { if *op == PInfixOp::Access {
let sty = &ctx.program.get_var(res1.id).ty; let sty = &ctx.program.get_var(res1.id).ty;
let Type::Concrete(tid) = sty else { let Type::Struct {
id: struct_id,
args,
} = sty
else {
ctx.err(format!( ctx.err(format!(
"Type {:?} has no fields", "Type {:?} has no fields",
ctx.program.type_name(sty) ctx.program.type_name(sty)
)); ));
return None; return None;
}; };
let struc = ctx.program.get_struct(*tid); let struc = ctx.program.get_struct(*struct_id);
let Some(box PExpr::Ident(ident)) = &e2.inner else { let Some(box PExpr::Ident(ident)) = &e2.inner else {
ctx.err(format!("Field accesses must be identifiers",)); ctx.err("Field accesses must be identifiers".to_string());
return None; return None;
}; };
let fname = &ident.as_ref()?.0; let fname = &ident.as_ref()?.0;
let Some(field) = struc.fields.get(fname) else { let Some(&field) = struc.field_map.get(fname) else {
ctx.err(format!("Field '{fname}' not in struct")); ctx.err(format!("Field '{fname}' not in struct"));
return None; return None;
}; };
let temp = ctx.temp(field.ty.clone()); let fdef = struc.field(field);
ctx.push(IRUInstruction::Access { ctx.temp_subvar(
dest: temp, fdef.ty.clone(),
src: res1, FieldRef {
field: fname.to_string(), var: res1.id,
}); struc: *struct_id,
temp field,
},
)
} else { } else {
let res2 = e2.lower(ctx)?; let res2 = e2.lower(ctx)?;
match op { match op {
@@ -186,6 +192,10 @@ impl FnLowerable for PExpr {
ctx.push(IRUInstruction::Break); ctx.push(IRUInstruction::Break);
return None; return None;
} }
PExpr::Continue => {
ctx.push(IRUInstruction::Continue);
return None;
}
}) })
} }
} }

View File

@@ -1,8 +1,8 @@
use super::{CompilerMsg, CompilerOutput, FileSpan, FnLowerable, Node, PFunction}; use super::{CompilerMsg, CompilerOutput, FileSpan, FnLowerable, Node, PFunction};
use crate::{ use crate::{
ir::{ ir::{
FnDef, FnID, IRUFunction, IRUInstrInst, IRUInstruction, IRUProgram, Idents, Origin, Type, FnDef, FnID, IRUFunction, IRUInstrInst, IRUInstruction, IRUProgram, Idents, Type, VarDef,
VarDef, VarInst, VarInst, FieldRef,
}, },
parser, parser,
}; };
@@ -32,6 +32,7 @@ impl PFunction {
a.lower(map, output).unwrap_or(VarDef { a.lower(map, output).unwrap_or(VarDef {
name: "{error}".to_string(), name: "{error}".to_string(),
origin: a.span, origin: a.span,
parent: None,
ty: Type::Error, ty: Type::Error,
}) })
}) })
@@ -117,6 +118,9 @@ impl FnLowerCtx<'_> {
pub fn temp(&mut self, ty: Type) -> VarInst { pub fn temp(&mut self, ty: Type) -> VarInst {
self.program.temp_var(self.span, ty) self.program.temp_var(self.span, ty)
} }
pub fn temp_subvar(&mut self, ty: Type, parent: FieldRef) -> VarInst {
self.program.temp_subvar(self.span, ty, parent)
}
pub fn push(&mut self, i: IRUInstruction) { pub fn push(&mut self, i: IRUInstruction) {
self.instructions.push(IRUInstrInst { i, span: self.span }); self.instructions.push(IRUInstrInst { i, span: self.span });
} }

View File

@@ -1,8 +1,8 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::{ use crate::{
common::{CompilerMsg, CompilerOutput, FileSpan}, common::{CompilerOutput, FileSpan},
ir::{IRUInstruction, IRUProgram, Origin, StructDef, StructField, VarInst}, ir::{FieldID, IRUInstruction, IRUProgram, StructDef, StructField, Type, VarInst},
parser::{Node, PConstruct, PConstructFields, PStruct, PStructFields}, parser::{Node, PConstruct, PConstructFields, PStruct, PStructFields},
}; };
@@ -12,6 +12,18 @@ impl FnLowerable for PConstruct {
type Output = VarInst; type Output = VarInst;
fn lower(&self, ctx: &mut FnLowerCtx) -> Option<VarInst> { fn lower(&self, ctx: &mut FnLowerCtx) -> Option<VarInst> {
let ty = self.name.lower(ctx.program, ctx.output); let ty = self.name.lower(ctx.program, ctx.output);
let field_map = match ty {
Type::Struct { id, .. } => ctx.program.get_struct(id),
_ => {
ctx.err(format!(
"Type {} cannot be constructed",
ctx.program.type_name(&ty)
));
return None;
}
}
.field_map
.clone();
let fields = match &self.fields { let fields = match &self.fields {
PConstructFields::Named(nodes) => nodes PConstructFields::Named(nodes) => nodes
.iter() .iter()
@@ -19,7 +31,15 @@ impl FnLowerable for PConstruct {
let def = n.as_ref()?; let def = n.as_ref()?;
let name = def.name.as_ref()?.to_string(); let name = def.name.as_ref()?.to_string();
let expr = def.val.as_ref()?.lower(ctx)?; let expr = def.val.as_ref()?.lower(ctx)?;
Some((name, expr)) let Some(&field) = field_map.get(&name) else {
ctx.err(format!(
"Struct {} has no field {}",
ctx.program.type_name(&ty),
name
));
return None;
};
Some((field, expr))
}) })
.collect(), .collect(),
PConstructFields::Tuple(nodes) => nodes PConstructFields::Tuple(nodes) => nodes
@@ -27,10 +47,19 @@ impl FnLowerable for PConstruct {
.enumerate() .enumerate()
.flat_map(|(i, n)| { .flat_map(|(i, n)| {
let expr = n.as_ref()?.lower(ctx)?; let expr = n.as_ref()?.lower(ctx)?;
Some((format!("{i}"), expr)) let name = format!("{i}");
let Some(&field) = field_map.get(&name) else {
ctx.err(format!(
"Struct {} has no field {}",
ctx.program.type_name(&ty),
name
));
return None;
};
Some((field, expr))
}) })
.collect(), .collect(),
PConstructFields::None => HashMap::new(), PConstructFields::None => Default::default(),
}; };
let id = ctx.temp(ty); let id = ctx.temp(ty);
ctx.push(IRUInstruction::Construct { dest: id, fields }); ctx.push(IRUInstruction::Construct { dest: id, fields });
@@ -45,7 +74,7 @@ impl PStruct {
output: &mut CompilerOutput, output: &mut CompilerOutput,
span: FileSpan, span: FileSpan,
) -> Option<()> { ) -> Option<()> {
let mut offset = 0; let mut field_map = HashMap::new();
let fields = match &self.fields { let fields = match &self.fields {
PStructFields::Named(nodes) => nodes PStructFields::Named(nodes) => nodes
.iter() .iter()
@@ -54,16 +83,7 @@ impl PStruct {
let name = def.name.as_ref()?.to_string(); let name = def.name.as_ref()?.to_string();
let tynode = def.ty.as_ref()?; let tynode = def.ty.as_ref()?;
let ty = tynode.lower(p, output); let ty = tynode.lower(p, output);
let size = p.size_of_type(&ty).unwrap_or_else(|| { Some((name, ty))
output.err(CompilerMsg {
msg: format!("Size of type '{}' unknown", p.type_name(&ty)),
spans: vec![tynode.span],
});
0
});
let res = Some((name, StructField { ty, offset }));
offset += size;
res
}) })
.collect(), .collect(),
PStructFields::Tuple(nodes) => nodes PStructFields::Tuple(nodes) => nodes
@@ -71,18 +91,23 @@ impl PStruct {
.enumerate() .enumerate()
.flat_map(|(i, n)| { .flat_map(|(i, n)| {
let ty = n.as_ref()?.lower(p, output, span); let ty = n.as_ref()?.lower(p, output, span);
let size = p.size_of_type(&ty)?; Some((format!("{i}"), ty))
let res = Some((format!("{i}"), StructField { ty, offset }));
offset += size;
res
}) })
.collect(), .collect(),
PStructFields::None => HashMap::new(), PStructFields::None => vec![],
}; }
p.def_type(StructDef { .into_iter()
.enumerate()
.map(|(i, (name, ty))| {
let id = FieldID(i);
field_map.insert(name.clone(), id);
StructField { name, ty }
})
.collect();
p.def_struct(StructDef {
name: self.name.as_ref()?.to_string(), name: self.name.as_ref()?.to_string(),
origin: span, origin: span,
size: offset, field_map,
fields, fields,
}); });
Some(()) Some(())

View File

@@ -22,6 +22,7 @@ pub enum PExpr {
If(BoxNode, BoxNode), If(BoxNode, BoxNode),
Loop(BoxNode), Loop(BoxNode),
Break, Break,
Continue,
} }
impl Parsable for PExpr { impl Parsable for PExpr {
@@ -57,6 +58,9 @@ impl Parsable for PExpr {
} else if next.is_keyword(Keyword::Break) { } else if next.is_keyword(Keyword::Break) {
ctx.next(); ctx.next();
Self::Break Self::Break
} else if next.is_keyword(Keyword::Continue) {
ctx.next();
Self::Continue
} else if next.is_keyword(Keyword::Asm) { } else if next.is_keyword(Keyword::Asm) {
ctx.next(); ctx.next();
Self::AsmBlock(ctx.parse()?) Self::AsmBlock(ctx.parse()?)
@@ -165,6 +169,7 @@ impl Debug for PExpr {
PExpr::If(cond, res) => write!(f, "if {cond:?} then {res:?}")?, PExpr::If(cond, res) => write!(f, "if {cond:?} then {res:?}")?,
PExpr::Loop(res) => write!(f, "loop -> {res:?}")?, PExpr::Loop(res) => write!(f, "loop -> {res:?}")?,
PExpr::Break => write!(f, "break")?, PExpr::Break => write!(f, "break")?,
PExpr::Continue => write!(f, "continue")?,
} }
Ok(()) Ok(())
} }

View File

@@ -1,8 +1,8 @@
use super::{PExpr, Keyword, Node, Parsable, ParseResult, ParserCtx, Symbol, Token, PVarDef}; use super::{Keyword, Node, PExpr, PVarDef, Parsable, ParseResult, ParserCtx, Symbol, Token};
pub enum PStatement { pub enum PStatement {
Let(Node<PVarDef>, Node<PExpr>), Let(Node<PVarDef>, Node<PExpr>),
Return(Node<PExpr>), Return(Option<Node<PExpr>>),
Expr(Node<PExpr>), Expr(Node<PExpr>),
} }
@@ -18,7 +18,11 @@ impl Parsable for PStatement {
} }
Token::Keyword(Keyword::Return) => { Token::Keyword(Keyword::Return) => {
ctx.next(); ctx.next();
ctx.parse().map(Self::Return) if ctx.peek().is_some_and(|t| t.is_symbol(Symbol::Semicolon)) {
ParseResult::Ok(Self::Return(None))
} else {
ctx.parse().map(|res| Self::Return(Some(res)))
}
} }
_ => ctx.parse().map(Self::Expr), _ => ctx.parse().map(Self::Expr),
} }

View File

@@ -4,8 +4,9 @@ pub enum Keyword {
Let, Let,
If, If,
Return, Return,
Break,
Loop, Loop,
Break,
Continue,
Struct, Struct,
Trait, Trait,
Impl, Impl,
@@ -24,6 +25,7 @@ impl Keyword {
"for" => Self::For, "for" => Self::For,
"return" => Self::Return, "return" => Self::Return,
"break" => Self::Break, "break" => Self::Break,
"continue" => Self::Continue,
"loop" => Self::Loop, "loop" => Self::Loop,
"trait" => Self::Trait, "trait" => Self::Trait,
"impl" => Self::Impl, "impl" => Self::Impl,