mirror of
https://gitlab.cs.washington.edu/fidelp/frustration.git
synced 2024-11-16 07:48:10 +01:00
313 lines
8.2 KiB
Rust
313 lines
8.2 KiB
Rust
use std::cmp;
|
|
use std::io;
|
|
use std::process;
|
|
use std::convert::TryInto;
|
|
|
|
//const CORE_SIZE: usize = 65280
|
|
const CORE_SIZE: usize = 192;
|
|
|
|
#[derive(Debug, Eq, PartialEq)]
|
|
enum State {
|
|
Compiling,
|
|
Interpreting
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct Core {
|
|
ram: [u8; CORE_SIZE],
|
|
ip: u16,
|
|
dp: u16, // newest link field, or 0
|
|
here: u16, // first unused byte
|
|
state: State,
|
|
dstack: [u16; 64],
|
|
tds: usize, // post-incremented; exceeds top by one
|
|
rstack: [u16; 64],
|
|
trs: usize, // post-incremented; exceeds top by one
|
|
}
|
|
|
|
type Primitive = fn(&mut Core);
|
|
|
|
struct ShortName {
|
|
bytes: [u8; 3],
|
|
length: u8
|
|
}
|
|
|
|
fn truncate_name(name: &str) -> ShortName {
|
|
let name_bytes = name.as_bytes();
|
|
let mut out = ShortName {
|
|
bytes: [32, 32, 32],
|
|
length: std::cmp::min(3, name_bytes.len()) as u8 };
|
|
out.bytes[0..out.length as usize].copy_from_slice(&name_bytes[0..out.length as usize]);
|
|
return out;
|
|
}
|
|
|
|
struct TableEntry {
|
|
f: Primitive,
|
|
name: ShortName,
|
|
immediate: bool
|
|
}
|
|
|
|
// NOTE: lazy_static or SyncLazy would allow nicer init
|
|
const PRIMITIVES: [TableEntry; 12] = [
|
|
TableEntry {f: ret_ , name: ShortName {bytes: ['r' as u8, 'e' as u8, 't' as u8], length: 3}, immediate: false},
|
|
TableEntry {f: dot , name: ShortName {bytes: ['.' as u8, 32, 32], length: 1}, immediate: false},
|
|
TableEntry {f: comma_, name: ShortName {bytes: [',' as u8, 32, 32], length: 1}, immediate: false},
|
|
TableEntry {f: store, name: ShortName {bytes: ['!' as u8, 32, 32], length: 1}, immediate: false},
|
|
TableEntry {f: load , name: ShortName {bytes: ['@' as u8, 32, 32], length: 1}, immediate: false},
|
|
TableEntry {f: lit , name: ShortName {bytes: ['l' as u8, 'i' as u8, 't' as u8], length: 3}, immediate: false},
|
|
TableEntry {f: add , name: ShortName {bytes: ['+' as u8, 32, 32], length: 1}, immediate: false},
|
|
TableEntry {f: sub , name: ShortName {bytes: ['-' as u8, 32, 32], length: 1}, immediate: false},
|
|
TableEntry {f: mul , name: ShortName {bytes: ['*' as u8, 32, 32], length: 1}, immediate: false},
|
|
TableEntry {f: div , name: ShortName {bytes: ['/' as u8, 32, 32], length: 1}, immediate: false},
|
|
TableEntry {f: lbracket , name: ShortName {bytes: ['[' as u8, 32, 32], length: 1}, immediate: true},
|
|
TableEntry {f: rbracket , name: ShortName {bytes: [']' as u8, 32, 32], length: 1}, immediate: false}
|
|
];
|
|
|
|
fn new_core() -> Core {
|
|
let mut c = Core {
|
|
ram: [0; CORE_SIZE], ip: 0, dp: 0, here: 2, state: State::Interpreting,
|
|
dstack: [0; 64], tds: 0,
|
|
rstack: [0; 64], trs: 0 };
|
|
init_dictionary(&mut c);
|
|
return c;
|
|
}
|
|
|
|
fn create(c: &mut Core, name: ShortName) {
|
|
let addr: usize = c.here as usize;
|
|
c.ram[addr+0..=addr+1].copy_from_slice(&c.dp.to_le_bytes());
|
|
c.dp = addr as u16;
|
|
c.ram[addr+2] = name.length & 0x7f;
|
|
c.ram[addr+3..=addr+5].copy_from_slice(&name.bytes);
|
|
c.here = (addr+6) as u16;
|
|
}
|
|
|
|
fn smudge(c: &mut Core) {
|
|
c.ram[(c.dp as usize) + 2] ^= 0x40;
|
|
}
|
|
|
|
fn immediate(c: &mut Core) {
|
|
c.ram[(c.dp as usize) + 2] ^= 0x80;
|
|
}
|
|
|
|
fn is_immediate(c: &mut Core, addr: u16) -> bool {
|
|
return (c.ram[(addr as usize) - 4] & 0x80) != 0;
|
|
}
|
|
|
|
fn lbracket(c: &mut Core) {
|
|
c.state = State::Interpreting;
|
|
}
|
|
|
|
fn rbracket(c: &mut Core) {
|
|
c.state = State::Compiling;
|
|
}
|
|
|
|
fn find(c: &mut Core, name: ShortName) -> Option<u16> {
|
|
let mut addr = c.dp as usize;
|
|
while addr != 0 {
|
|
if (c.ram[addr+2] & 0x7f) == name.length {
|
|
if c.ram[addr+3..=addr+5] == name.bytes {
|
|
return Some((addr+6) as u16);
|
|
}
|
|
}
|
|
addr = u16::from_le_bytes(c.ram[addr..=addr+1].try_into().unwrap()) as usize;
|
|
}
|
|
return None;
|
|
}
|
|
|
|
fn init_dictionary(c: &mut Core) {
|
|
let mut opcode = 65535;
|
|
for p in PRIMITIVES {
|
|
create(c, p.name);
|
|
if p.immediate {
|
|
immediate(c);
|
|
}
|
|
comma(c, opcode);
|
|
comma(c, 65535); // ret
|
|
opcode -= 1;
|
|
}
|
|
}
|
|
|
|
fn push(c: &mut Core, val: u16) {
|
|
c.dstack[c.tds] = val;
|
|
c.tds += 1;
|
|
}
|
|
|
|
fn pop(c: &mut Core) -> u16 {
|
|
c.tds -= 1;
|
|
return c.dstack[c.tds];
|
|
}
|
|
|
|
fn to_r(c: &mut Core, val: u16) {
|
|
c.rstack[c.trs] = val;
|
|
c.trs += 1;
|
|
}
|
|
|
|
fn from_r(c: &mut Core) -> u16 {
|
|
c.trs -= 1;
|
|
return c.rstack[c.trs];
|
|
}
|
|
|
|
fn fetch(c: &mut Core) -> u16 {
|
|
let ip = c.ip as usize;
|
|
let opcode = u16::from_le_bytes(c.ram[ip..=ip+1].try_into().unwrap());
|
|
c.ip += 2;
|
|
return opcode;
|
|
}
|
|
|
|
fn execute(c: &mut Core, opcode: u16) {
|
|
let primitive_index = (65535 - opcode) as usize;
|
|
if primitive_index < PRIMITIVES.len() {
|
|
(PRIMITIVES[primitive_index].f)(c);
|
|
} else {
|
|
// call
|
|
to_r(c, c.ip);
|
|
c.ip = opcode;
|
|
}
|
|
}
|
|
|
|
fn step(c: &mut Core) {
|
|
let opcode = fetch(c);
|
|
execute(c, opcode);
|
|
}
|
|
|
|
fn inner(c: &mut Core) {
|
|
loop {
|
|
step(c);
|
|
println!("ip={} trs={}", c.ip, c.trs);
|
|
if c.trs == 0 {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn ret(c: &mut Core) {
|
|
if c.trs == 0 {
|
|
std::process::exit(0);
|
|
}
|
|
c.ip = from_r(c);
|
|
}
|
|
// TODO: i dont think this actually fixed anything, 'cos it affected both the primitive
|
|
// and the dictionary entry.
|
|
// having separate tables would probably make more sense
|
|
// lit shouldn't be a primitive anyway! just a dictionary entry.
|
|
fn ret_(c: &mut Core) {
|
|
ret(c);
|
|
ret(c);
|
|
}
|
|
|
|
fn dot(c: &mut Core) {
|
|
print!("{} ", pop(c));
|
|
}
|
|
|
|
fn comma(c: &mut Core, val: u16) {
|
|
let addr = c.here as usize;
|
|
c.ram[addr..=addr+1].copy_from_slice(&val.to_le_bytes());
|
|
c.here += 2;
|
|
}
|
|
|
|
fn comma_(c: &mut Core) {
|
|
let val = pop(c);
|
|
comma(c, val);
|
|
}
|
|
|
|
fn store(c: &mut Core) {
|
|
let addr = pop(c) as usize;
|
|
let val = pop(c);
|
|
c.ram[addr..=addr+1].copy_from_slice(&val.to_le_bytes());
|
|
}
|
|
|
|
fn load(c: &mut Core) {
|
|
let addr = pop(c) as usize;
|
|
push(c, u16::from_le_bytes(c.ram[addr..=addr+1].try_into().unwrap()));
|
|
}
|
|
|
|
// TODO: again the nesting of primitive inside dictionary entry is acting fucky
|
|
fn lit(c: &mut Core) {
|
|
let retaddr = from_r(c) as usize;
|
|
push(c, u16::from_le_bytes(c.ram[retaddr..=retaddr+1].try_into().unwrap()));
|
|
to_r(c, (retaddr as u16) + 2);
|
|
}
|
|
|
|
fn add(c: &mut Core) {
|
|
let v1 = pop(c);
|
|
let v2 = pop(c);
|
|
push(c, v1 + v2);
|
|
}
|
|
|
|
fn sub(c: &mut Core) {
|
|
let v1 = pop(c);
|
|
let v2 = pop(c);
|
|
push(c, v2 - v1);
|
|
}
|
|
|
|
fn mul(c: &mut Core) {
|
|
let v1 = pop(c);
|
|
let v2 = pop(c);
|
|
push(c, v1 * v2);
|
|
}
|
|
|
|
fn div(c: &mut Core) {
|
|
let v1 = pop(c);
|
|
let v2 = pop(c);
|
|
push(c, v2 / v1);
|
|
}
|
|
|
|
fn outer(c: &mut Core, s: &str) {
|
|
let ss = s.trim();
|
|
let tokens = ss.split(" ");
|
|
for t in tokens {
|
|
match t {
|
|
"s" => {
|
|
step(c);
|
|
},
|
|
"g" => {
|
|
c.ip = 0;
|
|
inner(c);
|
|
},
|
|
"dmp" => {
|
|
println!("{:?}", c);
|
|
},
|
|
_ => {
|
|
match find(c, truncate_name(t)) {
|
|
Some(addr) => {
|
|
if c.state == State::Interpreting || is_immediate(c, addr) {
|
|
c.ip = addr;
|
|
inner(c);
|
|
} else {
|
|
comma(c, addr);
|
|
}
|
|
}
|
|
None => {
|
|
let val = t.parse::<u16>();
|
|
match val {
|
|
Ok(n) => {
|
|
match(c.state) {
|
|
State::Interpreting => { push(c, n) }
|
|
State::Compiling => {
|
|
comma(c, 65530); // lit. TODO make robust
|
|
comma(c, n);
|
|
}
|
|
}
|
|
}
|
|
Err(_) => { if t != "" { println!("{}?", t) }}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
let mut c = new_core();
|
|
loop {
|
|
let mut buf = String::new();
|
|
match io::stdin().read_line(&mut buf) {
|
|
Ok(_) => { outer(&mut c, &buf); println!(" ok");}
|
|
Err(_) => { break; }
|
|
}
|
|
}
|
|
//push(&mut c, 56); push(&mut c, 9); mul(&mut c); dot(&mut c);
|
|
//let v = pop(&mut c); call(&mut c, v);
|
|
//println!("{:?}", c);
|
|
}
|