made changes

This commit is contained in:
TriMill 2023-06-15 00:27:09 -04:00
parent 36e2f03894
commit 4fcd317390
18 changed files with 528 additions and 230 deletions

View File

@ -1,8 +1,8 @@
[workspace]
members = [
"libcxgraph",
"cxgraph-desktop",
"cxgraph-web",
"libcxgraph",
"cxgraph-desktop",
"cxgraph-web",
]

8
README.md Normal file
View File

@ -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)

View File

@ -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();

View File

@ -8,8 +8,9 @@
<div class="canvas-container">
<canvas id="canvas"></canvas>
<svg id="overlay">
<line id="overlay_axis_x" x1="0" y1="0" x2="0" y2="0" stroke="#0006" stroke-width="1.5" visibility="hidden" />
<line id="overlay_axis_y" x1="0" y1="0" x2="0" y2="0" stroke="#0006" stroke-width="1.5" visibility="hidden" />
<line id="svg_axis_x" x1="0" y1="0" x2="0" y2="0" stroke="#0006" stroke-width="1.5" visibility="hidden" />
<line id="svg_axis_y" x1="0" y1="0" x2="0" y2="0" stroke="#0006" stroke-width="1.5" visibility="hidden" />
<circle id="svg_unitcircle" cx="0" cy="0" r="0" stroke="#0006" fill="none" stroke-width="1.5" visibility="hidden" />
<g id="svg_point_template" visibility="hidden">
<circle cx="0" cy="0" r="15" stroke="none" fill="#6664" />
<circle cx="0" cy="0" r="5" stroke="none" fill="#6666" />
@ -39,7 +40,7 @@
<summary>Options</summary>
<div>
<input type="button" id="button_reset_view" value="Reset view">
<input type="button" id="button_help" value="Help" onclick="window.open('docs.html')">
<input type="button" id="button_help" value="Help" onclick="window.open('https://g.trimill.xyz/trimill/cxgraph')">
</div>
<div>
<div><label for="range_resolution">Resolution</label></div>
@ -91,9 +92,13 @@
<div>
Overlay
<div>
<input type="checkbox" id="overlay_axes" checked>
<input type="checkbox" id="overlay_axes">
<label for="overlay_axes">Draw axes</label>
</div>
<div>
<input type="checkbox" id="overlay_unitcircle">
<label for="overlay_unitcircle">Draw unit circle</label>
</div>
</div>
</details>

View File

@ -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);
});
//

View File

@ -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;
});
});
}

View File

