From 4fcd3173900ffd1d0dcd48e1696c3e5fb3790af0 Mon Sep 17 00:00:00 2001 From: TriMill Date: Thu, 15 Jun 2023 00:27:09 -0400 Subject: [PATCH] made changes --- Cargo.toml | 6 +- README.md | 8 ++ cxgraph-desktop/src/main.rs | 4 +- cxgraph-web/index.html | 13 +- cxgraph-web/index.js | 49 +++++-- cxgraph-web/src/lib.rs | 4 +- cxgraph-web/style.css | 5 +- docs/language.md | 165 ++++++++++++++++++++++++ docs/web.md | 0 libcxgraph/build.rs | 2 +- libcxgraph/src/language/ast.rs | 6 + libcxgraph/src/language/builtins.rs | 14 ++ libcxgraph/src/language/compiler.rs | 35 +++-- libcxgraph/src/language/syntax.lalrpop | 137 ++++++++++---------- libcxgraph/src/language/token.rs | 94 ++++++++------ libcxgraph/src/renderer/fragment.wgsl | 170 ++++++++++++++++--------- libcxgraph/src/renderer/mod.rs | 32 ++--- libcxgraph/src/renderer/vertex.wgsl | 14 +- 18 files changed, 528 insertions(+), 230 deletions(-) create mode 100644 README.md create mode 100644 docs/language.md create mode 100644 docs/web.md diff --git a/Cargo.toml b/Cargo.toml index 5d3b27c..1949363 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [workspace] members = [ - "libcxgraph", - "cxgraph-desktop", - "cxgraph-web", + "libcxgraph", + "cxgraph-desktop", + "cxgraph-web", ] diff --git a/README.md b/README.md new file mode 100644 index 0000000..13d07a8 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# cxgraph + +cxgraph is a complex function graphing tool built around WebGPU available +[on the web](https://cx.trimill.xyz/) or (slightly) for desktop. + +## documentation +- [language](docs/language.md) +- [web interface](docs/web.md) diff --git a/cxgraph-desktop/src/main.rs b/cxgraph-desktop/src/main.rs index ac0c9d5..4f3867c 100644 --- a/cxgraph-desktop/src/main.rs +++ b/cxgraph-desktop/src/main.rs @@ -21,10 +21,10 @@ fn main() { } async fn run(event_loop: EventLoop<()>, window: Window, code: &str) { - let size = window.inner_size(); + let size = window.inner_size(); let mut state = WgpuState::new(&window, size.into()).await; - state.load_shaders(code); + state.load_shaders(code); state.uniforms.bounds_min = (-5.0, -5.0).into(); state.uniforms.bounds_max = ( 5.0, 5.0).into(); diff --git a/cxgraph-web/index.html b/cxgraph-web/index.html index de29a02..c5d204c 100644 --- a/cxgraph-web/index.html +++ b/cxgraph-web/index.html @@ -8,8 +8,9 @@
- - + + + @@ -39,7 +40,7 @@ Options
- +
@@ -91,9 +92,13 @@
Overlay
- +
+
+ + +
diff --git a/cxgraph-web/index.js b/cxgraph-web/index.js index 7a4d5a9..935701b 100644 --- a/cxgraph-web/index.js +++ b/cxgraph-web/index.js @@ -19,7 +19,7 @@ let mouseY = 0.0; let mousePressed = false; function remap(x, lo1, hi1, lo2, hi2) { - return lo2 + (hi2 - lo2) * (x - lo1) / (hi1 - lo1); + return lo2 + (hi2 - lo2) * (x - lo1) / (hi1 - lo1); } function cxToScreen(cx) { @@ -70,15 +70,25 @@ function onViewChange() { let bounds = calcBounds(); cxgraph.set_bounds(bounds.x_min, bounds.y_min, bounds.x_max, bounds.y_max); let origin = cxToScreen({ re: 0, im: 0 }); + let one = cxToScreen({ re: 1, im: 0 }); - overlay_axis_x.setAttribute("x1", 0) - overlay_axis_x.setAttribute("x2", dim.w); - overlay_axis_x.setAttribute("y1", origin.y); - overlay_axis_x.setAttribute("y2", origin.y); - overlay_axis_y.setAttribute("x1", origin.x); - overlay_axis_y.setAttribute("x2", origin.x); - overlay_axis_y.setAttribute("y1", 0); - overlay_axis_y.setAttribute("y2", dim.h); + if(svg_axis_x.visibility != "hidden") { + svg_axis_x.setAttribute("x1", 0) + svg_axis_x.setAttribute("x2", dim.w); + svg_axis_x.setAttribute("y1", origin.y); + svg_axis_x.setAttribute("y2", origin.y); + } + if(svg_axis_y.visibility != "hidden") { + svg_axis_y.setAttribute("x1", origin.x); + svg_axis_y.setAttribute("x2", origin.x); + svg_axis_y.setAttribute("y1", 0); + svg_axis_y.setAttribute("y2", dim.h); + } + if(svg_unitcircle.visibility != "hidden") { + svg_unitcircle.setAttribute("cx", origin.x); + svg_unitcircle.setAttribute("cy", origin.y); + svg_unitcircle.setAttribute("r", one.x - origin.x); + } for(let point of graphPoints) { point.onViewChange(); @@ -148,7 +158,7 @@ function onGraph() { redraw(); } catch(e) { console.log(e); - div_error_msg.textContent = e.toString(); + div_error_msg.textContent = e.toString().replace("\n", "\r\n"); div_error_msg.hidden = false; } } @@ -214,10 +224,16 @@ let specialChars = new RegExp( console.log(specialChars); source_text.addEventListener("input", () => { + let e = source_text.selectionEnd; + let amnt = 0; source_text.value = source_text.value.replace( specialChars, - (_m, p) => charMap[p] - ) + (m, p) => { + amnt += m.length - charMap[p].length; + return charMap[p]; + } + ); + source_text.selectionEnd = e - amnt; }); // @@ -276,8 +292,13 @@ nameColorMode[0].checked = true; overlay_axes.addEventListener("change", () => { let vis = overlay_axes.checked ? "visible" : "hidden"; - overlay_axis_x.setAttribute("visibility", vis); - overlay_axis_y.setAttribute("visibility", vis); + svg_axis_x.setAttribute("visibility", vis); + svg_axis_y.setAttribute("visibility", vis); +}); + +overlay_unitcircle.addEventListener("change", () => { + let vis = overlay_unitcircle.checked ? "visible" : "hidden"; + svg_unitcircle.setAttribute("visibility", vis); }); // diff --git a/cxgraph-web/src/lib.rs b/cxgraph-web/src/lib.rs index c16160f..f7b8d22 100644 --- a/cxgraph-web/src/lib.rs +++ b/cxgraph-web/src/lib.rs @@ -42,7 +42,7 @@ pub async fn start() { .build(&event_loop) .expect("Failed to build window"); - let size = window.inner_size(); + let size = window.inner_size(); let mut state = WgpuState::new(&window, size.into()).await; state.uniforms.bounds_min = (-5.0, -5.0).into(); state.uniforms.bounds_max = ( 5.0, 5.0).into(); @@ -113,5 +113,5 @@ pub fn set_variable(idx: usize, re: f32, im: f32) { with_state(|state| { state.uniforms.variables[idx*2 + 0] = re; state.uniforms.variables[idx*2 + 1] = im; - }); + }); } diff --git a/cxgraph-web/style.css b/cxgraph-web/style.css index 326b9bb..91483fe 100644 --- a/cxgraph-web/style.css +++ b/cxgraph-web/style.css @@ -71,13 +71,14 @@ summary { } #source_text { - width: 300px; - height: 500px; + width: 400px; + height: 150px; font-size: 15px; } #div_error_msg { color: #f9a; + white-space: pre-line; } input { diff --git a/docs/language.md b/docs/language.md new file mode 100644 index 0000000..626e2dc --- /dev/null +++ b/docs/language.md @@ -0,0 +1,165 @@ +# cxgraph language + +cxgraph uses a custom expression language that is compiled to WGSL. + +## names + +names must begin with any alphabetic character (lowercase or capital letters, Greek letters, +etc.) and may contain alphanumeric chararcters as well as underscores (`_`) and apostrophes (`'`). +the words `sum`, `prod`, and `iter` may not be used for names. names may refer to either +functions or variables. + +examples of names include: +``` +a A aaa ω z_3 __5__ f' К'םαl'けx焼__검 +``` + +names may either be **built-in**, **global**, or **local**. global or local names may shadow +built-in names, and local names may shadow global ones. + +## declarations + +a **function declaration** declares a new function. functions may have zero or more arguments. + +``` +f(x) = 3 +``` + +a **constant declaration** declares a new constant. + +``` +n = 5 +``` + +declarations are separated by newlines. declarations may only reference functions and constants in +declarations that precede them. the name used in a declaration (`f` and `n` in the above examples) +is in the global scope + +## built-ins + + +arithmetic functions: +| name | description | +|---------------|-------------------------------------------------------| +| `pos(z)` | equivalent to unary `+` | +| `neg(z)` | equivalent to unary `-` | +| `conj(z)` | complex conjugate, equivalent to unary `*` | +| `re(z)` | real part | +| `im(z)` | imaginary part | +| `abs(z)` | absolute value (distance from `0`) | +| `abs_sq(z)` | square of absolute value | +| `arg(z)` | argument (angle about `0`) in the range `(-τ/2, τ/2]` | +| `argbr(z,br)` | argument in the range `(-τ/2, τ/2] + br` | +| `add(z,w)` | equivalent to `z + w` | +| `sub(z,w)` | equivalent to `z - w` | +| `mul(z,w)` | equivalent to `z * w` | +| `div(z,w)` | equivalent to `z / w` | +| `recip(z)` | reciprocal, equivalent to `1/z` | + +power/exponential functions: +| name | description | +|---------------|-------------------------------------------| +| `exp(z)` | exponential function, equivalent to `e^z` | +| `log(z)` | logarithm base `e` | +| `logbr(z,br)` | logarithm base `e` with specified branch | +| `pow(z)` | power, equivalent to `^` | +| `powbr(z,br)` | `pow` with specified branch | +| `sqrt(z)` | square root, equivalent to `z^0.5` | +| `sqrtbr(z,br)`| square root with specified branch | +| `cbrt(z)` | cube root, equivalent to `z^0.5` | +| `cbrtbr(z,br)`| cube root with specified branch | + +trigonometric functions: +| name | description | +|------------|-------------------------------------| +| `sin(z)` | sine function | +| `cos(z)` | cosine function | +| `tan(z)` | tangent function | +| `sinh(z)` | hyperbolic sine function | +| `cosh(z)` | hyperbolic cosine function | +| `tanh(z)` | hyperbolic tangent function | +| `asin(z)` | inverse sine function | +| `acos(z)` | inverse cosine function | +| `atan(z)` | inverse tangent function | +| `asinh(z)` | inverse hyperbolic sine function | +| `acosh(z)` | inverse hyperbolic cosine function | +| `atanh(z)` | inverse hyperbolic tangent function | + +special functions: +| function | description | +|--------------------------|--------------------------------------------------------------------| +| `gamma(z)`, `Γ(z)` | [gamma function](https://en.wikipedia.org/wiki/Gamma_function) | +| `invgamma(z)`, `invΓ(z)` | reciprocal of the gamma function | +| `loggamma(z)`, `logΓ(z)` | logarithm of the gamma function | +| `digamma(z)`, `ψ(z)` | [digamma function](https://en.wikipedia.org/wiki/Digamma_function) | + +logic functions: +| function | description | +|-----------------|----------------------------------------------------------------------------| +| `signre(z)` | sign of real part (1 if `re(z) > 0`, -1 if `re(z) < 0`, 0 if `re(z) == 0`) | +| `signim(z)` | sign of imaginary part | +| `ifgt(p,q,z,w)` | evaluates to `z` if `re(p) > re(q)`, otherwise `w` | +| `iflt(p,q,z,w)` | evaluates to `z` if `re(p) < re(q)`, otherwise `w` | +| `ifge(p,q,z,w)` | evaluates to `z` if `re(p) ≥ re(q)`, otherwise `w` | +| `ifle(p,q,z,w)` | evaluates to `z` if `re(p) ≤ re(q)`, otherwise `w` | +| `ifeq(p,q,z,w)` | evaluates to `z` if `re(p) = re(q)`, otherwise `w` | +| `ifne(p,q,z,w)` | evaluates to `z` if `re(p) ≠ re(q)`, otherwise `w` | +| `ifnan(p,z,w)` | evaluates to `z` if `p` is `NaN`, otherwise `w` | + +constants: +| name | description | +|----------------|--------------------------------------------------------------------------------------------------------| +| `i` | the imaginary constant, equal to `sqrt(-1)` | +| `e` | the [exponential constant](https://en.wikipedia.org/wiki/E_(mathematical_constant)), equal to `exp(1)` | +| `tau`, `τ` | the [circle constant](https://tauday.com/tau-manifesto) | +| `emgamma`, `γ` | the [Euler-Mascheroni](https://en.wikipedia.org/wiki/Euler%27s_constant) constant, equal to `-ψ(1)` | +| `phi`, `φ` | the [golden ratio](https://en.wikipedia.org/wiki/Golden_ratio), equal to `1/2 + sqrt(5)/2` | + +## ebnf grammar + +``` +Program := Definitions + +Definitions := NEWLINE* (Definition NEWLINE+)* Definition? + +Definition := NAME "(" (NAME ",") NAME? ")" "=" Exprs + | NAME "=" Exprs + +Exprs := (Expr ",")* Expr ","? + +Expr := Store + +Store := Store "->" NAME | Sum + +Sum := Sum "+" Product + | Sum "-" Product + | Product + +Product := Product "*" Unary + | Product "/" Unary + | Unary + +Unary := "+" Unary + | "-" Unary + | "*" Unary + | Juxtapose Power + | Power + +Juxtapose := Juxtapose PreJuxtapose | PreJuxtapose + +Power := FnCall "^" Unary | FnCall + +FnCall := NAME "(" Exprs ")" | Item + +PreJuxtapose := Number | "(" ")" + +Item := Number + | NAME + | "(" Expr ")" + | "{" Exprs "}" + | "sum" "(" NAME ":" INT "," INT ")" "{" Exprs "}" + | "prod" "(" NAME ":" INT "," INT ")" "{" Exprs "}" + | "iter" "(" INT "," NAME ":" Expr ")" "{" Exprs "}" + +Number = FLOAT | INT +``` diff --git a/docs/web.md b/docs/web.md new file mode 100644 index 0000000..e69de29 diff --git a/libcxgraph/build.rs b/libcxgraph/build.rs index ca5c283..2f459b8 100644 --- a/libcxgraph/build.rs +++ b/libcxgraph/build.rs @@ -1,3 +1,3 @@ fn main() { - lalrpop::process_root().unwrap(); + lalrpop::process_root().unwrap(); } diff --git a/libcxgraph/src/language/ast.rs b/libcxgraph/src/language/ast.rs index b95a2de..cddb959 100644 --- a/libcxgraph/src/language/ast.rs +++ b/libcxgraph/src/language/ast.rs @@ -14,6 +14,7 @@ pub enum UnaryOp { #[derive(Clone, Copy, Debug)] pub enum ExpressionType<'a> { + Block, Number(Complex), Name(&'a str), Binary(BinaryOp), @@ -32,6 +33,10 @@ pub struct Expression<'a> { } impl<'a> Expression<'a> { + pub fn new_block(exs: Vec>) -> Self { + Self { ty: ExpressionType::Block, children: exs } + } + pub fn new_number(x: f64) -> Self { Self { ty: ExpressionType::Number(Complex::new(x, 0.0)), children: Vec::with_capacity(0) } } @@ -87,6 +92,7 @@ pub enum Definition<'a> { fn display_expr(w: &mut impl fmt::Write, expr: &Expression, depth: usize) -> fmt::Result { let indent = depth*2; match expr.ty { + ExpressionType::Block => write!(w, "{:indent$}BLOCK", "", indent=indent)?, ExpressionType::Number(n) => write!(w, "{:indent$}NUMBER {n:?}", "", indent=indent)?, ExpressionType::Name(n) => write!(w, "{:indent$}NAME {n}", "", indent=indent)?, ExpressionType::Binary(op) => write!(w, "{:indent$}OP {op:?}", "", indent=indent)?, diff --git a/libcxgraph/src/language/builtins.rs b/libcxgraph/src/language/builtins.rs index 9612580..596349d 100644 --- a/libcxgraph/src/language/builtins.rs +++ b/libcxgraph/src/language/builtins.rs @@ -10,6 +10,14 @@ thread_local! { m.insert("recip", ("c_recip", 1)); m.insert("conj", ("c_conj", 1)); + m.insert("ifgt", ("c_ifgt", 4)); + m.insert("iflt", ("c_iflt", 4)); + m.insert("ifge", ("c_ifge", 4)); + m.insert("ifle", ("c_ifle", 4)); + m.insert("ifeq", ("c_ifeq", 4)); + m.insert("ifne", ("c_ifne", 4)); + m.insert("ifnan", ("c_ifnan", 3)); + m.insert("re", ("c_re", 1)); m.insert("im", ("c_im", 1)); m.insert("signre", ("c_signre", 1)); @@ -24,11 +32,15 @@ thread_local! { m.insert("mul", ("c_mul", 2)); m.insert("div", ("c_div", 2)); m.insert("pow", ("c_pow", 2)); + m.insert("powbr", ("c_powbr", 3)); m.insert("exp", ("c_exp", 1)); m.insert("log", ("c_log", 1)); m.insert("logbr", ("c_logbr", 2)); m.insert("sqrt", ("c_sqrt", 1)); + m.insert("sqrtbr", ("c_sqrtbr", 2)); + m.insert("cbrt", ("c_cbrt", 1)); + m.insert("cbrtbr", ("c_cbrtbr", 2)); m.insert("sin", ("c_sin", 1)); m.insert("cos", ("c_cos", 1)); @@ -46,6 +58,8 @@ thread_local! { m.insert("gamma", ("c_gamma", 1)); m.insert("\u{0393}", ("c_gamma", 1)); + m.insert("invgamma", ("c_invgamma", 1)); + m.insert("inv\u{0393}", ("c_invgamma", 1)); m.insert("loggamma", ("c_loggamma", 1)); m.insert("log\u{0393}", ("c_loggamma", 1)); m.insert("digamma", ("c_digamma", 1)); diff --git a/libcxgraph/src/language/compiler.rs b/libcxgraph/src/language/compiler.rs index 5b2de98..d76b4b1 100644 --- a/libcxgraph/src/language/compiler.rs +++ b/libcxgraph/src/language/compiler.rs @@ -6,23 +6,23 @@ use super::{ast::{Definition, Expression, ExpressionType, BinaryOp, UnaryOp}, bu pub struct CompileError(String); impl fmt::Display for CompileError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(&self.0) - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0) + } } impl std::error::Error for CompileError {} impl From for CompileError { - fn from(value: String) -> Self { - Self(value) - } + fn from(value: String) -> Self { + Self(value) + } } impl From for CompileError { - fn from(value: fmt::Error) -> Self { - Self(value.to_string()) - } + fn from(value: fmt::Error) -> Self { + Self(value.to_string()) + } } fn format_char(buf: &mut String, c: char) { @@ -136,9 +136,9 @@ impl<'w, 'i, W: fmt::Write> Compiler<'w, 'i, W> { pub fn ensure_plot_defined(&self) -> Result<(), CompileError> { if let Some(n) = self.global_funcs.get("plot") { if *n == 1 { - Ok(()) + Ok(()) } else { - Err("Plot function has wrong number of arguments".to_owned().into()) + Err("Plot function has wrong number of arguments".to_owned().into()) } } else { Err("No plot function defined".to_owned().into()) @@ -148,6 +148,19 @@ impl<'w, 'i, W: fmt::Write> Compiler<'w, 'i, W> { fn compile_expr(&mut self, local: &mut LocalState<'i>, expr: &Expression<'i>) -> Result { match expr.ty { + ExpressionType::Block => { + let tmp = local.next_tmp(); + writeln!(self.buf, "var {tmp}: vec2f;")?; + writeln!(self.buf, "{{")?; + let mut block_local = local.clone(); + let mut last = String::new(); + for child in &expr.children { + last = self.compile_expr(&mut block_local, child)?; + } + writeln!(self.buf, "{tmp} = {last};")?; + writeln!(self.buf, "}}")?; + Ok(tmp) + } ExpressionType::Name(v) => self.resolve_var(local, v), ExpressionType::Store(var) => { let a = self.compile_expr(local, &expr.children[0])?; diff --git a/libcxgraph/src/language/syntax.lalrpop b/libcxgraph/src/language/syntax.lalrpop index ed09132..0c249f8 100644 --- a/libcxgraph/src/language/syntax.lalrpop +++ b/libcxgraph/src/language/syntax.lalrpop @@ -4,31 +4,31 @@ use crate::language::token::*; grammar<'input>(input: &'input str); extern { - type Location = usize; - type Error = LexerError; + type Location = usize; + type Error = LexerError; - enum Token<'input> { - "(" => Token::LParen, - ")" => Token::RParen, - "{" => Token::LBrace, - "}" => Token::RBrace, - "+" => Token::Plus, - "-" => Token::Minus, - "*" => Token::Star, - "/" => Token::Slash, - "^" => Token::Caret, - "," => Token::Comma, - "->" => Token::Arrow, - "=" => Token::Equal, - ":" => Token::Colon, - "\n" => Token::Newline, - "sum" => Token::Sum, - "prod" => Token::Prod, - "iter" => Token::Iter, - Float => Token::Float(), - Int => Token::Int(), - Name => Token::Name(<&'input str>), - } + enum Token<'input> { + "(" => Token::LParen, + ")" => Token::RParen, + "{" => Token::LBrace, + "}" => Token::RBrace, + "+" => Token::Plus, + "-" => Token::Minus, + "*" => Token::Star, + "/" => Token::Slash, + "^" => Token::Caret, + "," => Token::Comma, + "->" => Token::Arrow, + "=" => Token::Equal, + ":" => Token::Colon, + "\n" => Token::Newline, + "sum" => Token::Sum, + "prod" => Token::Prod, + "iter" => Token::Iter, + Float => Token::Float(), + Int => Token::Int(), + Name => Token::Name(<&'input str>), + } } // Definitions @@ -36,88 +36,89 @@ extern { pub Program: Vec> = Definitions; Definitions: Vec> = { - "\n"* "\n"+)*> => defs.into_iter().chain(last).collect(), + "\n"* "\n"+)*> => defs.into_iter().chain(last).collect(), } Definition: Definition<'input> = { - "(" ",")*> ")" "=" => Definition::Function { - name: n, - args: args.into_iter().chain(last).collect(), - value: exs, - }, - "=" => Definition::Constant { - name: n, - value: exs, - }, + "(" ",")*> ")" "=" => Definition::Function { + name: n, + args: args.into_iter().chain(last).collect(), + value: exs, + }, + "=" => Definition::Constant { + name: n, + value: exs, + }, } // Expressions Exprs: Vec> = { - ",")*> ","? => args.into_iter().chain(std::iter::once(last)).collect(), + ",")*> ","? => args.into_iter().chain(std::iter::once(last)).collect(), } Expr: Expression<'input> = Store; Store: Expression<'input> = { - "->" => Expression::new_store(a, n), - Sum, + "->" => Expression::new_store(a, n), + Sum, } Sum: Expression<'input> = { - "+" => Expression::new_binary(BinaryOp::Add, a, b), - "-" => Expression::new_binary(BinaryOp::Sub, a, b), - Product, + "+" => Expression::new_binary(BinaryOp::Add, a, b), + "-" => Expression::new_binary(BinaryOp::Sub, a, b), + Product, } Product: Expression<'input> = { - "*" => Expression::new_binary(BinaryOp::Mul, a, b), - "/" => Expression::new_binary(BinaryOp::Div, a, b), - Unary, + "*" => Expression::new_binary(BinaryOp::Mul, a, b), + "/" => Expression::new_binary(BinaryOp::Div, a, b), + Unary, } Unary: Expression<'input> = { - "+" => Expression::new_unary(UnaryOp::Pos, a), - "-" => Expression::new_unary(UnaryOp::Neg, a), - "*" => Expression::new_unary(UnaryOp::Conj, a), - => Expression::new_binary(BinaryOp::Mul, a, b), - Power, + "+" => Expression::new_unary(UnaryOp::Pos, a), + "-" => Expression::new_unary(UnaryOp::Neg, a), + "*" => Expression::new_unary(UnaryOp::Conj, a), + => Expression::new_binary(BinaryOp::Mul, a, b), + Power, } Juxtapose: Expression<'input> = { - => Expression::new_binary(BinaryOp::Mul, a, b), - PreJuxtapose, + => Expression::new_binary(BinaryOp::Mul, a, b), + PreJuxtapose, } Power: Expression<'input> = { - "^" => Expression::new_binary(BinaryOp::Pow, a, b), - FnCall, + "^" => Expression::new_binary(BinaryOp::Pow, a, b), + FnCall, } FnCall: Expression<'input> = { - "(" ")" - => Expression::new_fncall(n, args), - + "(" ")" + => Expression::new_fncall(n, args), + } PreJuxtapose: Expression<'input> = { - Number, - "(" ")", + Number, + "(" ")", } Item: Expression<'input> = { - Number, - => Expression::new_name(n), - "(" ")", - "sum" "(" ":" "," ")" "{" "}" - => Expression::new_sum(name, min, max, exs), - "prod" "(" ":" "," ")" "{" "}" - => Expression::new_prod(name, min, max, exs), - "iter" "(" "," ":" ")" "{" "}" - => Expression::new_iter(name, count, init, exs), + Number, + => Expression::new_name(n), + "(" ")", + "{" "}" => Expression::new_block(exs), + "sum" "(" ":" "," ")" "{" "}" + => Expression::new_sum(name, min, max, exs), + "prod" "(" ":" "," ")" "{" "}" + => Expression::new_prod(name, min, max, exs), + "iter" "(" "," ":" ")" "{" "}" + => Expression::new_iter(name, count, init, exs), } Number: Expression<'input> = { - => Expression::new_number(n), - => Expression::new_number(n as f64), + => Expression::new_number(n), + => Expression::new_number(n as f64), } diff --git a/libcxgraph/src/language/token.rs b/libcxgraph/src/language/token.rs index ad01236..ef77304 100644 --- a/libcxgraph/src/language/token.rs +++ b/libcxgraph/src/language/token.rs @@ -7,7 +7,7 @@ pub enum Token<'i> { Int(i32), Name(&'i str), Sum, Prod, Iter, - LParen, RParen, + LParen, RParen, LBrace, RBrace, Plus, Minus, Star, Slash, Caret, Comma, Arrow, Equal, Colon, @@ -15,30 +15,30 @@ pub enum Token<'i> { } impl<'i> fmt::Display for Token<'i> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Token::Float(n) => write!(f, "{n}"), - Token::Int(n) => write!(f, "{n}"), - Token::Name(n) => write!(f, "{n}"), + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Token::Float(n) => write!(f, "{n}"), + Token::Int(n) => write!(f, "{n}"), + Token::Name(n) => write!(f, "{n}"), Token::Sum => f.write_str("sum"), Token::Prod => f.write_str("prod"), Token::Iter => f.write_str("iter"), - Token::LParen => f.write_str("("), - Token::RParen => f.write_str(")"), - Token::LBrace => f.write_str("{{"), - Token::RBrace => f.write_str("}}"), - Token::Plus => f.write_str("+"), - Token::Minus => f.write_str("-"), - Token::Star => f.write_str("*"), - Token::Slash => f.write_str("/"), - Token::Caret => f.write_str("^"), - Token::Comma => f.write_str(","), - Token::Arrow => f.write_str("->"), - Token::Equal => f.write_str("="), - Token::Colon => f.write_str(":"), - Token::Newline => f.write_str("newline") - } - } + Token::LParen => f.write_str("("), + Token::RParen => f.write_str(")"), + Token::LBrace => f.write_str("{"), + Token::RBrace => f.write_str("}"), + Token::Plus => f.write_str("+"), + Token::Minus => f.write_str("-"), + Token::Star => f.write_str("*"), + Token::Slash => f.write_str("/"), + Token::Caret => f.write_str("^"), + Token::Comma => f.write_str(","), + Token::Arrow => f.write_str("->"), + Token::Equal => f.write_str("="), + Token::Colon => f.write_str(":"), + Token::Newline => f.write_str("newline") + } + } } #[derive(Clone, Copy, Debug)] @@ -48,12 +48,12 @@ pub enum LexerError { } impl fmt::Display for LexerError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - LexerError::Unexpected(i, c) => write!(f, "Unexpected character {c:?} at {i}"), - LexerError::InvalidNumber(i, j) => write!(f, "Invalid number at {i}:{j}"), - } - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + LexerError::Unexpected(i, c) => write!(f, "Unexpected character {c:?} at {i}"), + LexerError::InvalidNumber(i, j) => write!(f, "Invalid number at {i}:{j}"), + } + } } pub type Spanned = Result<(L, T, L), E>; @@ -61,6 +61,7 @@ pub type Spanned = Result<(L, T, L), E>; pub struct Lexer<'i> { src: &'i str, chars: Peekable>, + bracket_depth: usize, } fn is_ident_begin(c: char) -> bool { @@ -73,7 +74,11 @@ fn is_ident_middle(c: char) -> bool { impl<'i> Lexer<'i> { pub fn new(src: &'i str) -> Self { - Self { src, chars: src.char_indices().peekable() } + Self { + src, + chars: src.char_indices().peekable(), + bracket_depth: 0, + } } fn next_number(&mut self, i: usize, mut has_dot: bool) -> Spanned, usize, LexerError> { @@ -117,16 +122,29 @@ impl<'i> Lexer<'i> { } } - fn next_token(&mut self) -> Option, usize, LexerError>> { - while matches!(self.chars.peek(), Some((_, ' ' | '\t' | '\r'))) { + fn skip_whitespace(&mut self) { + while matches!(self.chars.peek(), Some((_, ' ' | '\t' | '\n' | '\r'))) { + if self.bracket_depth == 0 && matches!(self.chars.peek(), Some((_, '\n'))) { + break + } self.chars.next(); } + } + + fn next_token(&mut self) -> Option, usize, LexerError>> { + self.skip_whitespace(); Some(match self.chars.next()? { - (i, '(') => Ok((i, Token::LParen, i + 1)), - (i, ')') => Ok((i, Token::RParen, i + 1)), - (i, '{') => Ok((i, Token::LBrace, i + 1)), - (i, '}') => Ok((i, Token::RBrace, i + 1)), + (_, '#') => { + while !matches!(self.chars.peek(), Some((_, '\n')) | None) { + self.chars.next(); + } + self.next_token()? + } + (i, '(') => { self.bracket_depth += 1; Ok((i, Token::LParen, i + 1)) }, + (i, ')') => { self.bracket_depth -= 1; Ok((i, Token::RParen, i + 1)) }, + (i, '{') => { self.bracket_depth += 1; Ok((i, Token::LBrace, i + 1)) }, + (i, '}') => { self.bracket_depth -= 1; Ok((i, Token::RBrace, i + 1)) }, (i, '+') => Ok((i, Token::Plus, i + 1)), (i, '-') => match self.chars.peek() { Some((_, '>')) => { @@ -151,9 +169,9 @@ impl<'i> Lexer<'i> { } impl<'i> Iterator for Lexer<'i> { - type Item = Spanned, usize, LexerError>; + type Item = Spanned, usize, LexerError>; - fn next(&mut self) -> Option { + fn next(&mut self) -> Option { self.next_token() - } + } } diff --git a/libcxgraph/src/renderer/fragment.wgsl b/libcxgraph/src/renderer/fragment.wgsl index 41254e5..c407116 100644 --- a/libcxgraph/src/renderer/fragment.wgsl +++ b/libcxgraph/src/renderer/fragment.wgsl @@ -31,6 +31,18 @@ fn correct_mod2(x: vec2f, y: vec2f) -> vec2f { return ((x % y) + y) % y; } +fn vlength(v: vec2f) -> f32 { + let l = length(v); + if l != l || l <= 3.4e+38 { + return l; + } + let a = max(abs(v.x), abs(v.y)); + let b = RECIP_SQRT2 * abs(v.x) + RECIP_SQRT2 * abs(v.y); + let c = 2.0 * RECIP_SQRT29 * abs(v.x) + 5.0 * RECIP_SQRT29 * abs(v.y); + let d = 5.0 * RECIP_SQRT29 * abs(v.x) + 2.0 * RECIP_SQRT29 * abs(v.y); + return max(max(a, b), max(c, d)); +} + ///////////////// // constants // ///////////////// @@ -40,6 +52,7 @@ const E = 2.718281828459045; const RECIP_SQRT2 = 0.7071067811865475; const LOG_TAU = 1.8378770664093453; const LOG_2 = 0.6931471805599453; +const RECIP_SQRT29 = 0.18569533817705186; const C_TAU = vec2f(TAU, 0.0); const C_E = vec2f(E, 0.0); @@ -67,6 +80,34 @@ fn c_signim(z: vec2f) -> vec2f { return vec2(sign(z.y), 0.0); } +fn c_ifgt(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f { + return select(w, z, p.x > q.x); +} + +fn c_iflt(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f { + return select(w, z, p.x < q.x); +} + +fn c_ifge(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f { + return select(w, z, p.x >= q.x); +} + +fn c_ifle(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f { + return select(w, z, p.x <= q.x); +} + +fn c_ifeq(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f { + return select(w, z, p.x == q.x); +} + +fn c_ifne(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f { + return select(w, z, p.x != q.x); +} + +fn c_ifnan(p: vec2f, z: vec2f, w: vec2f) -> vec2f { + return select(w, z, p.x != p.x && p.y != p.y); +} + fn c_conj(z: vec2f) -> vec2f { return z * vec2(1.0, -1.0); } @@ -76,17 +117,20 @@ fn c_abs_sq(z: vec2f) -> vec2f { } fn c_abs(z: vec2f) -> vec2f { - return vec2(length(z), 0.0); + return vec2(vlength(z), 0.0); } fn c_arg(z: vec2f) -> vec2f { + if z.x < 0.0 && z.y == 0.0 { + return vec2(TAU/2.0, 0.0); + } return vec2(atan2(z.y, z.x), 0.0); } fn c_argbr(z: vec2f, br: vec2f) -> vec2f { let r = vec2(cos(-br.x), sin(-br.x)); let zr = c_mul(z, r); - return vec2(atan2(zr.y, zr.x) + br.x, 0.0); + return c_arg(zr) + vec2(br.x, 0.0); } fn c_add(u: vec2f, v: vec2f) -> vec2f { @@ -122,7 +166,7 @@ fn c_exp(z: vec2f) -> vec2f { } fn c_log(z: vec2f) -> vec2f { - return vec2(0.5 * log(dot(z, z)), atan2(z.y, z.x)); + return vec2(0.5 * log(dot(z, z)), c_arg(z).x); } fn c_logbr(z: vec2f, br: vec2f) -> vec2f { @@ -133,10 +177,26 @@ fn c_pow(u: vec2f, v: vec2f) -> vec2f { return c_exp(c_mul(c_log(u), v)); } +fn c_powbr(u: vec2f, v: vec2f, br: vec2f) -> vec2f { + return c_exp(c_mul(c_logbr(u, br), v)); +} + fn c_sqrt(z: vec2f) -> vec2f { return c_pow(z, vec2(0.5, 0.0)); } +fn c_sqrtbr(z: vec2f, br: vec2f) -> vec2f { + return c_powbr(z, vec2(0.5, 0.0), br); +} + +fn c_cbrt(z: vec2f, br: vec2f) -> vec2f { + return c_pow(z, vec2(1.0/3.0, 0.0)); +} + +fn c_cbrtbr(z: vec2f, br: vec2f) -> vec2f { + return c_powbr(z, vec2(1.0/3.0, 0.0), br); +} + fn c_sin(z: vec2f) -> vec2f { return vec2(sin(z.x)*cosh(z.y), cos(z.x)*sinh(z.y)); } @@ -162,84 +222,50 @@ fn c_tanh(z: vec2f) -> vec2f { } fn c_asin(z: vec2f) -> vec2f { - let u = c_sqrt(vec2f(1.0, 0.0) - c_mul(z, z)); + let u = c_sqrt(vec2(1.0, 0.0) - c_mul(z, z)); let v = c_log(u + vec2(-z.y, z.x)); return vec2(v.y, -v.x); } fn c_acos(z: vec2f) -> vec2f { - let u = c_sqrt(vec2f(1.0, 0.0) - c_mul(z, z)); - let v = c_log(u + vec2f(-z.y, z.x)); - return vec2f(TAU*0.25 - v.y, v.x); + let u = c_sqrt(vec2(1.0, 0.0) - c_mul(z, z)); + let v = c_log(u + vec2(-z.y, z.x)); + return vec2(TAU*0.25 - v.y, v.x); } fn c_atan(z: vec2f) -> vec2f { - let u = vec2f(1.0, 0.0) - vec2f(-z.y, z.x); - let v = vec2f(1.0, 0.0) + vec2f(-z.y, z.x); + let u = vec2(1.0, 0.0) - vec2(-z.y, z.x); + let v = vec2(1.0, 0.0) + vec2(-z.y, z.x); let w = c_log(c_div(u, v)); - return 0.5 * vec2f(-w.y, w.x); + return 0.5 * vec2(-w.y, w.x); } fn c_asinh(z: vec2f) -> vec2f { - let u = c_sqrt(vec2f(1.0, 0.0) + c_mul(z, z)); + let u = c_sqrt(vec2(1.0, 0.0) + c_mul(z, z)); return c_log(u + z); } fn c_acosh(z: vec2f) -> vec2f { - let u = c_sqrt(vec2f(-1.0, 0.0) + c_mul(z, z)); + let u = c_sqrt(vec2(-1.0, 0.0) + c_mul(z, z)); return c_log(u + z); } fn c_atanh(z: vec2f) -> vec2f { - let u = vec2f(1.0, 0.0) + z; - let v = vec2f(1.0, 0.0) - z; + let u = vec2(1.0, 0.0) + z; + let v = vec2(1.0, 0.0) - z; return 0.5 * c_log(c_div(u, v)); } -// gamma // - -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)))); -} - // log gamma // fn c_loggamma(z: vec2f) -> vec2f { - let reflect = z.x < 0.5 && abs(z.y) < 10.0; + let reflect = z.x < 0.5 && abs(z.y) < 13.0; var zp = z; if reflect { zp = vec2(1.0, 0.0) - z; } var w = c_loggamma_inner2(zp); if reflect { - //let offset = select(0.0, TAU / 2.0, z % 2.0 < 1.0); let br = 0.5 * TAU * (0.5 - z.x) * sign(z.y); w = vec2(LOG_TAU - LOG_2, 0.0) - c_logbr(c_sin(TAU/2.0 * z), vec2(br, 0.0)) - w; } @@ -256,10 +282,20 @@ fn c_loggamma_inner2(z: vec2f) -> vec2f { return w - l; } +// gamma // + +fn c_gamma(z: vec2f) -> vec2f { + return c_exp(c_loggamma(z)); +} + +fn c_invgamma(z: vec2f) -> vec2f { + return c_exp(-c_loggamma(z)); +} + // digamma // fn c_digamma(z: vec2f) -> vec2f { - let reflect = z.x < 0.5; + let reflect = z.x < 0.5 && abs(z.y) < 13.0; var zp = z; if reflect { zp = vec2(1.0, 0.0) - z; @@ -290,8 +326,8 @@ fn c_digamma_inner2(z: vec2f) -> vec2f { ///////////////// 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); + 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 { @@ -308,13 +344,18 @@ fn coloring_standard(z: vec2f) -> vec3f { return vec3f(0.5, 0.5, 0.5); } - let mag_sq = dot(z, z); - if mag_sq > 3.40282347E+38 { + let mag = vlength(z); + if mag > 3.40282347E+38 { return vec3f(1.0, 1.0, 1.0); } - let mag = sqrt(mag_sq); + if uniforms.shading_intensity > 0.0 && mag > 1.8446E+19 { + return vec3f(1.0, 1.0, 1.0); + } + if uniforms.shading_intensity == 0.0 && mag < 1.0E-38 { + return vec3f(0.0, 0.0, 0.0); + } - let arg = atan2(z.y, z.x); + let arg = c_arg(z).x; let hsv = vec3f(arg / TAU + 1.0, shademap(1.0/mag), shademap(mag)); return hsv2rgb(hsv); @@ -328,13 +369,18 @@ fn coloring_uniform(z: vec2f) -> vec3f { return vec3f(0.5, 0.5, 0.5); } - let mag_sq = dot(z, z); - if mag_sq > 3.40282347E+38 { + let mag = vlength(z); + if mag > 3.40282347E+38 { return vec3f(1.0, 1.0, 1.0); } - let mag = sqrt(mag_sq); + if uniforms.shading_intensity > 0.0 && mag > 1.8446E+19 { + return vec3f(1.0, 1.0, 1.0); + } + if uniforms.shading_intensity == 0.0 && mag < 1.0E-38 { + return vec3f(0.0, 0.0, 0.0); + } - let arg = atan2(z.y, z.x); + let arg = c_arg(z).x; let r = cos(arg - 0.0*TAU/3.0)*0.5 + 0.5; let g = cos(arg - 1.0*TAU/3.0)*0.5 + 0.5; @@ -358,7 +404,7 @@ fn decoration_contour_im(z: vec2f) -> f32 { } fn decoration_contour_arg(z: vec2f) -> f32 { - let arg = atan2(z.y, z.x); + let arg = c_arg(z).x; return round(correct_mod(arg + TAU, TAU/8.0) * 8.0/TAU) * 2.0 - 1.0; } @@ -370,7 +416,7 @@ fn decoration_contour_mag(z: vec2f) -> f32 { @fragment fn main(@builtin(position) in: vec4f) -> @location(0) vec4f { let pos = vec2(in.x, f32(uniforms.resolution.y) - in.y); - let w = remap(pos, vec2(0.0), vec2f(uniforms.resolution), uniforms.bounds_min, uniforms.bounds_max); + let w = remap(pos, vec2(0.0, 0.0), vec2f(uniforms.resolution), uniforms.bounds_min, uniforms.bounds_max); let z = func_plot(w); diff --git a/libcxgraph/src/renderer/mod.rs b/libcxgraph/src/renderer/mod.rs index 0c4df04..729efde 100644 --- a/libcxgraph/src/renderer/mod.rs +++ b/libcxgraph/src/renderer/mod.rs @@ -54,7 +54,7 @@ impl WgpuState { where W: raw_window_handle::HasRawWindowHandle + raw_window_handle::HasRawDisplayHandle { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default()); - let surface = unsafe { instance.create_surface(&window) }.unwrap(); + let surface = unsafe { instance.create_surface(&window) }.unwrap(); let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::default(), @@ -125,17 +125,17 @@ impl WgpuState { // Done // - let uniforms = Uniforms { + let uniforms = Uniforms { variables: [0.0; 16], - resolution: size.into(), - bounds_min: (-0.0, -0.0), - bounds_max: ( 0.0, 0.0), - shading_intensity: 0.0, - contour_intensity: 0.0, + resolution: size.into(), + bounds_min: (-0.0, -0.0), + bounds_max: ( 0.0, 0.0), + shading_intensity: 0.0, + contour_intensity: 0.0, decorations: 0, coloring: 0, _padding: [0; 8], - }; + }; Self { uniforms, @@ -187,10 +187,10 @@ impl WgpuState { // Pipeline // let pipeline_layout = self.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: None, - bind_group_layouts: &[&self.uniform_layout], - push_constant_ranges: &[], - }); + label: None, + bind_group_layouts: &[&self.uniform_layout], + push_constant_ranges: &[], + }); let render_pipeline = self.device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, @@ -226,10 +226,10 @@ impl WgpuState { 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); + 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 cursor = Cursor::new([0; UNIFORM_SIZE]); diff --git a/libcxgraph/src/renderer/vertex.wgsl b/libcxgraph/src/renderer/vertex.wgsl index 4d6899c..8fac842 100644 --- a/libcxgraph/src/renderer/vertex.wgsl +++ b/libcxgraph/src/renderer/vertex.wgsl @@ -1,11 +1,11 @@ @vertex fn main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4f { - var pos = array( - vec2(-1.0,-1.0), - vec2( 1.0,-1.0), - vec2(-1.0, 1.0), - vec2( 1.0, 1.0), - ); + var pos = array( + 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); + return vec4f(pos[in_vertex_index], 0.0, 1.0); }