mfp (minimum fizzbuzzable programming language

This commit is contained in:
mehbark 2026-01-07 01:59:29 -05:00
parent addec372fe
commit 8329545560
4 changed files with 194 additions and 12 deletions

47
examples/fizzbuzz.puyo Normal file
View 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;
] [];
];

View file

@ -1,4 +1,10 @@
use std::collections::HashMap; // TODO: usable closures
use std::{
collections::HashMap,
io::{self, Write},
process,
};
use crate::{ use crate::{
ast::{Ast, BinOp, Ident}, ast::{Ast, BinOp, Ident},
@ -21,6 +27,7 @@ pub enum Error {
pub type Result<T> = std::result::Result<T, 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> { fn eval(expr: &Ast, local_env: &mut Map<Val>, return_env: &mut Map<Val>) -> Result<Val> {
match expr { match expr {
Ast::Var(ident) => match ident { 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(); let mut outputs = outputs.clone();
outputs.reverse(); outputs.reverse();
let env = local_env.clone();
Ok(Val::Function(Box::new(Function { Ok(Val::Function(Box::new(Function {
inputs, inputs,
outputs, outputs,
env: local_env.clone(), env,
body: FunctionBody::Ast(Ast::Block(body.clone())), 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::Sub => lhs - rhs,
BinOp::Mul => lhs * rhs, BinOp::Mul => lhs * rhs,
BinOp::Div => 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( res.insert(
"println".to_owned(), "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 res
@ -156,6 +218,16 @@ pub fn run(program: &[Ast]) -> Result<()> {
Ok(()) Ok(())
} }
impl Val {
fn run(&mut self) -> Result<Val> {
if let Val::Function(func) = self {
func.run()
} else {
Ok(self.clone())
}
}
}
impl Function { impl Function {
pub fn call(&mut self, val: Val) -> Result<Option<Val>> { pub fn call(&mut self, val: Val) -> Result<Option<Val>> {
match val { match val {

View file

@ -15,11 +15,11 @@ mod tests;
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
let src = &env::args().nth(1).expect("give me an argument now"); let src = &env::args().nth(1).expect("give me an argument now");
let res = parse(src); let res = parse(src);
eprintln!("-- ast"); eprintln!("-- ast");
for stmt in &res { for stmt in &res {
eprintln!("{stmt};"); eprintln!("{stmt};");
} }
eprintln!("-- /ast"); eprintln!("-- output");
run(&res)?; run(&res)?;
Ok(()) Ok(())
} }

View file

@ -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}; use crate::{ast::Ast, map::Map, symbol::Symbol};
#[derive(Clone)] #[derive(Clone, Debug)]
pub enum Val { pub enum Val {
Number(f64), Number(f64),
Function(Box<Function>), 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 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::Number(n) => write!(f, "{n:?}"), Self::Number(n) => write!(f, "{n}"),
// TODO: ok // TODO: ok
Self::Function(fun) => write!( Self::Function(fun) => write!(
f, 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)] #[derive(Debug, Clone)]
pub struct Function { pub struct Function {
/// Inputs in reverse order (fn ([a b c])) -> vec![c b a] /// Inputs in reverse order (fn ([a b c])) -> vec![c b a]
@ -107,10 +163,17 @@ pub enum FunctionBody {
impl Val { impl Val {
pub fn prim(inputs: &[&str], outputs: &[&str], run: PrimFn) -> Val { pub fn prim(inputs: &[&str], outputs: &[&str], run: PrimFn) -> Val {
Self::Function(Box::new(Function { Self::Function(Box::new(Function {
inputs: inputs.iter().map(ToString::to_string).collect(), inputs: inputs.iter().rev().map(ToString::to_string).collect(),
outputs: outputs.iter().map(ToString::to_string).collect(), outputs: outputs.iter().rev().map(ToString::to_string).collect(),
body: FunctionBody::Prim(run), body: FunctionBody::Prim(run),
env: HashMap::new(), 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:?}"),
}
}
} }