@ -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 {

165
docs/language.md Normal file
View File

@ -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 | "(" <Expr> ")"
Item := Number
| NAME
| "(" Expr ")"
| "{" Exprs "}"
| "sum" "(" NAME ":" INT "," INT ")" "{" Exprs "}"
| "prod" "(" NAME ":" INT "," INT ")" "{" Exprs "}"
| "iter" "(" INT "," NAME ":" Expr ")" "{" Exprs "}"
Number = FLOAT | INT
```

0
docs/web.md Normal file
View File

View File

@ -1,3 +1,3 @@
fn main() {
lalrpop::process_root().unwrap();
lalrpop::process_root().unwrap();
}

View File

@ -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<Expression<'a>>) -> 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)?,

View File

@ -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));

View File

@ -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<String> for CompileError {
fn from(value: String) -> Self {
Self(value)
}
fn from(value: String) -> Self {
Self(value)
}
}
impl From<fmt::Error> 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<String, CompileError> {
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])?;

View File

@ -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(<f64>),
Int => Token::Int(<i32>),
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(<f64>),
Int => Token::Int(<i32>),
Name => Token::Name(<&'input str>),
}
}
// Definitions
@ -36,88 +36,89 @@ extern {
pub Program: Vec<Definition<'input>> = Definitions;
Definitions: Vec<Definition<'input>> = {
"\n"* <defs:(<Definition> "\n"+)*> <last:Definition?> => defs.into_iter().chain(last).collect(),
"\n"* <defs:(<Definition> "\n"+)*> <last:Definition?> => defs.into_iter().chain(last).collect(),
}
Definition: Definition<'input> = {
<n:Name> "(" <args:(<Name> ",")*> <last:Name?> ")" "=" <exs:Exprs> => Definition::Function {
name: n,
args: args.into_iter().chain(last).collect(),
value: exs,
},
<n:Name> "=" <exs:Exprs> => Definition::Constant {
name: n,
value: exs,
},
<n:Name> "(" <args:(<Name> ",")*> <last:Name?> ")" "=" <exs:Exprs> => Definition::Function {
name: n,
args: args.into_iter().chain(last).collect(),
value: exs,
},
<n:Name> "=" <exs:Exprs> => Definition::Constant {
name: n,
value: exs,
},
}
// Expressions
Exprs: Vec<Expression<'input>> = {
<args:(<Expr> ",")*> <last:Expr> ","? => args.into_iter().chain(std::iter::once(last)).collect(),
<args:(<Expr> ",")*> <last:Expr> ","? => args.into_iter().chain(std::iter::once(last)).collect(),
}
Expr: Expression<'input> = Store;
Store: Expression<'input> = {
<a:Store> "->" <n:Name> => Expression::new_store(a, n),
Sum,
<a:Store> "->" <n:Name> => Expression::new_store(a, n),
Sum,
}
Sum: Expression<'input> = {
<a:Sum> "+" <b:Product> => Expression::new_binary(BinaryOp::Add, a, b),
<a:Sum> "-" <b:Product> => Expression::new_binary(BinaryOp::Sub, a, b),
Product,
<a:Sum> "+" <b:Product> => Expression::new_binary(BinaryOp::Add, a, b),
<a:Sum> "-" <b:Product> => Expression::new_binary(BinaryOp::Sub, a, b),
Product,
}
Product: Expression<'input> = {
<a:Product> "*" <b:Unary> => Expression::new_binary(BinaryOp::Mul, a, b),
<a:Product> "/" <b:Unary> => Expression::new_binary(BinaryOp::Div, a, b),
Unary,
<a:Product> "*" <b:Unary> => Expression::new_binary(BinaryOp::Mul, a, b),
<a:Product> "/" <b:Unary> => Expression::new_binary(BinaryOp::Div, a, b),
Unary,
}
Unary: Expression<'input> = {
"+" <a:Unary> => Expression::new_unary(UnaryOp::Pos, a),
"-" <a:Unary> => Expression::new_unary(UnaryOp::Neg, a),
"*" <a:Unary> => Expression::new_unary(UnaryOp::Conj, a),
<a:Juxtapose> <b:Power> => Expression::new_binary(BinaryOp::Mul, a, b),
Power,
"+" <a:Unary> => Expression::new_unary(UnaryOp::Pos, a),
"-" <a:Unary> => Expression::new_unary(UnaryOp::Neg, a),
"*" <a:Unary> => Expression::new_unary(UnaryOp::Conj, a),
<a:Juxtapose> <b:Power> => Expression::new_binary(BinaryOp::Mul, a, b),
Power,
}
Juxtapose: Expression<'input> = {
<a:Juxtapose> <b:PreJuxtapose> => Expression::new_binary(BinaryOp::Mul, a, b),
PreJuxtapose,
<a:Juxtapose> <b:PreJuxtapose> => Expression::new_binary(BinaryOp::Mul, a, b),
PreJuxtapose,
}
Power: Expression<'input> = {
<a:FnCall> "^" <b:Unary> => Expression::new_binary(BinaryOp::Pow, a, b),
FnCall,
<a:FnCall> "^" <b:Unary> => Expression::new_binary(BinaryOp::Pow, a, b),
FnCall,
}
FnCall: Expression<'input> = {
<n:Name> "(" <args:Exprs> ")"
=> Expression::new_fncall(n, args),
<Item>
<n:Name> "(" <args:Exprs> ")"
=> Expression::new_fncall(n, args),
<Item>
}
PreJuxtapose: Expression<'input> = {
Number,
"(" <Expr> ")",
Number,
"(" <Expr> ")",
}
Item: Expression<'input> = {
Number,
<n:Name> => Expression::new_name(n),
"(" <Expr> ")",
"sum" "(" <name:Name> ":" <min:Int> "," <max:Int> ")" "{" <exs:Exprs> "}"
=> Expression::new_sum(name, min, max, exs),
"prod" "(" <name:Name> ":" <min:Int> "," <max:Int> ")" "{" <exs:Exprs> "}"
=> Expression::new_prod(name, min, max, exs),
"iter" "(" <count:Int> "," <name:Name> ":" <init:Expr> ")" "{" <exs:Exprs> "}"
=> Expression::new_iter(name, count, init, exs),
Number,
<n:Name> => Expression::new_name(n),
"(" <Expr> ")",
"{" <exs:Exprs> "}" => Expression::new_block(exs),
"sum" "(" <name:Name> ":" <min:Int> "," <max:Int> ")" "{" <exs:Exprs> "}"
=> Expression::new_sum(name, min, max, exs),
"prod" "(" <name:Name> ":" <min:Int> "," <max:Int> ")" "{" <exs:Exprs> "}"
=> Expression::new_prod(name, min, max, exs),
"iter" "(" <count:Int> "," <name:Name> ":" <init:Expr> ")" "{" <exs:Exprs> "}"
=> Expression::new_iter(name, count, init, exs),
}
Number: Expression<'input> = {
<n:Float> => Expression::new_number(n),
<n:Int> => Expression::new_number(n as f64),
<n:Float> => Expression::new_number(n),
<n:Int> => Expression::new_number(n as f64),
}

View File

@ -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<T, L, E> = Result<(L, T, L), E>;
@ -61,6 +61,7 @@ pub type Spanned<T, L, E> = Result<(L, T, L), E>;
pub struct Lexer<'i> {
src: &'i str,
chars: Peekable<CharIndices<'i>>,
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<Token<'i>, usize, LexerError> {
@ -117,16 +122,29 @@ impl<'i> Lexer<'i> {
}
}
fn next_token(&mut self) -> Option<Spanned<Token<'i>, 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<Spanned<Token<'i>, 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<Token<'i>, usize, LexerError>;
type Item = Spanned<Token<'i>, usize, LexerError>;
fn next(&mut self) -> Option<Self::Item> {
fn next(&mut self) -> Option<Self::Item> {
self.next_token()
}
}
}

View File

@ -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);

View File

@ -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]);

View File

@ -1,11 +1,11 @@
@vertex
fn 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),
);
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);
return vec4f(pos[in_vertex_index], 0.0, 1.0);
}