commit 4bfea02287e68918d4dae81666fdbc4772b42de8 Author: Nyeogmi Date: Sat Feb 17 13:07:00 2024 -0800 Most basic programming language features diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..004ad04 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "crocolisk" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ad9ee31 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "crocolisk" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..b227c84 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,166 @@ +struct VM<'p> { program: &'p Program, block: u32, ip: u32, stack: Vec, status: Status } + +enum Status { + Running, + AwaitSyscall(Term), + Crash(&'static str), + Exit(Term), +} + +#[derive(Clone, Debug)] +struct Term { atom: u32, args: Vec, } +struct Program { blocks: Vec } +struct Block { instructions: Vec } + +enum Instruction { + // push Atom to stack, optionally with the current `stack` appended to it + Push { atom: u32, cc: bool }, + // do a binary operation between the top two stack items + BinOp { op: fn(Term, Term) -> Result }, + // expand the top stack item to the stack, failing if its len is not `len` + Expand { len: u32 }, + // jump to the given block. its stack is the top stack item + Call { block: u32 }, + // system call. pass the top stack item. get a stack item back + Syscall { syscall: u32 }, + // kill the program + Crash(&'static str), + // end the program, popping the top stack item + Exit +} + +const SYSCALL_DBG: u32 = 0x1000; + + +fn main() { + host_program(&Program { + blocks: vec![ + Block { instructions: vec![ + Instruction::Expand { len: 0 }, + Instruction::Push { atom: SYSCALL_DBG, cc: false }, + Instruction::Push { atom: 0xdead0000, cc: false }, + Instruction::Push { atom: 0x0000ba75, cc: false }, + Instruction::BinOp { op: binop_add }, + Instruction::BinOp { op: binop_append }, + Instruction::Syscall { syscall: SYSCALL_DBG }, + Instruction::Push { atom: 0x0, cc: false }, + Instruction::Exit, + ]} + ] + }).unwrap(); +} + +fn host_program(p: &Program) -> Result { + let mut vm = VM::start(p); + loop { + let status = vm.step(); + match status { + Status::Running => {} + Status::AwaitSyscall(sc) => { + match sc.atom { + SYSCALL_DBG => { + println!("debug print: {:08x?}", sc.args[0].atom); + vm.complete_syscall(None) + } + _ => { return Err(format!("unknown syscall: {}", sc.atom)) } + } + } + Status::Crash(err) => { return Err(err.to_string()); } + Status::Exit(t) => { return Ok(t.clone()); } + } + } +} + + +impl<'p> VM<'p> { + pub fn start(program: &'p Program) -> VM<'p> { + VM { + program, + block: 0, + ip: 0, + stack: vec![Term { atom: 0, args: vec![] }], + status: Status::Running, + + } + } + + pub fn step<'a>(&'a mut self) -> &'a Status { + let error = match self.internal_step() { + Ok(_) => { return &self.status; } + Err(e) => e + }; + self.status = Status::Crash(error); + return &self.status + } + fn internal_step(&mut self) -> Result<(), &'static str> { + if let Status::Running = self.status { } else { return Ok(()); } + + let instruction = + self.program + .blocks.get(self.block as usize).ok_or("block must exist")? + .instructions.get(self.ip as usize).ok_or("instruction must exist")?; + self.ip += 1; + + match instruction { + &Instruction::Push { atom, cc } => { + let mut term = Term { atom, args: vec![] }; + if cc { term.args.extend(self.stack.drain(..)) } + self.stack.push(term) + } + Instruction::BinOp { op } => { + let t1 = self.stack.pop().ok_or("t1 must be present")?; + let t0 = self.stack.pop().ok_or("t0 must be present")?; + self.stack.push(op(t0, t1)?); + } + &Instruction::Expand { len } => { + let term = self.stack.pop().ok_or("term must be present")?; + if term.args.len() != len as usize { panic!("term must have {} args", len) } + } + &Instruction::Call { block } => { + let invocation = self.stack.pop().ok_or("invocation must be present")?; + if invocation.atom != block { panic!("invocation doesn't match block"); } + self.stack.clear(); + self.stack.push(invocation); + self.block = block; + self.ip = 0; + + } + &Instruction::Syscall { syscall } => { + let invocation = self.stack.pop().ok_or("invocation must be present")?; + if invocation.atom != syscall { panic!("invocation doesn't match syscall"); } + self.status = Status::AwaitSyscall(invocation) + } + Instruction::Crash(c) => { + panic!("crashed: {}", c) + } + Instruction::Exit => { + let exit = self.stack.pop().ok_or("exit arg must be present")?; + self.status = Status::Exit(exit) + } + } + + Ok(()) + } + + pub fn complete_syscall(&mut self, result: Option) { + match &self.status { + Status::AwaitSyscall(_) => { + if let Some(t) = result { self.stack.push(t); } + self.status = Status::Running; + }, // continue + _ => { + self.status = Status::Crash("tried to complete a syscall while not awaiting one") + } + } + } +} + + +fn binop_append(mut t0: Term, mut t1: Term) -> Result { + t0.args.push(t1); Ok(t0) +} + +fn binop_add(mut t0: Term, mut t1: Term) -> Result { + t0.atom += t1.atom; + Ok(t0) +} \ No newline at end of file