initial commit

This commit is contained in:
TriMill 2023-05-13 00:20:26 -04:00
commit 5a8ef55bb3
13 changed files with 3405 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

1886
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

16
Cargo.toml Normal file
View File

@ -0,0 +1,16 @@
[package]
name = "wgpu_calc"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
wgpu = "0.16"
env_logger = "0.10"
log = "0.4"
pollster = "0.3"
winit = "0.28"
cgmath = "0.18"
encase = { version = "0.6", features = ["cgmath"] }
regex = "1.8"

132
src/complex.rs Normal file
View File

@ -0,0 +1,132 @@
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Complex {
pub re: f64,
pub im: f64
}
impl Complex {
pub const fn new(re: f64, im: f64) -> Self {
Self { re, im }
}
}
impl From<f64> for Complex {
fn from(value: f64) -> Self {
Self { re: value, im: 0.0 }
}
}
pub mod cxfn {
use super::Complex;
pub type Function = fn(args: &[Complex]) -> Complex;
pub fn pos(a: &[Complex]) -> Complex {
a[0]
}
pub fn neg(a: &[Complex]) -> Complex {
Complex::new(-a[0].re, -a[0].im)
}
pub fn recip(a: &[Complex]) -> Complex {
let a = a[0];
let d = a.re*a.re + a.im*a.im;
Complex::new(a.re / d, -a.im / d)
}
pub fn re(a: &[Complex]) -> Complex {
Complex::new(a[0].re, 0.0)
}
pub fn im(a: &[Complex]) -> Complex {
Complex::new(a[0].im, 0.0)
}
pub fn conj(a: &[Complex]) -> Complex {
Complex::new(a[0].re, -a[0].im)
}
pub fn abs_sq(a: &[Complex]) -> Complex {
Complex::new(a[0].re*a[0].re + a[0].im*a[0].im, 0.0)
}
pub fn abs(a: &[Complex]) -> Complex {
Complex::new((a[0].re*a[0].re + a[0].im*a[0].im).sqrt(), 0.0)
}
pub fn arg(a: &[Complex]) -> Complex {
Complex::new(f64::atan2(a[0].im, a[0].re), 0.0)
}
pub fn add(a: &[Complex]) -> Complex {
let (a, b) = (a[0], a[1]);
Complex::new(a.re + b.re, a.im + b.im)
}
pub fn sub(a: &[Complex]) -> Complex {
let (a, b) = (a[0], a[1]);
Complex::new(a.re - b.re, a.im - b.im)
}
pub fn mul(a: &[Complex]) -> Complex {
let (a, b) = (a[0], a[1]);
Complex::new(a.re*b.re - a.im*b.im, a.im*b.re + a.re*b.im)
}
pub fn div(a: &[Complex]) -> Complex {
let (a, b) = (a[0], a[1]);
let d = b.re*b.re + b.im*b.im;
Complex::new((a.re*b.re + a.im*b.im)/d, (a.im*b.re - a.re*b.im)/d)
}
pub fn exp(a: &[Complex]) -> Complex {
let e = a[0].re.exp();
Complex::new(e * a[0].im.cos(), e * a[0].im.sin())
}
pub fn log(a: &[Complex]) -> Complex {
let a = a[0];
Complex::new(0.5 * (a.re*a.re + a.im*a.im).ln(), f64::atan2(a.im, a.re))
}
pub fn pow(a: &[Complex]) -> Complex {
exp(&[mul(&[log(&[a[0]]), a[1]])])
}
pub fn sqrt(a: &[Complex]) -> Complex {
pow(&[a[0],Complex::new(0.5, 0.0)])
}
pub fn sin(a: &[Complex]) -> Complex {
let a = a[0];
Complex::new(a.re.sin()*a.im.cosh(), a.re.cos()*a.im.sinh())
}
pub fn cos(a: &[Complex]) -> Complex {
let a = a[0];
Complex::new(a.re.cos()*a.im.cosh(), -a.re.sin()*a.re.sinh())
}
pub fn tan(a: &[Complex]) -> Complex {
let a = a[0];
let d = (2.0*a.re).cos() + (2.0*a.im).cosh();
Complex::new((2.0*a.re).sin() / d, (2.0*a.im).sinh() / d)
}
pub fn sinh(a: &[Complex]) -> Complex {
let a = a[0];
Complex::new(a.re.sinh()*a.im.cos(), a.re.cosh()*a.re.sin())
}
pub fn cosh(a: &[Complex]) -> Complex {
let a = a[0];
Complex::new(a.re.cosh()*a.im.cos(), a.re.sinh()*a.re.sin())
}
pub fn tanh(a: &[Complex]) -> Complex {
let a = a[0];
let d = (2.0*a.re).cosh() + (2.0*a.im).cos();
Complex::new((2.0*a.re).sinh() / d, (2.0*a.im).sin() / d)
}
}

449
src/language/compiler.rs Normal file
View File

