diff --git a/Cargo.lock b/Cargo.lock index a5d0608..c30458e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,6 +52,43 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "bstr" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "cc" +version = "1.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clap" version = "4.5.32" @@ -98,11 +135,34 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "env_home" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "grapher" version = "0.1.0" dependencies = [ "clap", + "mlua", ] [[package]] @@ -117,12 +177,124 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "libc" +version = "0.2.170" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "lua-src" +version = "547.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edaf29e3517b49b8b746701e5648ccb5785cde1c119062cbabbc5d5cd115e42" +dependencies = [ + "cc", +] + +[[package]] +name = "luajit-src" +version = "210.5.12+a4f56a4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a8e7962a5368d5f264d045a5a255e90f9aa3fc1941ae15a8d2940d42cac671" +dependencies = [ + "cc", + "which", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mlua" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3f763c1041eff92ffb5d7169968a327e1ed2ebfe425dac0ee5a35f29082534b" +dependencies = [ + "bstr", + "either", + "mlua-sys", + "num-traits", + "parking_lot", + "rustc-hash", +] + +[[package]] +name = "mlua-sys" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1901c1a635a22fe9250ffcc4fcc937c16b47c2e9e71adba8784af8bca1f69594" +dependencies = [ + "cc", + "cfg-if", + "lua-src", + "luajit-src", + "pkg-config", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cde51589ab56b20a6f686b2c68f7a0bd6add753d697abf720d63f8db3ab7b1ad" +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + [[package]] name = "proc-macro2" version = "1.0.94" @@ -141,6 +313,72 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "smallvec" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" + [[package]] name = "strsim" version = "0.11.1" @@ -170,6 +408,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "which" +version = "7.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2774c861e1f072b3aadc02f8ba886c26ad6321567ecc294c935434cad06f1283" +dependencies = [ + "either", + "env_home", + "rustix", + "winsafe", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -242,3 +492,9 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" diff --git a/Cargo.toml b/Cargo.toml index f0216fb..dc0f7ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,3 +5,4 @@ edition = "2021" [dependencies] clap = { version = "4.5.32", features = ["derive"] } +mlua = { version = "0.10.3", features = ["lua54", "vendored"] } diff --git a/src/main.rs b/src/main.rs index 14af16a..9d78218 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,7 @@ -use std::{ - error::Error, - fmt::{self}, - num::NonZero, - ops::{Add, Div, Mul, Sub}, - process, - str::FromStr, -}; +use std::{num::NonZero, process}; use clap::Parser; - +use mlua::Lua; fn main() { let Args { width, @@ -19,23 +12,40 @@ fn main() { xmax, ymax, } = Args::parse(); - let expr = match expr.parse::<Expr>() { - Ok(expr) => expr, - Err(err) => { - eprintln!("{err}"); + let lua = Lua::new(); + + // so we can write unqualified sin, cos, etc + lua.load("setmetatable(_G, {__index = math})") + .exec() + .unwrap(); + + match lua + .load(format!( + r" + function f(x, y) + return {expr} + end + " + )) + .exec() + { + Ok(()) => {} + Err(e) => { + eprintln!("lua load error: {e}"); process::exit(1); } }; + let f: mlua::Function = lua.globals().get("f").unwrap(); + let f = |x: f32, y: f32| -> f32 { f.call((x, y)).expect("return f32 plz") }; + let pos_to_sample = |ix: u16, iy: u16| -> (f32, f32) { // TODO: bad let (width, height): (u16, u16) = (width.into(), height.into()); let (width, height): (f32, f32) = (width.into(), height.into()); let (x, y) = (f32::from(ix), f32::from(iy)); - ( - x / width * (xmax - xmin) + xmin, - y / height * (ymax - ymin) + ymin, - ) + let (xt, yt) = (x / width, y / height); + (xt * (xmax - xmin) + xmin, yt * (ymax - ymin) + ymin) }; let (mut min, mut max) = (-1.0f32, 1.0f32); @@ -44,26 +54,28 @@ fn main() { for iy in 0..(height.into()) { for ix in 0..(width.into()) { let (x, y) = pos_to_sample(ix, iy); - let i = expr.eval(x, y); + let i = f(x, y); min = min.min(i); max = max.max(i); } } let (min, max) = (min, max); - for iy in 0..(height.into()) { + for iy in (0..(height.into())).rev() { for ix in 0..(width.into()) { let (x, y) = pos_to_sample(ix, iy); - let i = expr.eval(x, y); + let i = f(x, y); let scaled = i / (max - min); - let channel = (scaled * 256.0) as u8; + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] + // cube rooting makes things sharper + let channel = (scaled.cbrt() * 256.) as u8; put_pixel(channel, channel, channel); put_pixel(channel, channel, channel); } println!(); } // reset style - print!("\x1b[0m") + print!("\x1b[0m"); } fn put_pixel(r: u8, g: u8, b: u8) { @@ -87,126 +99,3 @@ struct Args { #[arg(default_value_t = 1.0)] ymax: f32, } - -#[derive(Debug)] -enum Expr { - X, - Y, - Const(f32), - Monadic(fn(f32) -> f32, Box<Expr>), - Dyadic(fn(f32, f32) -> f32, Box<Expr>, Box<Expr>), -} - -#[derive(Debug)] -enum ExprFunction { - Monadic(fn(f32) -> f32), - Dyadic(fn(f32, f32) -> f32), -} - -impl FromStr for ExprFunction { - type Err = (); - - fn from_str(s: &str) -> Result<Self, Self::Err> { - use ExprFunction::{Dyadic, Monadic}; - let func = match s { - "sin" => Monadic(f32::sin), - "cos" => Monadic(f32::cos), - "tan" => Monadic(f32::tan), - "arcsin" | "asin" => Monadic(f32::asin), - "arccos" | "acos" => Monadic(f32::acos), - "arctan" | "atan" => Monadic(f32::atan), - "sqrt" => Monadic(f32::sqrt), - "abs" => Monadic(f32::abs), - "+" => Dyadic(Add::add), - "-" => Dyadic(Sub::sub), - "*" => Dyadic(Mul::mul), - "/" => Dyadic(Div::div), - "^" => Dyadic(f32::powf), - _ => return Err(()), - }; - Ok(func) - } -} - -impl Expr { - #[allow(clippy::many_single_char_names)] - fn eval(&self, x: f32, y: f32) -> f32 { - match self { - Expr::X => x, - Expr::Y => y, - Expr::Const(n) => *n, - Expr::Monadic(f, expr) => f(expr.eval(x, y)), - Expr::Dyadic(f, a, b) => f(a.eval(x, y), b.eval(x, y)), - } - } -} - -#[derive(Debug)] -enum ExprParseError { - UnknownToken { token: String, stack: Vec<Expr> }, - ExpectedToken { stack: Vec<Expr> }, -} - -impl fmt::Display for ExprParseError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - ExprParseError::UnknownToken { token, stack } => write!( - f, - "Unknown token: \"{}\" Stack: {stack:?}", - token.escape_debug() - ), - ExprParseError::ExpectedToken { stack } => write!(f, "Expected token Stack: {stack:?}"), - } - } -} - -impl Error for ExprParseError {} - -impl FromStr for Expr { - type Err = ExprParseError; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - use Expr::{Const, Dyadic, Monadic, X, Y}; - let mut stack = Vec::new(); - for token in s.split_ascii_whitespace() { - if token == "x" { - stack.push(X); - continue; - } - if token == "y" { - stack.push(Y); - continue; - } - - if let Ok(n) = token.parse() { - stack.push(Const(n)); - continue; - } - - match ExprFunction::from_str(token) { - Ok(ExprFunction::Monadic(f)) => { - let Some(x) = stack.pop() else { - return Err(ExprParseError::ExpectedToken { stack }); - }; - stack.push(Monadic(f, x.into())); - } - Ok(ExprFunction::Dyadic(f)) => { - let Some(x) = stack.pop() else { - return Err(ExprParseError::ExpectedToken { stack }); - }; - let Some(y) = stack.pop() else { - return Err(ExprParseError::ExpectedToken { stack }); - }; - stack.push(Dyadic(f, x.into(), y.into())); - } - Err(()) => { - return Err(ExprParseError::UnknownToken { - token: token.to_owned(), - stack, - }) - } - } - } - stack.pop().ok_or(ExprParseError::ExpectedToken { stack }) - } -}