mfp (minimum fizzbuzzable programming language
This commit is contained in:
parent
addec372fe
commit
8329545560
4 changed files with 194 additions and 12 deletions
47
examples/fizzbuzz.puyo
Normal file
47
examples/fizzbuzz.puyo
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
fn nl() (put 10);
|
||||
|
||||
fn zz() (
|
||||
put 122;
|
||||
put 122;
|
||||
);
|
||||
|
||||
fn Fizz() (
|
||||
put 70;
|
||||
put 105;
|
||||
zz();
|
||||
);
|
||||
|
||||
fn Buzz() (
|
||||
put 66;
|
||||
put 117;
|
||||
zz();
|
||||
);
|
||||
|
||||
fn fizzbuzz1([n]) (
|
||||
if (n % 15 = 0) [
|
||||
Fizz(); Buzz();
|
||||
] [
|
||||
if (n % 3 = 0) [
|
||||
Fizz();
|
||||
] [
|
||||
if (n % 5 = 0) [
|
||||
Buzz();
|
||||
] [
|
||||
print n;
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
nl();
|
||||
);
|
||||
|
||||
i := 1;
|
||||
|
||||
loop [
|
||||
fizzbuzz1 i;
|
||||
i := i + 1;
|
||||
|
||||
if (i > 100) [
|
||||
exit 0;
|
||||
] [];
|
||||
];
|
||||
80
src/eval.rs
80
src/eval.rs
|
|
@ -1,4 +1,10 @@
|
|||
use std::collections::HashMap;
|
||||
// TODO: usable closures
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::{self, Write},
|
||||
process,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ast::{Ast, BinOp, Ident},
|
||||
|
|
@ -21,6 +27,7 @@ pub enum Error {
|
|||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[allow(clippy::too_many_lines, reason = "you're right")]
|
||||
fn eval(expr: &Ast, local_env: &mut Map<Val>, return_env: &mut Map<Val>) -> Result<Val> {
|
||||
match expr {
|
||||
Ast::Var(ident) => match ident {
|
||||
|
|
@ -61,10 +68,12 @@ fn eval(expr: &Ast, local_env: &mut Map<Val>, return_env: &mut Map<Val>) -> Resu
|
|||
let mut outputs = outputs.clone();
|
||||
outputs.reverse();
|
||||
|
||||
let env = local_env.clone();
|
||||
|
||||
Ok(Val::Function(Box::new(Function {
|
||||
inputs,
|
||||
outputs,
|
||||
env: local_env.clone(),
|
||||
env,
|
||||
body: FunctionBody::Ast(Ast::Block(body.clone())),
|
||||
})))
|
||||
}
|
||||
|
|
@ -106,7 +115,11 @@ fn eval(expr: &Ast, local_env: &mut Map<Val>, return_env: &mut Map<Val>) -> Resu
|
|||
BinOp::Sub => lhs - rhs,
|
||||
BinOp::Mul => lhs * rhs,
|
||||
BinOp::Div => lhs / rhs,
|
||||
_ => todo!(),
|
||||
BinOp::Mod => lhs % rhs,
|
||||
BinOp::Pow => lhs.pow(&rhs),
|
||||
BinOp::Eq => Val::from(lhs == rhs),
|
||||
BinOp::Lt => Val::from(lhs < rhs),
|
||||
BinOp::Le => Val::from(lhs <= rhs),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -139,7 +152,56 @@ fn stdenv() -> Map<Val> {
|
|||
|
||||
res.insert(
|
||||
"println".to_owned(),
|
||||
Val::prim(&["x"], &[], |local_env, _| println!("{:?}", local_env["x"])),
|
||||
Val::prim(&["x"], &[], |local_env, _| println!("{}", local_env["x"])),
|
||||
);
|
||||
|
||||
res.insert(
|
||||
"print".to_owned(),
|
||||
Val::prim(&["x"], &[], |local_env, _| print!("{}", local_env["x"])),
|
||||
);
|
||||
|
||||
res.insert(
|
||||
"put".to_owned(),
|
||||
Val::prim(&["x"], &[], |local_env, _| {
|
||||
let Some(Val::Number(n)) = local_env.get("x") else {
|
||||
panic!("where is it (put)");
|
||||
};
|
||||
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
||||
let n = (n.abs() % 256.) as u8;
|
||||
io::stdout().write_all(&[n]).unwrap();
|
||||
}),
|
||||
);
|
||||
|
||||
res.insert(
|
||||
"if".to_owned(),
|
||||
Val::prim(&["cond", "then", "else"], &["x"], |loc, ret| {
|
||||
if loc["cond"] == Val::Number(0.0) {
|
||||
ret.insert("x".to_owned(), loc.get_mut("else").unwrap().run().unwrap());
|
||||
} else {
|
||||
ret.insert("x".to_owned(), loc.get_mut("then").unwrap().run().unwrap());
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
res.insert(
|
||||
"loop".to_owned(),
|
||||
Val::prim(&["body"], &[], |loc, _| {
|
||||
loop {
|
||||
loc.get_mut("body").unwrap().run().unwrap();
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
res.insert(
|
||||
"exit".to_owned(),
|
||||
Val::prim(&["code"], &[], |loc, _| {
|
||||
let Val::Number(n) = loc["code"] else {
|
||||
panic!("exit code must be a number");
|
||||
};
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
process::exit(n.floor() as i32);
|
||||
}),
|
||||
);
|
||||
|
||||
res
|
||||
|
|
@ -156,6 +218,16 @@ pub fn run(program: &[Ast]) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
impl Val {
|
||||
fn run(&mut self) -> Result<Val> {
|
||||
if let Val::Function(func) = self {
|
||||
func.run()
|
||||
} else {
|
||||
Ok(self.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Function {
|
||||
pub fn call(&mut self, val: Val) -> Result<Option<Val>> {
|
||||
match val {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
for stmt in &res {
|
||||
eprintln!("{stmt};");
|
||||
}
|
||||
eprintln!("-- /ast");
|
||||
eprintln!("-- output");
|
||||
run(&res)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
75
src/val.rs
75
src/val.rs
|
|
@ -1,8 +1,13 @@
|
|||
use std::{collections::HashMap, fmt, ops, string::ToString};
|
||||
use std::{
|
||||
cmp::{self, Ordering},
|
||||
collections::HashMap,
|
||||
fmt, ops,
|
||||
string::ToString,
|
||||
};
|
||||
|
||||
use crate::{ast::Ast, map::Map, symbol::Symbol};
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Val {
|
||||
Number(f64),
|
||||
Function(Box<Function>),
|
||||
|
|
@ -15,10 +20,10 @@ impl Default for Val {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Val {
|
||||
impl fmt::Display for Val {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Number(n) => write!(f, "{n:?}"),
|
||||
Self::Number(n) => write!(f, "{n}"),
|
||||
// TODO: ok
|
||||
Self::Function(fun) => write!(
|
||||
f,
|
||||
|
|
@ -87,6 +92,57 @@ impl ops::Div for Val {
|
|||
}
|
||||
}
|
||||
|
||||
impl ops::Rem for Val {
|
||||
type Output = Self;
|
||||
|
||||
fn rem(self, rhs: Self) -> Self::Output {
|
||||
match (self, rhs) {
|
||||
(Val::Number(a), Val::Number(b)) => Val::Number(a % b),
|
||||
(lhs, rhs) => panic!("how do i div these {lhs:?} {rhs:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl cmp::PartialEq for Val {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Number(a), Self::Number(b)) => a == b,
|
||||
(Self::Map(a), Self::Map(b)) => a == b,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// yes this is a lie shut up
|
||||
impl Eq for Val {}
|
||||
|
||||
impl cmp::PartialOrd for Val {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
match (self, other) {
|
||||
(Val::Number(a), Val::Number(b)) => a.partial_cmp(b),
|
||||
(Val::Map(a), Val::Map(b)) => {
|
||||
if a == b {
|
||||
Some(Ordering::Equal)
|
||||
} else {
|
||||
for (k, v) in a.iter() {
|
||||
if b.get(k).is_none_or(|v2| v != v2) {
|
||||
return Some(Ordering::Greater);
|
||||
}
|
||||
}
|
||||
Some(Ordering::Less)
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for Val {
|
||||
fn from(value: bool) -> Self {
|
||||
Val::Number(f64::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Function {
|
||||
/// Inputs in reverse order (fn ([a b c])) -> vec![c b a]
|
||||
|
|
@ -107,10 +163,17 @@ pub enum FunctionBody {
|
|||
impl Val {
|
||||
pub fn prim(inputs: &[&str], outputs: &[&str], run: PrimFn) -> Val {
|
||||
Self::Function(Box::new(Function {
|
||||
inputs: inputs.iter().map(ToString::to_string).collect(),
|
||||
outputs: outputs.iter().map(ToString::to_string).collect(),
|
||||
inputs: inputs.iter().rev().map(ToString::to_string).collect(),
|
||||
outputs: outputs.iter().rev().map(ToString::to_string).collect(),
|
||||
body: FunctionBody::Prim(run),
|
||||
env: HashMap::new(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn pow(&self, rhs: &Self) -> Self {
|
||||
match (self, rhs) {
|
||||
(Val::Number(a), Val::Number(b)) => Val::Number(a.powf(*b)),
|
||||
(lhs, rhs) => panic!("how do i pow these {lhs:?} {rhs:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue