Most basic programming language features
This commit is contained in:
commit
4bfea02287
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
7
Cargo.lock
generated
Normal file
7
Cargo.lock
generated
Normal 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
8
Cargo.toml
Normal 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
166
src/main.rs
Normal 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)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user