@ -0,0 +1,449 @@
use std::{collections::HashMap, fmt::Write};
use regex::Regex;
use crate::complex::{Complex, cxfn};
use super::parser::{Expr, Stmt, UnaryOp, BinaryOp};
#[derive(Clone, Debug)]
pub enum CompileError<'s> {
FmtError,
TypeError(&'s str),
ArgCount(&'s str),
UndefinedVar(&'s str),
GlobalReassignment(&'s str),
BuiltinReassignment(&'s str),
StandaloneDerivative(&'s str),
}
impl <'s> From<std::fmt::Error> for CompileError<'s> {
fn from(_: std::fmt::Error) -> Self {
Self::FmtError
}
}
thread_local! {
pub static BUILTINS: HashMap<&'static str, (&'static str, Type, Option<cxfn::Function>)> = {
let mut m: HashMap<&'static str, (&'static str, Type, Option<cxfn::Function>)> = HashMap::new();
m.insert("i", ("CONST_I", Type::Number, Some(|_| Complex::new(0.0, 1.0))));
m.insert("e", ("CONST_E", Type::Number, Some(|_| Complex::new(std::f64::consts::E, 0.0))));
m.insert("tau", ("CONST_TAU",Type::Number, Some(|_| Complex::new(std::f64::consts::TAU, 0.0))));
m.insert("re", ("c_re", Type::Function(1), Some(cxfn::re)));
m.insert("im", ("c_im", Type::Function(1), Some(cxfn::im)));
m.insert("conj", ("c_conj", Type::Function(1), Some(cxfn::conj)));
m.insert("abs_sq", ("c_abs_sq", Type::Function(1), Some(cxfn::abs_sq)));
m.insert("abs", ("c_abs", Type::Function(1), Some(cxfn::abs)));
m.insert("arg", ("c_arg", Type::Function(1), Some(cxfn::arg)));
m.insert("pos", ("c_pos", Type::Function(1), Some(cxfn::pos)));
m.insert("neg", ("c_neg", Type::Function(1), Some(cxfn::neg)));
m.insert("recip", ("c_recip", Type::Function(1), Some(cxfn::recip)));
m.insert("add", ("c_add", Type::Function(2), Some(cxfn::add)));
m.insert("sub", ("c_sub", Type::Function(2), Some(cxfn::sub)));
m.insert("mul", ("c_mul", Type::Function(2), Some(cxfn::mul)));
m.insert("div", ("c_div", Type::Function(2), Some(cxfn::div)));
m.insert("recip", ("c_recip", Type::Function(1), Some(cxfn::recip)));
m.insert("exp", ("c_exp", Type::Function(1), Some(cxfn::exp)));
m.insert("log", ("c_log", Type::Function(1), Some(cxfn::log)));
m.insert("sqrt", ("c_sqrt", Type::Function(1), Some(cxfn::sqrt)));
m.insert("sin", ("c_sin", Type::Function(1), Some(cxfn::sin)));
m.insert("cos", ("c_cos", Type::Function(1), Some(cxfn::cos)));
m.insert("tan", ("c_tan", Type::Function(1), Some(cxfn::tan)));
m.insert("sinh", ("c_sinh", Type::Function(1), Some(cxfn::sinh)));
m.insert("cosh", ("c_cosh", Type::Function(1), Some(cxfn::cosh)));
m.insert("tanh", ("c_tanh", Type::Function(1), Some(cxfn::tanh)));
m.insert("gamma", ("c_gamma", Type::Function(1), None));
m
};
static RE_INVOKE: Regex = Regex::new(r"^invoke(\d+)$").unwrap();
static RE_ITER: Regex = Regex::new(r"^iter(\d+)$").unwrap();
}
#[derive(Clone, Copy)]
pub enum Type {
Number,
Function(u32),
}
#[derive(Clone, PartialEq, Eq, Hash)]
enum Generate {
FunctionId { argc: u32, id: u32 },
Invoke { argc: u32 },
Iter { argc: u32 },
Derivative { func: String, nderiv: u32, argc: u32 },
}
enum NameInfo {
Local { id: u32 },
Global { ty: Type, cname: String },
Builtin { ty: Type, bname: &'static str },
Generated { ty: Type, gname: String, gen: Generate }
}
impl NameInfo {
pub fn get_compiled_name(&self) -> String {
match self {
NameInfo::Local { id } => format!("arg_{id}"),
NameInfo::Global { cname, .. } => cname.to_owned(),
NameInfo::Builtin { bname, .. } => (*bname).to_owned(),
NameInfo::Generated { gname, .. } => gname.to_owned(),
}
}
}
type LocalTable<'s> = HashMap<&'s str, u32>;
type CompileResult<'s,T=()> = Result<T, CompileError<'s>>;
pub fn compile<'s>(buf: &mut impl Write, stmts: &[Stmt<'s>]) -> CompileResult<'s> {
let mut compiler = Compiler::new(buf);
for stmt in stmts {
compiler.compile_stmt(stmt)?;
}
compiler.generate()?;
Ok(())
}
struct Compiler<'s, 'w, W> where W: Write {
buf: &'w mut W,
globals: HashMap<&'s str, Type>,
generate: HashMap<String, Generate>,
next_fn_id: u32,
}
impl <'s, 'w, W: Write> Compiler<'s, 'w, W> {
fn new(buf: &'w mut W) -> Self {
Self {
buf,
globals: HashMap::new(),
generate: HashMap::new(),
next_fn_id: 0,
}
}
//////////////////
// Statements //
//////////////////
fn compile_stmt(&mut self, stmt: &Stmt<'s>) -> CompileResult<'s> {
match stmt {
Stmt::AssignConst(name, expr) => {
if BUILTINS.with(|m| m.contains_key(name)) {
return Err(CompileError::BuiltinReassignment(name))
}
if self.globals.contains_key(name) {
return Err(CompileError::GlobalReassignment(name))
}
self.globals.insert(name, Type::Number);
write!(self.buf, "const VAR_{name} = ")?;
let locals = LocalTable::with_capacity(0);
self.compile_expr(&locals, expr)?;
write!(self.buf, ";")?;
}
Stmt::AssignFunc(name, args, expr) => {
if BUILTINS.with(|m| m.contains_key(name)) {
return Err(CompileError::BuiltinReassignment(name))
}
if self.globals.contains_key(name) {
return Err(CompileError::GlobalReassignment(name))
}
self.globals.insert(name, Type::Function(args.len() as u32));
write!(self.buf, "fn func_{name}(")?;
let mut locals = LocalTable::with_capacity(args.len());
for (i, arg) in args.iter().enumerate() {
write!(self.buf, "arg_{}:vec2f,", i)?;
locals.insert(arg, i as u32);
}
write!(self.buf, ")->vec2f{{return ")?;
self.compile_expr(&locals, expr)?;
write!(self.buf, ";}}")?;
}
}
writeln!(self.buf)?;
Ok(())
}
///////////////////
// Expressions //
///////////////////
fn compile_expr(&mut self, locals: &LocalTable<'s>, expr: &Expr<'s>) -> CompileResult<'s> {
match expr {
Expr::Number(z) => self.compile_number(*z),
Expr::Name(name) => self.compile_var(locals, name),
Expr::NameDeriv(name, _) => return Err(CompileError::StandaloneDerivative(name)),
Expr::Unary(op, arg) => self.compile_unary(locals, *op, arg),
Expr::Binary(op, lhs, rhs) => self.compile_binary(locals, *op, lhs, rhs),
Expr::FnCall { name, args, nderiv } => self.compile_fncall(locals, name, args, *nderiv),
}
}
fn compile_number(&mut self, z: Complex) -> CompileResult<'s> {
write!(self.buf, "vec2f({:?},{:?})", z.re, z.im)?;
Ok(())
}
fn compile_unary(&mut self, locals: &LocalTable<'s>, op: UnaryOp, arg: &Expr<'s>) -> CompileResult<'s> {
let strings = unop_strings(op);
write!(self.buf, "{}", strings[0])?;
self.compile_expr(locals, arg)?;
write!(self.buf, "{}", strings[1])?;
Ok(())
}
fn compile_binary(&mut self, locals: &LocalTable<'s>, op: BinaryOp, lhs: &Expr<'s>, rhs: &Expr<'s>) -> CompileResult<'s> {
let strings = binop_strings(op);
write!(self.buf, "{}", strings[0])?;
self.compile_expr(locals, lhs)?;
write!(self.buf, "{}", strings[1])?;
self.compile_expr(locals, rhs)?;
write!(self.buf, "{}", strings[2])?;
Ok(())
}
fn compile_var(&mut self, locals: &LocalTable<'s>, name: &'s str) -> CompileResult<'s> {
let Some(name_info) = self.name_info(name, Some(locals)) else {
return Err(CompileError::UndefinedVar(name))
};
self.compile_name(name, name_info)?;
Ok(())
}
fn compile_fncall(&mut self, locals: &LocalTable<'s>, name: &'s str, args: &Vec<Expr<'s>>, nderiv: u32) -> CompileResult<'s> {
let Some(name_info) = self.name_info(name, Some(locals)) else {
return Err(CompileError::UndefinedVar(name))
};
let compname = &name_info.get_compiled_name();
for i in 0..nderiv {
self.add_gen_deriv(&compname, i+1, args.len() as u32);
write!(self.buf, "deriv_")?;
}
self.compile_name_fncall(name, name_info, args.len() as u32)?;
write!(self.buf, "(")?;
for arg in args {
self.compile_expr(locals, arg)?;
write!(self.buf, ",")?;
}
write!(self.buf, ")")?;
Ok(())
}
/////////////
// Names //
/////////////
fn compile_name_fncall(&mut self, name: &'s str, name_info: NameInfo, argc: u32) -> CompileResult<'s> {
match name_info {
NameInfo::Local { .. } => return Err(CompileError::TypeError(name)),
NameInfo::Global { ty: Type::Function(c), cname } if c == argc => {
write!(self.buf, "{cname}")?;
}
NameInfo::Global { ty: Type::Function(_), .. } => return Err(CompileError::ArgCount(name)),
NameInfo::Global { .. } => return Err(CompileError::TypeError(name)),
NameInfo::Builtin { ty: Type::Function(c), bname } if c == argc => {
write!(self.buf, "{bname}")?;
}
NameInfo::Builtin { ty: Type::Function(_), .. } => return Err(CompileError::ArgCount(name)),
NameInfo::Builtin { .. } => return Err(CompileError::TypeError(name)),
NameInfo::Generated { ty: Type::Function(c), gname, gen } if c == argc => {
write!(self.buf, "{gname}")?;
self.generate.insert(gname, gen);
}
NameInfo::Generated { ty: Type::Function(_), .. } => return Err(CompileError::ArgCount(name)),
NameInfo::Generated { .. } => return Err(CompileError::TypeError(name)),
}
Ok(())
}
fn compile_name(&mut self, name: &str, name_info: NameInfo) -> CompileResult<'s> {
match name_info {
NameInfo::Local { id } => {
write!(self.buf, "arg_{id}")?;
},
NameInfo::Global { ty: Type::Function(c), cname } => {
self.add_gen_fid(format!("{cname}"), c);
write!(self.buf, "FID_{cname}")?;
},
NameInfo::Global { ty: Type::Number, .. } => {
write!(self.buf, "VAR_{name}")?;
},
NameInfo::Builtin { ty: Type::Function(c), bname } => {
self.add_gen_fid(bname.to_owned(), c);
write!(self.buf, "FID_{bname}")?;
},
NameInfo::Builtin { ty: Type::Number, bname } => {
write!(self.buf, "{bname}")?;
},
NameInfo::Generated { ty: Type::Function(c), gname, gen } => {
write!(self.buf, "FID_{gname}")?;
self.add_gen_fid(gname.to_owned(), c);
self.generate.insert(gname, gen);
},
NameInfo::Generated { ty: Type::Number, gname, gen } => {
write!(self.buf, "{gname}")?;
self.generate.insert(gname, gen);
},
}
Ok(())
}
fn name_info(&self, name: &'s str, locals: Option<&LocalTable<'s>>) -> Option<NameInfo> {
if let Some(locals) = locals {
if let Some(id) = locals.get(name) {
return Some(NameInfo::Local { id: *id })
}
}
if let Some(ty) = self.globals.get(name) {
return Some(NameInfo::Global { ty: *ty, cname: format!("func_{name}") })
}
if let Some((bname, ty, _)) = BUILTINS.with(|m| m.get(name).copied()) {
return Some(NameInfo::Builtin { ty, bname })
}
if let Some(caps) = RE_INVOKE.with(|re| re.captures(name)) {
if let Ok(n) = caps[1].parse() {
return Some(NameInfo::Generated {
ty: Type::Function(n + 1),
gname: format!("invoke{n}"),
gen: Generate::Invoke { argc: n },
})
}
}
if let Some(caps) = RE_ITER.with(|re| re.captures(name)) {
if let Ok(n) = caps[1].parse() {
return Some(NameInfo::Generated {
ty: Type::Function(n + 2),
gname: format!("iter{n}"),
gen: Generate::Iter { argc: n }
})
}
}
None
}
//////////////////
// Generation //
//////////////////
fn add_gen_fid(&mut self, name: String, argc: u32) {
self.generate.insert(name, Generate::FunctionId { argc, id: self.next_fn_id });
self.next_fn_id += 1;
}
fn add_gen_deriv(&mut self, name: &str, nderiv: u32, argc: u32) {
let func_name = "deriv_".repeat(nderiv as usize - 1) + name;
let deriv_name = "deriv_".repeat(nderiv as usize) + name;
self.generate.insert(deriv_name, Generate::Derivative { func: func_name, nderiv, argc });
}
fn generate(&mut self) -> CompileResult<'s> {
for (name, g) in self.generate.clone() {
match g {
Generate::FunctionId { id, .. } => {
write!(self.buf, "const FID_{name}=vec2f({id}.0,0.0);")?;
}
Generate::Invoke { argc } => self.generate_invoke(argc)?,
Generate::Iter { argc } => self.generate_iter(argc)?,
Generate::Derivative { func, nderiv, argc } => self.generate_derivative(func, nderiv, argc)?,
}
writeln!(self.buf)?;
}
Ok(())
}
fn generate_invoke(&mut self, argc: u32) -> CompileResult<'s> {
write!(self.buf, "fn invoke{argc}(func:vec2f,")?;
for i in 0..argc {
write!(self.buf, "arg_{i}:vec2f")?;
write!(self.buf, ",")?;
}
write!(self.buf, ")->vec2f{{switch i32(func.x){{")?;
for (name, g) in &self.generate {
if let Generate::FunctionId { argc: fargc, id } = g {
write!(self.buf, "case {id}")?;
if *id == 0 {
write!(self.buf, ",default")?;
}
write!(self.buf, "{{return {name}(")?;
let a = argc.min(*fargc);
for i in 0..a {
write!(self.buf, "arg_{i},")?;
}
for _ in a..*fargc {
write!(self.buf, "vec2f(0.0),")?;
}
write!(self.buf, ");}}")?;
}
}
write!(self.buf, "}};}}")?;
Ok(())
}
fn generate_iter(&mut self, argc: u32) -> CompileResult<'s> {
if !self.generate.contains_key(&format!("invoke{argc}")) {
self.generate.insert(format!("invoke{argc}"), Generate::Iter { argc });
self.generate_invoke(argc)?;
writeln!(self.buf)?;
}
write!(self.buf, "fn iter{argc}(func:vec2f,n:vec2f,")?;
for i in 0..argc {
write!(self.buf, "arg_{i}:vec2f")?;
write!(self.buf, ",")?;
}
write!(self.buf, ")->vec2f{{var result=arg_0;")?;
write!(self.buf, "for(var i=0;i<i32(n.x);i++){{result=invoke{argc}(func,result,")?;
for i in 1..argc {
write!(self.buf, "arg_{i},")?;
}
write!(self.buf, ");}}return result;}}")?;
Ok(())
}
fn generate_derivative(&mut self, func: String, nderiv: u32, argc: u32) -> CompileResult<'s> {
if let Some(f) = func.strip_suffix("deriv_") {
if !self.generate.contains_key(f) {
self.generate.insert(func.clone(), Generate::Derivative { func: f.to_owned(), nderiv: nderiv - 1, argc });
self.generate_derivative(f.to_owned(), nderiv - 1, argc)?;
}
}
write!(self.buf, "fn deriv_{func}(z:vec2f")?;
let mut args = String::new();
for i in 1..argc {
write!(self.buf, ",arg{i}:vec2f")?;
args += &format!(",arg{i}");
}
write!(self.buf, ")->vec2f{{")?;
write!(self.buf, "\
let a = c_mul({func}(z + vec2( D_EPS, 0.0){args}), vec2( 0.25/D_EPS, 0.0));\
let b = c_mul({func}(z + vec2(-D_EPS, 0.0){args}), vec2(-0.25/D_EPS, 0.0));\
let c = c_mul({func}(z + vec2(0.0, D_EPS){args}), vec2(0.0, -0.25/D_EPS));\
let d = c_mul({func}(z + vec2(0.0, -D_EPS){args}), vec2(0.0, 0.25/D_EPS));\
return a + b + c + d;}}\
")?;
Ok(())
}
}
const fn unop_strings(op: UnaryOp) -> [&'static str; 2] {
match op {
UnaryOp::Pos => ["+(", ")"],
UnaryOp::Neg => ["-(", ")"],
UnaryOp::Recip => ["c_recip(", ")"],
}
}
const fn binop_strings(op: BinaryOp) -> [&'static str; 3] {
match op {
BinaryOp::Add => ["(", ")+(", ")"],
BinaryOp::Sub => ["(", ")-(", ")"],
BinaryOp::Mul => ["c_mul(", ",", ")"],
BinaryOp::Div => ["c_div(", ",", ")"],
BinaryOp::Pow => ["c_pow(", ",", ")"],
}
}

42
src/language/mod.rs Normal file
View File

@ -0,0 +1,42 @@
mod scanner;
mod parser;
mod compiler;
mod optimizer;
pub use parser::ParseError;
pub use compiler::CompileError;
use self::optimizer::optimize;
#[derive(Debug, Clone, Copy)]
pub struct Position {
pub pos: u32,
pub line: u32,
pub col: u32,
}
#[derive(Clone, Debug)]
pub enum Error<'s> {
Parse(ParseError<'s>),
Compile(CompileError<'s>),
}
impl <'s> From<CompileError<'s>> for Error<'s> {
fn from(value: CompileError<'s>) -> Self {
Self::Compile(value)
}
}
impl <'s> From<ParseError<'s>> for Error<'s> {
fn from(value: ParseError<'s>) -> Self {
Self::Parse(value)
}
}
pub fn compile(src: &str) -> Result<String, Error> {
let mut buf = String::new();
let stmts = parser::Parser::new(src).parse()?;
let stmts = optimize(stmts);
compiler::compile(&mut buf, &stmts)?;
Ok(buf)
}

65
src/language/optimizer.rs Normal file
View File

@ -0,0 +1,65 @@
use crate::complex::{Complex, cxfn};
use super::{parser::{Expr, UnaryOp, BinaryOp, Stmt}, compiler::{BUILTINS, Type}};
fn apply_unary(op: UnaryOp, arg: Complex) -> Complex {
match op {
UnaryOp::Pos => cxfn::pos(&[arg]),
UnaryOp::Neg => cxfn::neg(&[arg]),
UnaryOp::Recip => cxfn::recip(&[arg]),
}
}
fn apply_binary(op: BinaryOp, u: Complex, v: Complex) -> Complex {
match op {
BinaryOp::Add => cxfn::add(&[u, v]),
BinaryOp::Sub => cxfn::sub(&[u, v]),
BinaryOp::Mul => cxfn::mul(&[u, v]),
BinaryOp::Div => cxfn::div(&[u, v]),
BinaryOp::Pow => cxfn::pow(&[u, v]),
}
}
pub fn optimize<'s>(stmts: Vec<Stmt<'s>>) -> Vec<Stmt<'s>> {
stmts.into_iter().map(|s| match s {
Stmt::AssignConst(a, expr) => Stmt::AssignConst(a, optimize_expr(expr)),
Stmt::AssignFunc(a, b, expr) => Stmt::AssignFunc(a, b, optimize_expr(expr)),
}).collect()
}
fn optimize_expr<'s>(e: Expr<'s>) -> Expr<'s> {
match e {
Expr::Unary(op, arg) => {
match optimize_expr(*arg) {
Expr::Number(z) => Expr::Number(apply_unary(op, z)),
e => Expr::Unary(op, Box::new(e)),
}
},
Expr::Binary(op, lhs, rhs) => {
match (optimize_expr(*lhs), optimize_expr(*rhs)) {
(Expr::Number(u), Expr::Number(v)) => Expr::Number(apply_binary(op, u, v)),
(u, v) => Expr::Binary(op, Box::new(u), Box::new(v))
}
},
Expr::FnCall { name, args, nderiv } => {
let args: Vec<Expr<'s>> = args.into_iter().map(|a| optimize_expr(a)).collect();
if let Some((_, Type::Function(argc), Some(func))) = BUILTINS.with(|m| m.get(name).copied()) {
if argc as usize == args.len() && nderiv == 0 {
if args.iter().all(|e| matches!(e, Expr::Number(_))) {
let ns: Vec<Complex> = args.into_iter().map(|a| match a { Expr::Number(n) => n, _ => unreachable!() }).collect();
return Expr::Number(func(&ns))
}
}
}
Expr::FnCall { name, args, nderiv }
}
Expr::Name(name) => {
if let Some((_, Type::Number, Some(func))) = BUILTINS.with(|m| m.get(name).copied()) {
Expr::Number(func(&[]))
} else {
e
}
}
_ => e,
}
}

