@@ -91,9 +92,13 @@
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);
}