Most basic programming language features

This commit is contained in:
Pyrex 2024-02-17 13:07:00 -08:00
commit 4bfea02287
4 changed files with 182 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

7
Cargo.lock generated Normal file
View File

@ -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"

8
Cargo.toml Normal file
View File

@ -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]

166
src/main.rs Normal file
View File

@ -0,0 +1,166 @@
struct VM<'p> { program: &'p Program, block: u32, ip: u32, stack: Vec<Term>, status: Status }
enum Status {
Running,
AwaitSyscall(Term),
Crash(&'static str),
Exit(Term),
}
#[derive(Clone, Debug)]
struct Term { atom: u32, args: Vec<Term>, }
struct Program { blocks: Vec<Block> }
struct Block { instructions: Vec<Instruction> }
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<Term, &'static str> },
// 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<Term, String> {
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<Term>) {
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<Term, &'static str> {
t0.args.push(t1); Ok(t0)
}
fn binop_add(mut t0: Term, mut t1: Term) -> Result<Term, &'static str> {
t0.atom += t1.atom;
Ok(t0)
}