216
src/language/parser.rs Normal file
View File

@ -0,0 +1,216 @@
use std::iter::Peekable;
use crate::complex::Complex;
use super::{scanner::{Scanner, Token}, Position};
#[derive(Clone, Debug)]
pub enum ParseError<'s> {
UnexpectedTokenPrefix(Position, Token<'s>),
Expected(Position, Token<'s>),
InvalidLValue(Position),
InvalidFunction(Expr<'s>),
UnexpectedEof,
}
#[derive(Debug, Clone, Copy)]
pub enum BinaryOp {
Add, Sub, Mul, Div, Pow,
}
impl BinaryOp {
pub const fn from_token(tok: Token) -> Option<Self> {
match tok {
Token::Plus => Some(Self::Add),
Token::Minus => Some(Self::Sub),
Token::Star => Some(Self::Mul),
Token::Slash => Some(Self::Div),
Token::Caret => Some(Self::Pow),
_ => None,
}
}
pub const fn precedence(self) -> (u32, u32) {
match self {
BinaryOp::Add => (10, 11),
BinaryOp::Sub => (10, 11),
BinaryOp::Mul => (20, 21),
BinaryOp::Div => (20, 21),
BinaryOp::Pow => (31, 30),
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum UnaryOp {
Pos, Neg, Recip,
}
impl UnaryOp {
pub const fn from_token(tok: Token) -> Option<Self> {
match tok {
Token::Plus => Some(Self::Pos),
Token::Minus => Some(Self::Neg),
Token::Slash => Some(Self::Recip),
_ => None,
}
}
pub const fn precedence(self) -> u32 {
40
}
}
#[derive(Clone, Debug)]
pub enum Expr<'s> {
Number(Complex),
Name(&'s str),
NameDeriv(&'s str, u32),
Unary(UnaryOp, Box<Expr<'s>>),
Binary(BinaryOp, Box<Expr<'s>>, Box<Expr<'s>>),
FnCall { name: &'s str, args: Vec<Expr<'s>>, nderiv: u32 },
}
#[derive(Debug)]
pub enum Stmt<'s> {
AssignConst(&'s str, Expr<'s>),
AssignFunc(&'s str, Vec<&'s str>, Expr<'s>),
}
pub struct Parser<'s> {
scanner: Peekable<Scanner<'s>>,
}
impl <'s> Parser<'s> {
pub fn new(src: &'s str) -> Self {
Self {
scanner: Scanner::new(src).peekable()
}
}
fn expect(&mut self, tok: Token<'s>) -> Result<Position, ParseError<'s>> {
match self.scanner.peek() {
Some((p, t)) if *t == tok => {
let p = *p;
self.scanner.next();
Ok(p)
},
Some((p, _)) => Err(ParseError::Expected(*p, tok)),
None => Err(ParseError::UnexpectedEof),
}
}
fn expr(&mut self, min_prec: u32) -> Result<Expr<'s>, ParseError<'s>> {
let (pos, tok) = self.scanner.next().unwrap();
let mut expr = match tok {
Token::Number(n) => Expr::Number(Complex::from(n)),
Token::Name(n) => Expr::Name(n),
Token::LParen => {
let e = self.expr(0)?;
self.expect(Token::RParen)?;
e
}
tok => if let Some(op) = UnaryOp::from_token(tok) {
Expr::Unary(op, Box::new(self.expr(op.precedence())?))
} else {
return Err(ParseError::UnexpectedTokenPrefix(pos, tok))
}
};
while let Some((_, tok)) = self.scanner.peek() {
expr = match tok {
Token::Equal | Token::RParen | Token::Newline | Token::Comma => break,
Token::Quote => {
self.scanner.next();
match expr {
Expr::Name(name) => Expr::NameDeriv(name, 1),
Expr::NameDeriv(name, nderiv) => Expr::NameDeriv(name, nderiv+1),
_ => return Err(ParseError::InvalidFunction(expr))
}
},
Token::LParen => {
self.scanner.next();
let mut args = Vec::new();
while !matches!(self.scanner.peek(), None | Some((_, Token::RParen))) {
args.push(self.expr(0)?);
match self.scanner.peek() {
Some((_, Token::Comma)) => { self.scanner.next(); },
Some((_, Token::RParen)) => break,
Some((pos, _)) => return Err(ParseError::Expected(*pos, Token::RParen)),
None => return Err(ParseError::UnexpectedEof),
}
}
self.expect(Token::RParen)?;
match expr {
Expr::Name(name) => Expr::FnCall { name, args, nderiv: 0 },
Expr::NameDeriv(name, nderiv) => Expr::FnCall { name, args, nderiv },
_ => return Err(ParseError::InvalidFunction(expr)),
}
},
tok => if let Some(op) = BinaryOp::from_token(*tok) {
let (lp, rp) = op.precedence();
if lp < min_prec {
break;
}
self.scanner.next();
let rhs = self.expr(rp)?;
Expr::Binary(op, Box::new(expr), Box::new(rhs))
} else {
let (lp, rp) = BinaryOp::Mul.precedence();
if lp < min_prec {
break;
}
let rhs = self.expr(rp)?;
Expr::Binary(BinaryOp::Mul, Box::new(expr), Box::new(rhs))
}
}
}
Ok(expr)
}
pub fn parse(&mut self) -> Result<Vec<Stmt<'s>>, ParseError<'s>> {
let mut stmts = Vec::new();
while self.scanner.peek().is_some() {
while matches!(self.scanner.peek(), Some((_, Token::Newline))) {
self.scanner.next();
}
if self.scanner.peek().is_none() {
break;
}
let lhs_pos = self.scanner.peek().unwrap().0;
let lhs = self.expr(0)?;
self.expect(Token::Equal)?;
if self.scanner.peek().is_none() {
return Err(ParseError::UnexpectedEof)
}
let rhs = self.expr(0)?;
if self.scanner.peek().is_some() {
self.expect(Token::Newline)?;
}
let stmt = match lhs {
Expr::Name(name) => Stmt::AssignConst(name, rhs),
Expr::FnCall { name, args, nderiv: 0 } => {
let mut arg_names = Vec::with_capacity(args.len());
for arg in args {
if let Expr::Name(name) = arg {
arg_names.push(name)
}
}
Stmt::AssignFunc(name, arg_names, rhs)
}
_ => return Err(ParseError::InvalidLValue(lhs_pos))
};
stmts.push(stmt);
}
Ok(stmts)
}
}

116
src/language/scanner.rs Normal file
View File

@ -0,0 +1,116 @@
use std::{iter::Peekable, str::Chars};
use super::Position;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Token<'s> {
Error,
Name(&'s str),
Number(f64),
Plus,
Minus,
Star,
Slash,
Caret,
Equal,
Comma,
Quote,
LParen,
RParen,
Newline,
}
pub struct Scanner<'s> {
pub src: &'s str,
pub chars: Peekable<Chars<'s>>,
pub pos: Position,
}
impl <'s> Scanner<'s> {
pub fn new(src: &'s str) -> Self {
Self {
src,
chars: src.chars().peekable(),
pos: Position { pos: 0, line: 1, col: 1 }
}
}
fn next(&mut self) -> Option<char> {
match self.chars.next() {
Some('\n') => {
self.pos.pos += 1;
self.pos.line += 1;
self.pos.col = 1;
Some('\n')
},
Some(c) => {
self.pos.pos += 1;
self.pos.col += 1;
Some(c)
},
None => None,
}
}
fn peek(&mut self) -> Option<char> {
self.chars.peek().copied()
}
fn next_number(&mut self, pos: u32) -> Token<'s> {
while matches!(self.peek(), Some('0'..='9')) {
self.next();
}
if matches!(self.peek(), Some('.')) {
self.next();
while matches!(self.peek(), Some('0'..='9')) {
self.next();
}
}
let s = &self.src[pos as usize..self.pos.pos as usize];
match s.parse() {
Ok(x) => Token::Number(x),
Err(_) => Token::Error,
}
}
fn next_name(&mut self, pos: u32) -> Token<'s> {
while matches!(self.peek(), Some('a'..='z' | 'A'..='Z' | '0'..='9' | '_')) {
self.next();
}
let s = &self.src[pos as usize..self.pos.pos as usize];
Token::Name(s)
}
pub fn next_token(&mut self) -> Option<(Position, Token<'s>)> {
while matches!(self.peek(), Some(' ' | '\t')) {
self.next();
}
self.peek()?;
let pos = self.pos;
let tok = match self.next().unwrap() {
'\n' => Token::Newline,
'+' => Token::Plus,
'-' => Token::Minus,
'*' => Token::Star,
'/' => Token::Slash,
'^' => Token::Caret,
'\'' => Token::Quote,
'=' => Token::Equal,
',' => Token::Comma,
'(' => Token::LParen,
')' => Token::RParen,
'0'..='9' => self.next_number(pos.pos),
'a'..='z' | 'A'..='Z' => self.next_name(pos.pos),
_ => Token::Error,
};
Some((pos, tok))
}
}
impl <'s> Iterator for Scanner<'s> {
type Item = (Position, Token<'s>);
fn next(&mut self) -> Option<Self::Item> {
self.next_token()
}
}

26
src/main.rs Normal file
View File

@ -0,0 +1,26 @@
use language::compile;
use renderer::run;
use winit::{event_loop::EventLoop, window::Window};
mod language;
mod renderer;
mod complex;
fn main() {
env_logger::init();
let src = r#"
f(z,a,b,c) = (z-a)*(z-b)*(z-c)
g(z,a,b,c) = z - f(z,a,b,c)/f'(z,a,b,c)
plot(z) = iter4(g, 50, z/3, z, 1, -1)
"#;
let wgsl = compile(src).unwrap();
println!("{}", wgsl);
let event_loop = EventLoop::new();
let window = Window::new(&event_loop).unwrap();
window.set_title("window");
pollster::block_on(run(event_loop, window, &wgsl));
}

236
src/renderer/mod.rs Normal file
View File

@ -0,0 +1,236 @@
use std::num::NonZeroU64;
use encase::{ShaderType, ShaderSize, UniformBuffer};
use wgpu::util::DeviceExt;
use winit::{event_loop::EventLoop, window::Window, event::{Event, WindowEvent}, dpi::PhysicalSize};
type Vec2u = cgmath::Vector2<u32>;
type Vec2f = cgmath::Vector2<f32>;
#[derive(ShaderType)]
struct Uniforms {
resolution: Vec2u,
bounds_min: Vec2f,
bounds_max: Vec2f,
shading_intensity: f32,
}
struct State {
surface: wgpu::Surface,
device: wgpu::Device,
config: wgpu::SurfaceConfiguration,
render_pipeline: Option<wgpu::RenderPipeline>,
uniform_bind_group: wgpu::BindGroup,
uniform_bind_group_layout: wgpu::BindGroupLayout,
uniform_buffer: wgpu::Buffer,
queue: wgpu::Queue,
}
impl State {
async fn new(window: &Window) -> Self {
let size = window.inner_size();
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default());
let surface = unsafe { instance.create_surface(&window) }.unwrap();
let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&surface),
force_fallback_adapter: false,
}).await.unwrap();
let (device, queue) = adapter.request_device(
&wgpu::DeviceDescriptor {
label: None,
features: wgpu::Features::empty(),
limits: wgpu::Limits::default(),
},
None
).await.unwrap();
let format = surface.get_capabilities(&adapter).formats[0];
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format,
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::Mailbox,
alpha_mode: wgpu::CompositeAlphaMode::Auto,
view_formats: vec![format],
};
surface.configure(&device, &config);
// Uniforms //
let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: None,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
contents: &[0; Uniforms::SHADER_SIZE.get() as usize],
});
let uniform_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: None,
entries: &[
wgpu::BindGroupLayoutEntry {
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
binding: 1,
},
]
});
let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &uniform_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: &uniform_buffer,
offset: 0,
size: Some(NonZeroU64::new(Uniforms::SHADER_SIZE.get()).unwrap()),
}),
},
],
});
// Done //
Self {
surface,
device,
config,
render_pipeline: None,
queue,
uniform_bind_group,
uniform_bind_group_layout,
uniform_buffer,
}
}
fn load_shaders(&mut self, userdefs: &str) {
// Shaders //
let src = include_str!("shader.wgsl");
let src = src.replace("//INCLUDE//\n", userdefs);
let shader = self.device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: None,
source: wgpu::ShaderSource::Wgsl(src.into())
});
let vertex = wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[]
};
let fragment = wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[Some(wgpu::ColorTargetState {
format: self.config.format,
blend: Some(wgpu::BlendState {
color: wgpu::BlendComponent::REPLACE,
alpha: wgpu::BlendComponent::REPLACE,
}),
write_mask: wgpu::ColorWrites::ALL,
})],
};
// Pipeline //
let pipeline_layout = self.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &[&self.uniform_bind_group_layout],
push_constant_ranges: &[],
});
let render_pipeline = self.device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: None,
layout: Some(&pipeline_layout),
vertex,
fragment: Some(fragment),
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
});
self.render_pipeline = Some(render_pipeline);
}
fn redraw(&self, uniforms: &Uniforms) {
let frame = self.surface.get_current_texture().unwrap();
let view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
{
let color_attachment = wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: true,
}
};
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(color_attachment)],
depth_stencil_attachment: None,
});
if let Some(pipeline) = &self.render_pipeline {
rpass.set_pipeline(pipeline);
rpass.set_bind_group(0, &self.uniform_bind_group, &[]);
rpass.draw(0..3, 0..1);
rpass.draw(1..4, 0..1);
}
}
let mut uniform_buffer = UniformBuffer::new([0; Uniforms::SHADER_SIZE.get() as usize]);
uniform_buffer.write(uniforms).unwrap();
self.queue.write_buffer(&self.uniform_buffer, 0, &uniform_buffer.into_inner());
self.queue.submit(Some(encoder.finish()));
frame.present();
}
fn resize(&mut self, size: PhysicalSize<u32>) {
self.config.width = size.width;
self.config.height = size.height;
self.surface.configure(&self.device, &self.config);
}
}
pub async fn run(event_loop: EventLoop<()>, window: Window, code: &str) {
let mut state = State::new(&window).await;
state.load_shaders(code);
let mut uniforms = Uniforms {
resolution: [window.inner_size().width, window.inner_size().height].into(),
bounds_min: [0.73, 0.65].into(),
bounds_max: [0.98, 0.9].into(),
shading_intensity: 0.001,
};
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent { event: WindowEvent::CloseRequested, .. }
=> control_flow.set_exit(),
Event::RedrawRequested(_)
=> state.redraw(&uniforms),
Event::WindowEvent { event: WindowEvent::Resized(size), .. } => {
uniforms.resolution = [size.width, size.height].into();
state.resize(size);
window.request_redraw()
}
Event::MainEventsCleared => {
//window.request_redraw();
}
_ => (),
}
});
}

