From 83295455606954a11f534383b41c19dd6793f471 Mon Sep 17 00:00:00 2001 From: mehbark Date: Wed, 7 Jan 2026 01:59:29 -0500 Subject: [PATCH] mfp (minimum fizzbuzzable programming language --- examples/fizzbuzz.puyo | 47 +++++++++++++++++++++++++ src/eval.rs | 80 +++++++++++++++++++++++++++++++++++++++--- src/main.rs | 4 +-- src/val.rs | 75 +++++++++++++++++++++++++++++++++++---- 4 files changed, 194 insertions(+), 12 deletions(-) create mode 100644 examples/fizzbuzz.puyo diff --git a/examples/fizzbuzz.puyo b/examples/fizzbuzz.puyo new file mode 100644 index 0000000..dea6828 --- /dev/null +++ b/examples/fizzbuzz.puyo @@ -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; + ] []; +]; diff --git a/src/eval.rs b/src/eval.rs index 53f40d4..4d0ab4b 100644 --- a/src/eval.rs +++ b/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 = std::result::Result; +#[allow(clippy::too_many_lines, reason = "you're right")] fn eval(expr: &Ast, local_env: &mut Map, return_env: &mut Map) -> Result { match expr { Ast::Var(ident) => match ident { @@ -61,10 +68,12 @@ fn eval(expr: &Ast, local_env: &mut Map, return_env: &mut Map) -> 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, return_env: &mut Map) -> 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 { 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 { + if let Val::Function(func) = self { + func.run() + } else { + Ok(self.clone()) + } + } +} + impl Function { pub fn call(&mut self, val: Val) -> Result> { match val { diff --git a/src/main.rs b/src/main.rs index 0b5e244..712f888 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,11 +15,11 @@ mod tests; fn main() -> Result<(), Box> { let src = &env::args().nth(1).expect("give me an argument now"); let res = parse(src); - eprintln!("-- ast"); + eprintln!("-- ast"); for stmt in &res { eprintln!("{stmt};"); } - eprintln!("-- /ast"); + eprintln!("-- output"); run(&res)?; Ok(()) } diff --git a/src/val.rs b/src/val.rs index 3bf6017..e39f4ad 100644 --- a/src/val.rs +++ b/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), @@ -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 { + 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 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:?}"), + } + } }