205
src/renderer/shader.wgsl Normal file
View File

@ -0,0 +1,205 @@
////////////////
// uniforms //
////////////////
struct Uniforms {
resolution: vec2u,
bounds_min: vec2f,
bounds_max: vec2f,
shading_intensity: f32,
}
@group(0) @binding(1) var<uniform> uniforms: Uniforms;
///////////////
// utility //
///////////////
fn remap(val: vec2f, a1: vec2f, b1: vec2f, a2: vec2f, b2: vec2f) -> vec2f {
return a2 + (b2 - a2) * ((val - a1) / (b1 - a1));
}
/////////////////
// constants //
/////////////////
const TAU = 6.283185307179586;
const E = 2.718281828459045;
/////////////////////////
// complex functions //
/////////////////////////
fn c_re(z: vec2f) -> vec2f {
return vec2(z.x, 0.0);
}
fn c_im(z: vec2f) -> vec2f {
return vec2(z.y, 0.0);
}
fn c_conj(z: vec2f) -> vec2f {
return z * vec2(1.0, -1.0);
}
fn c_abs_sq(z: vec2f) -> vec2f {
return vec2(dot(z, z), 0.0);
}
fn c_abs(z: vec2f) -> vec2f {
return vec2(sqrt(dot(z, z)), 0.0);
}
fn c_arg(z: vec2f) -> vec2f {
return vec2(atan2(z.y, z.x), 0.0);
}
fn c_add(u: vec2f, v: vec2f) -> vec2f {
return u + v;
}
fn c_sub(u: vec2f, v: vec2f) -> vec2f {
return u - v;
}
fn c_mul(u: vec2f, v: vec2f) -> vec2f {
return vec2(u.x*v.x - u.y*v.y, u.y*v.x + u.x*v.y);
}
fn c_div(u: vec2f, v: vec2f) -> vec2f {
return vec2(u.x*v.x + u.y*v.y, u.y*v.x - u.x*v.y) / dot(v, v);
}
fn c_pos(v: vec2f) -> vec2f {
return v;
}
fn c_neg(v: vec2f) -> vec2f {
return -v;
}
fn c_recip(v: vec2f) -> vec2f {
return vec2(v.x, -v.y) / dot(v, v);
}
fn c_exp(z: vec2f) -> vec2f {
return exp(z.x) * vec2(cos(z.y), sin(z.y));
}
fn c_log(z: vec2f) -> vec2f {
return vec2(0.5 * log(dot(z, z)), atan2(z.y, z.x));
}
fn c_pow(u: vec2f, v: vec2f) -> vec2f {
return c_exp(c_mul(c_log(u), v));
}
fn c_sqrt(z: vec2f) -> vec2f {
return c_pow(z, vec2(0.5, 0.0));
}
fn c_sin(z: vec2f) -> vec2f {
return vec2(sin(z.x)*cosh(z.y), cos(z.x)*sinh(z.y));
}
fn c_cos(z: vec2f) -> vec2f {
return vec2(cos(z.x)*cosh(z.y), -sin(z.x)*sinh(z.y));
}
fn c_tan(z: vec2f) -> vec2f {
return vec2(sin(2.0*z.x), sinh(2.0*z.y)) / (cos(2.0*z.x) + cosh(2.0*z.y));
}
fn c_sinh(z: vec2f) -> vec2f {
return vec2(sinh(z.x)*cos(z.y), cosh(z.x)*sin(z.y));
}
fn c_cosh(z: vec2f) -> vec2f {
return vec2(cosh(z.x)*cos(z.y), sinh(z.x)*sin(z.y));
}
fn c_tanh(z: vec2f) -> vec2f {
return vec2(sinh(2.0*z.x), sin(2.0*z.y)) / (cosh(2.0*z.x) + cos(2.0*z.y));
}
fn c_gamma(z: vec2f) -> vec2f {
let reflect = z.x < 0.5;
var zp = z;
if reflect {
zp = vec2(1.0, 0.0) - z;
}
var w = c_gamma_inner2(zp);
if reflect {
w = TAU * 0.5 * c_recip(c_mul(c_sin(TAU * 0.5 * z), w));
}
return w;
}
// Yang, ZH., Tian, JF. An accurate approximation formula for gamma function. J Inequal Appl 2018, 56 (2018).
// https://doi.org/10.1186/s13660-018-1646-6
fn c_gamma_inner(z: vec2f) -> vec2f {
let z2 = c_mul(z, z);
let z3 = c_mul(z2, z);
let a = c_sqrt(TAU * z);
let b = c_pow(1.0 / (E * E) * c_mul(z3, c_sinh(c_recip(z))), 0.5 * z);
let c = c_exp(7.0/324.0 * c_recip(c_mul(z3, 35.0 * z2 + 33.0)));
return c_mul(c_mul(a, b), c);
}
fn c_gamma_inner2(z: vec2f) -> vec2f {
let w = c_gamma_inner(z + vec2(3.0, 0.0));
return c_div(w, c_mul(c_mul(z, z + vec2(1.0, 0.0)), c_mul(z + vec2(2.0, 0.0), z + vec2(3.0, 0.0))));
}
const D_EPS = 0.01;
/////////////////
// user code //
/////////////////
//INCLUDE//
//////////////
// vertex //
//////////////
@vertex
fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4f {
var pos = array<vec2f, 4>(
vec2(-1.0,-1.0),
vec2( 1.0,-1.0),
vec2(-1.0, 1.0),
vec2( 1.0, 1.0),
);
return vec4f(pos[in_vertex_index], 0.0, 1.0);
}
////////////////
// fragment //
////////////////
fn hsv2rgb(c: vec3f) -> vec3f {
let p = abs(fract(c.xxx + vec3f(1.0, 2.0/3.0, 1.0/3.0)) * 6.0 - vec3f(3.0));
return c.z * mix(vec3f(1.0), clamp(p - vec3f(1.0), vec3f(0.0), vec3f(1.0)), c.y);
}
fn shademap(r: f32) -> f32 {
return r*inverseSqrt(r * r + 0.0625 * uniforms.shading_intensity)*0.9875 + 0.0125;
}
@fragment
fn fs_main(@builtin(position) in: vec4f) -> @location(0) vec4f {
let pos = vec2(in.x, f32(uniforms.resolution.y) - in.y);
var z = remap(pos, vec2(0.0), vec2f(uniforms.resolution), uniforms.bounds_min, uniforms.bounds_max);
z = func_plot(z);
let r = sqrt(z.x*z.x + z.y*z.y);
let arg = atan2(z.y, z.x);
let hsv = vec3f(arg / TAU + 1.0, shademap(1.0/r), shademap(r));
let col = pow(hsv2rgb(hsv), vec3(2.0));
return vec4f(col, 1.0);
}

15
syntax.md Normal file
View File

@ -0,0 +1,15 @@
```
f(z) = z^3 - 1
fp(z): deriv f(z)
n(z) = z - f(z)/fp(z)
plot(z): iter w = z, w = f(w), w, 100
```
unary ops: - / (neg recip)
binary ops: + - * / ^ (add sub mul div pow)
holomorphic fns: sqrt exp ln sin cos tan sinh cosh tanh asin acos atan asinh acosh atanh gamma
bad fns: re im abs abssq arg conj
higher order: iter deriv
constants: i tau e
type sigils: complex, $function, @integer