This commit is contained in:
trimill 2024-08-30 10:02:02 -04:00
parent 4fcd317390
commit 8702cddf44
11 changed files with 820 additions and 518 deletions

852
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -5,4 +5,4 @@ members = [
"cxgraph-desktop", "cxgraph-desktop",
"cxgraph-web", "cxgraph-web",
] ]
resolver = "2"

View file

@ -3,6 +3,15 @@
cxgraph is a complex function graphing tool built around WebGPU available cxgraph is a complex function graphing tool built around WebGPU available
[on the web](https://cx.trimill.xyz/) or (slightly) for desktop. [on the web](https://cx.trimill.xyz/) or (slightly) for desktop.
## building (web)
install `wasm-pack` through your package manager or with `cargo install wasm-pack`.
```sh
cd cxgraph-web
wasm-pack build --no-typescript --no-pack --target web
```
## documentation ## documentation
- [language](docs/language.md) - [language](docs/language.md)
- [web interface](docs/web.md) - [web interface](docs/web.md)

View file

@ -4,9 +4,9 @@ import init, * as cxgraph from "./pkg/cxgraph_web.js";
await init(); await init();
let graphView = { let graphView = {
xoff: 0, xoff: 0.00001,
yoff: 0, yoff: 0.00001,
scale: 3, scale: 2.99932736,
res_mult: 1, res_mult: 1,
varNames: [], varNames: [],
}; };
@ -217,13 +217,23 @@ let charMap = {
"Psi": "\u03a8", "Psi": "\u03a8",
"Omega": "\u03a9", "Omega": "\u03a9",
"vartheta": "\u03d1", "vartheta": "\u03d1",
"0": "\u2080",
"1": "\u2081",
"2": "\u2082",
"3": "\u2083",
"4": "\u2084",
"5": "\u2085",
"6": "\u2086",
"7": "\u2087",
"8": "\u2088",
"9": "\u2089",
}; };
let specialChars = new RegExp( let specialChars = new RegExp(
`\\\\(${Object.keys(charMap).join("|")})` `\\\\(${Object.keys(charMap).join("|")})`
); );
console.log(specialChars);
source_text.addEventListener("input", () => { source_text.addEventListener("input", (event) => {
if(event.isComposing) return;
let e = source_text.selectionEnd; let e = source_text.selectionEnd;
let amnt = 0; let amnt = 0;
source_text.value = source_text.value.replace( source_text.value = source_text.value.replace(

View file

@ -13,6 +13,7 @@ lalrpop-util = { version = "0.20.0", features = ["lexer", "unicode"] }
num-complex = "0.4" num-complex = "0.4"
wgpu = "0.16" wgpu = "0.16"
raw-window-handle = "0.5" raw-window-handle = "0.5"
unicode-xid = "0.2"
[build-dependencies] [build-dependencies]
lalrpop = "0.20.0" lalrpop = "0.20.0"

View file

@ -5,6 +5,7 @@ use num_complex::Complex64 as Complex;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum BinaryOp { pub enum BinaryOp {
Add, Sub, Mul, Div, Pow, Add, Sub, Mul, Div, Pow,
Gt, Lt, Ge, Le, Eq, Ne,
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@ -21,6 +22,7 @@ pub enum ExpressionType<'a> {
Unary(UnaryOp), Unary(UnaryOp),
FnCall(&'a str), FnCall(&'a str),
Store(&'a str), Store(&'a str),
If,
Sum { countvar: &'a str, min: i32, max: i32 }, Sum { countvar: &'a str, min: i32, max: i32 },
Prod { countvar: &'a str, min: i32, max: i32 }, Prod { countvar: &'a str, min: i32, max: i32 },
Iter { itervar: &'a str, count: i32 }, Iter { itervar: &'a str, count: i32 },
@ -61,25 +63,31 @@ impl<'a> Expression<'a> {
Self { ty: ExpressionType::Store(name), children: vec![expr] } Self { ty: ExpressionType::Store(name), children: vec![expr] }
} }
pub fn new_sum(countvar: &'a str, min: i32, max: i32, body: Vec<Self>) -> Self { pub fn new_if(cond: Self, t: Self, f: Self) -> Self {
Self {
ty: ExpressionType::If,
children: vec![cond, t, f],
}
}
pub fn new_sum(countvar: &'a str, min: i32, max: i32, body: Self) -> Self {
Self { Self {
ty: ExpressionType::Sum { countvar, min, max }, ty: ExpressionType::Sum { countvar, min, max },
children: body, children: vec![body],
} }
} }
pub fn new_prod(accvar: &'a str, min: i32, max: i32, body: Vec<Self>) -> Self { pub fn new_prod(accvar: &'a str, min: i32, max: i32, body: Self) -> Self {
Self { Self {
ty: ExpressionType::Prod { countvar: accvar, min, max }, ty: ExpressionType::Prod { countvar: accvar, min, max },
children: body, children: vec![body],
} }
} }
pub fn new_iter(itervar: &'a str, count: i32, init: Self, mut body: Vec<Self>) -> Self { pub fn new_iter(itervar: &'a str, count: i32, init: Self, body: Self) -> Self {
body.push(init);
Self { Self {
ty: ExpressionType::Iter { itervar, count }, ty: ExpressionType::Iter { itervar, count },
children: body, children: vec![init, body],
} }
} }
} }
@ -99,6 +107,7 @@ fn display_expr(w: &mut impl fmt::Write, expr: &Expression, depth: usize) -> fmt
ExpressionType::Unary(op) => write!(w, "{:indent$}OP {op:?}", "", indent=indent)?, ExpressionType::Unary(op) => write!(w, "{:indent$}OP {op:?}", "", indent=indent)?,
ExpressionType::FnCall(f) => write!(w, "{:indent$}CALL {f}", "", indent=indent)?, ExpressionType::FnCall(f) => write!(w, "{:indent$}CALL {f}", "", indent=indent)?,
ExpressionType::Store(n) => write!(w, "{:indent$}STORE {n}", "", indent=indent)?, ExpressionType::Store(n) => write!(w, "{:indent$}STORE {n}", "", indent=indent)?,
ExpressionType::If => write!(w, "{:indent$}IF", "", indent=indent)?,
ExpressionType::Sum { countvar, min, max } => write!(w, "{:indent$}SUM {countvar} {min} {max}", "", indent=indent)?, ExpressionType::Sum { countvar, min, max } => write!(w, "{:indent$}SUM {countvar} {min} {max}", "", indent=indent)?,
ExpressionType::Prod { countvar, min, max } => write!(w, "{:indent$}PROD {countvar} {min} {max}", "", indent=indent)?, ExpressionType::Prod { countvar, min, max } => write!(w, "{:indent$}PROD {countvar} {min} {max}", "", indent=indent)?,
ExpressionType::Iter { itervar, count } => write!(w, "{:indent$}ITER {itervar} {count}", "", indent=indent)?, ExpressionType::Iter { itervar, count } => write!(w, "{:indent$}ITER {itervar} {count}", "", indent=indent)?,

View file

@ -10,18 +10,13 @@ thread_local! {
m.insert("recip", ("c_recip", 1)); m.insert("recip", ("c_recip", 1));
m.insert("conj", ("c_conj", 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("re", ("c_re", 1));
m.insert("im", ("c_im", 1)); m.insert("im", ("c_im", 1));
m.insert("signre", ("c_signre", 1)); m.insert("signre", ("c_signre", 1));
m.insert("signim", ("c_signim", 1)); m.insert("signim", ("c_signim", 1));
m.insert("absre", ("c_absre", 1));
m.insert("absim", ("c_absim", 1));
m.insert("isnan", ("c_isnan", 1));
m.insert("abs_sq", ("c_abs_sq", 1)); m.insert("abs_sq", ("c_abs_sq", 1));
m.insert("abs", ("c_abs", 1)); m.insert("abs", ("c_abs", 1));
m.insert("arg", ("c_arg", 1)); m.insert("arg", ("c_arg", 1));
@ -48,7 +43,6 @@ thread_local! {
m.insert("sinh", ("c_sinh", 1)); m.insert("sinh", ("c_sinh", 1));
m.insert("cosh", ("c_cosh", 1)); m.insert("cosh", ("c_cosh", 1));
m.insert("tanh", ("c_tanh", 1)); m.insert("tanh", ("c_tanh", 1));
m.insert("asin", ("c_asin", 1)); m.insert("asin", ("c_asin", 1));
m.insert("acos", ("c_acos", 1)); m.insert("acos", ("c_acos", 1));
m.insert("atan", ("c_atan", 1)); m.insert("atan", ("c_atan", 1));
@ -64,6 +58,8 @@ thread_local! {
m.insert("log\u{0393}", ("c_loggamma", 1)); m.insert("log\u{0393}", ("c_loggamma", 1));
m.insert("digamma", ("c_digamma", 1)); m.insert("digamma", ("c_digamma", 1));
m.insert("\u{03C8}", ("c_digamma", 1)); m.insert("\u{03C8}", ("c_digamma", 1));
m.insert("lambertw", ("c_lambertw", 1));
m.insert("lambertwbr", ("c_lambertwbr", 2));
m m
}; };

View file

@ -29,6 +29,11 @@ fn format_char(buf: &mut String, c: char) {
match c { match c {
'_' => buf.push_str("u_"), '_' => buf.push_str("u_"),
'\'' => buf.push_str("p_"), '\'' => buf.push_str("p_"),
'\u{2080}'..='\u{2089}' => {
buf.push('s');
buf.push((c as u32 - 0x2080 + '0' as u32).try_into().expect("invalid codepoint"));
buf.push('_');
}
c => buf.push(c), c => buf.push(c),
} }
} }
@ -190,6 +195,12 @@ impl<'w, 'i, W: fmt::Write> Compiler<'w, 'i, W> {
BinaryOp::Mul => writeln!(self.buf, "var {name} = c_mul({a}, {b});")?, BinaryOp::Mul => writeln!(self.buf, "var {name} = c_mul({a}, {b});")?,
BinaryOp::Div => writeln!(self.buf, "var {name} = c_div({a}, {b});")?, BinaryOp::Div => writeln!(self.buf, "var {name} = c_div({a}, {b});")?,
BinaryOp::Pow => writeln!(self.buf, "var {name} = c_pow({a}, {b});")?, BinaryOp::Pow => writeln!(self.buf, "var {name} = c_pow({a}, {b});")?,
BinaryOp::Gt => writeln!(self.buf, "var {name} = select(C_ZERO, C_ONE, {a}.x > {b}.x);")?,
BinaryOp::Lt => writeln!(self.buf, "var {name} = select(C_ZERO, C_ONE, {a}.x < {b}.x);")?,
BinaryOp::Ge => writeln!(self.buf, "var {name} = select(C_ZERO, C_ONE, {a}.x >= {b}.x);")?,
BinaryOp::Le => writeln!(self.buf, "var {name} = select(C_ZERO, C_ONE, {a}.x <= {b}.x);")?,
BinaryOp::Eq => writeln!(self.buf, "var {name} = select(C_ZERO, C_ONE, ({a}.x == {b}.x) && ({a}.y == {b}.y));")?,
BinaryOp::Ne => writeln!(self.buf, "var {name} = select(C_ZERO, C_ONE, ({a}.x != {b}.x) || ({a}.y != {b}.y));")?,
} }
Ok(name) Ok(name)
@ -226,6 +237,19 @@ impl<'w, 'i, W: fmt::Write> Compiler<'w, 'i, W> {
Ok(name) Ok(name)
}, },
ExpressionType::If => {
let cond = self.compile_expr(local, &expr.children[0])?;
let result = local.next_tmp();
writeln!(self.buf, "var {result}: vec2f;")?;
writeln!(self.buf, "if {cond}.x > 0.0 {{")?;
let t = self.compile_expr(local, &expr.children[1])?;
writeln!(self.buf, "{result} = {t};")?;
writeln!(self.buf, "}} else {{")?;
let f = self.compile_expr(local, &expr.children[2])?;
writeln!(self.buf, "{result} = {f};")?;
writeln!(self.buf, "}}")?;
Ok(result)
},
ExpressionType::Sum { countvar, min, max } ExpressionType::Sum { countvar, min, max }
| ExpressionType::Prod { countvar, min, max } => { | ExpressionType::Prod { countvar, min, max } => {
let acc = local.next_tmp(); let acc = local.next_tmp();
@ -239,19 +263,17 @@ impl<'w, 'i, W: fmt::Write> Compiler<'w, 'i, W> {
writeln!(self.buf, "var {} = vec2f(f32({ivar}), 0.0);", format_local(countvar))?; writeln!(self.buf, "var {} = vec2f(f32({ivar}), 0.0);", format_local(countvar))?;
let mut loop_local = local.clone(); let mut loop_local = local.clone();
loop_local.local_vars.insert(countvar); loop_local.local_vars.insert(countvar);
let mut last = String::new(); let body = self.compile_expr(&mut loop_local, &expr.children[0])?;
for child in &expr.children {
last = self.compile_expr(&mut loop_local, child)?;
}
if matches!(expr.ty, ExpressionType::Sum { .. }) { if matches!(expr.ty, ExpressionType::Sum { .. }) {
writeln!(self.buf, "{acc} = {acc} + {last};\n}}")?; writeln!(self.buf, "{acc} = {acc} + {body};")?;
} else { } else {
writeln!(self.buf, "{acc} = c_mul({acc}, {last});\n}}")?; writeln!(self.buf, "{acc} = c_mul({acc}, {body});")?;
} }
writeln!(self.buf, "}}")?;
Ok(acc) Ok(acc)
}, },
ExpressionType::Iter { itervar, count } => { ExpressionType::Iter { itervar, count } => {
let init = expr.children.last().unwrap(); let init = &expr.children[0];
let itervar_fmt = format_local(itervar); let itervar_fmt = format_local(itervar);
let v = self.compile_expr(local, init)?; let v = self.compile_expr(local, init)?;
writeln!(self.buf, "var {itervar_fmt} = {v};")?; writeln!(self.buf, "var {itervar_fmt} = {v};")?;
@ -259,11 +281,9 @@ impl<'w, 'i, W: fmt::Write> Compiler<'w, 'i, W> {
writeln!(self.buf, "for(var {ivar}: i32 = 0; {ivar} < {count}; {ivar}++) {{")?; writeln!(self.buf, "for(var {ivar}: i32 = 0; {ivar} < {count}; {ivar}++) {{")?;
let mut loop_local = local.clone(); let mut loop_local = local.clone();
loop_local.local_vars.insert(itervar); loop_local.local_vars.insert(itervar);
let mut last = String::new(); let body = self.compile_expr(&mut loop_local, &expr.children[1])?;
for child in &expr.children[..expr.children.len() - 1] { writeln!(self.buf, "{itervar_fmt} = {body};")?;
last = self.compile_expr(&mut loop_local, child)?; writeln!(self.buf, "}}")?;
}
writeln!(self.buf, "{itervar_fmt} = {last};\n}}")?;
Ok(itervar_fmt) Ok(itervar_fmt)
} }
} }

View file

@ -21,10 +21,17 @@ extern {
"->" => Token::Arrow, "->" => Token::Arrow,
"=" => Token::Equal, "=" => Token::Equal,
":" => Token::Colon, ":" => Token::Colon,
">" => Token::Greater,
"<" => Token::Less,
">=" => Token::GreaterEqual,
"<=" => Token::LessEqual,
"==" => Token::EqualEqual,
"!=" => Token::BangEqual,
"\n" => Token::Newline, "\n" => Token::Newline,
"sum" => Token::Sum, "sum" => Token::Sum,
"prod" => Token::Prod, "prod" => Token::Prod,
"iter" => Token::Iter, "iter" => Token::Iter,
"if" => Token::If,
Float => Token::Float(<f64>), Float => Token::Float(<f64>),
Int => Token::Int(<i32>), Int => Token::Int(<i32>),
Name => Token::Name(<&'input str>), Name => Token::Name(<&'input str>),
@ -60,7 +67,21 @@ Exprs: Vec<Expression<'input>> = {
Expr: Expression<'input> = Store; Expr: Expression<'input> = Store;
Store: Expression<'input> = { Store: Expression<'input> = {
<a:Store> "->" <n:Name> => Expression::new_store(a, n), <a:Equality> "->" <n:Name> => Expression::new_store(a, n),
Equality,
}
Equality: Expression<'input> = {
<a:Compare> "==" <b:Compare> => Expression::new_binary(BinaryOp::Eq, a, b),
<a:Compare> "!=" <b:Compare> => Expression::new_binary(BinaryOp::Ne, a, b),
Compare,
}
Compare: Expression<'input> = {
<a:Sum> ">" <b:Sum> => Expression::new_binary(BinaryOp::Gt, a, b),
<a:Sum> "<" <b:Sum> => Expression::new_binary(BinaryOp::Lt, a, b),
<a:Sum> ">=" <b:Sum> => Expression::new_binary(BinaryOp::Ge, a, b),
<a:Sum> "<=" <b:Sum> => Expression::new_binary(BinaryOp::Le, a, b),
Sum, Sum,
} }
@ -105,17 +126,23 @@ PreJuxtapose: Expression<'input> = {
"(" <Expr> ")", "(" <Expr> ")",
} }
Block: Expression<'input> = {
"{" <exs:Exprs> "}" => Expression::new_block(exs),
}
Item: Expression<'input> = { Item: Expression<'input> = {
Number, Number,
<n:Name> => Expression::new_name(n), <n:Name> => Expression::new_name(n),
"(" <Expr> ")", "(" <Expr> ")",
"{" <exs:Exprs> "}" => Expression::new_block(exs), Block,
"sum" "(" <name:Name> ":" <min:Int> "," <max:Int> ")" "{" <exs:Exprs> "}" "sum" "(" <name:Name> ":" <min:Int> "," <max:Int> ")" <body:Block>
=> Expression::new_sum(name, min, max, exs), => Expression::new_sum(name, min, max, body),
"prod" "(" <name:Name> ":" <min:Int> "," <max:Int> ")" "{" <exs:Exprs> "}" "prod" "(" <name:Name> ":" <min:Int> "," <max:Int> ")" <body:Block>
=> Expression::new_prod(name, min, max, exs), => Expression::new_prod(name, min, max, body),
"iter" "(" <count:Int> "," <name:Name> ":" <init:Expr> ")" "{" <exs:Exprs> "}" "iter" "(" <count:Int> "," <name:Name> ":" <init:Expr> ")" <body:Block>
=> Expression::new_iter(name, count, init, exs), => Expression::new_iter(name, count, init, body),
"if" "(" <cond:Expr> ")" <t:Block> <f:Block>
=> Expression::new_if(cond, t, f),
} }
Number: Expression<'input> = { Number: Expression<'input> = {

View file

@ -1,15 +1,19 @@
use std::{str::CharIndices, iter::Peekable, fmt}; use std::{str::CharIndices, iter::Peekable, fmt};
use unicode_xid::UnicodeXID;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum Token<'i> { pub enum Token<'i> {
Float(f64), Float(f64),
Int(i32), Int(i32),
Name(&'i str), Name(&'i str),
Sum, Prod, Iter, Sum, Prod, Iter, If,
LParen, RParen, LParen, RParen,
LBrace, RBrace, LBrace, RBrace,
Plus, Minus, Star, Slash, Caret, Plus, Minus, Star, Slash, Caret,
Greater, Less, GreaterEqual, LessEqual,
EqualEqual, BangEqual,
Comma, Arrow, Equal, Colon, Comma, Arrow, Equal, Colon,
Newline, Newline,
} }
@ -23,6 +27,7 @@ impl<'i> fmt::Display for Token<'i> {
Token::Sum => f.write_str("sum"), Token::Sum => f.write_str("sum"),
Token::Prod => f.write_str("prod"), Token::Prod => f.write_str("prod"),
Token::Iter => f.write_str("iter"), Token::Iter => f.write_str("iter"),
Token::If => f.write_str("if"),
Token::LParen => f.write_str("("), Token::LParen => f.write_str("("),
Token::RParen => f.write_str(")"), Token::RParen => f.write_str(")"),
Token::LBrace => f.write_str("{"), Token::LBrace => f.write_str("{"),
@ -36,6 +41,12 @@ impl<'i> fmt::Display for Token<'i> {
Token::Arrow => f.write_str("->"), Token::Arrow => f.write_str("->"),
Token::Equal => f.write_str("="), Token::Equal => f.write_str("="),
Token::Colon => f.write_str(":"), Token::Colon => f.write_str(":"),
Token::Greater => f.write_str(">"),
Token::Less => f.write_str("<"),
Token::GreaterEqual => f.write_str(">="),
Token::LessEqual => f.write_str("<="),
Token::EqualEqual => f.write_str("=="),
Token::BangEqual => f.write_str("!="),
Token::Newline => f.write_str("newline") Token::Newline => f.write_str("newline")
} }
} }
@ -44,12 +55,14 @@ impl<'i> fmt::Display for Token<'i> {
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum LexerError { pub enum LexerError {
Unexpected(usize, char), Unexpected(usize, char),
UnexpectedEof,
InvalidNumber(usize, usize), InvalidNumber(usize, usize),
} }
impl fmt::Display for LexerError { impl fmt::Display for LexerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
LexerError::UnexpectedEof => write!(f, "Unexpected EOF during lexing"),
LexerError::Unexpected(i, c) => write!(f, "Unexpected character {c:?} at {i}"), LexerError::Unexpected(i, c) => write!(f, "Unexpected character {c:?} at {i}"),
LexerError::InvalidNumber(i, j) => write!(f, "Invalid number at {i}:{j}"), LexerError::InvalidNumber(i, j) => write!(f, "Invalid number at {i}:{j}"),
} }
@ -61,15 +74,15 @@ pub type Spanned<T, L, E> = Result<(L, T, L), E>;
pub struct Lexer<'i> { pub struct Lexer<'i> {
src: &'i str, src: &'i str,
chars: Peekable<CharIndices<'i>>, chars: Peekable<CharIndices<'i>>,
bracket_depth: usize, bracket_depth: isize,
} }
fn is_ident_begin(c: char) -> bool { fn is_ident_begin(c: char) -> bool {
c.is_alphabetic() c.is_xid_start()
} }
fn is_ident_middle(c: char) -> bool { fn is_ident_middle(c: char) -> bool {
c.is_alphanumeric() || c == '_' || c == '\'' c.is_xid_continue() || matches!(c, '\'' | '\u{2080}'..='\u{2089}')
} }
impl<'i> Lexer<'i> { impl<'i> Lexer<'i> {
@ -118,6 +131,7 @@ impl<'i> Lexer<'i> {
"sum" => Ok((i, Token::Sum, j)), "sum" => Ok((i, Token::Sum, j)),
"prod" => Ok((i, Token::Prod, j)), "prod" => Ok((i, Token::Prod, j)),
"iter" => Ok((i, Token::Iter, j)), "iter" => Ok((i, Token::Iter, j)),
"if" => Ok((i, Token::If, j)),
_ => Ok((i, Token::Name(s), j)), _ => Ok((i, Token::Name(s), j)),
} }
} }
@ -141,25 +155,48 @@ impl<'i> Lexer<'i> {
} }
self.next_token()? self.next_token()?
} }
(i, '\n') => Ok((i, Token::Newline, i + 1)),
(i, '(') => { self.bracket_depth += 1; Ok((i, Token::LParen, i + 1)) }, (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::RParen, i + 1)) },
(i, '{') => { self.bracket_depth += 1; Ok((i, Token::LBrace, 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, '}') => { self.bracket_depth -= 1; Ok((i, Token::RBrace, i + 1)) },
(i, '+') => Ok((i, Token::Plus, i + 1)), (i, '+') => Ok((i, Token::Plus, i + 1)),
(i, '-') => match self.chars.peek() { (i, '-') => match self.chars.next_if(|(_, c)| *c == '>') {
Some((_, '>')) => { Some(_) => Ok((i, Token::Arrow, i + 2)),
self.chars.next();
Ok((i, Token::Arrow, i + 2))
},
_ => Ok((i, Token::Minus, i + 1)), _ => Ok((i, Token::Minus, i + 1)),
} },
(i, '*') => Ok((i, Token::Star, i + 1)), (i, '*') => Ok((i, Token::Star, i + 1)),
(i, '\u{22C5}') => Ok((i, Token::Star, i + '\u{22C5}'.len_utf8())),
(i, '/') => Ok((i, Token::Slash, i + 1)), (i, '/') => Ok((i, Token::Slash, i + 1)),
(i, '^') => Ok((i, Token::Caret, i + 1)), (i, '^') => Ok((i, Token::Caret, i + 1)),
(i, '<') => match self.chars.next_if(|(_, c)| *c == '=') {
Some(_) => Ok((i, Token::LessEqual, i + 2)),
_ => Ok((i, Token::Less, i + 1)),
},
(i, '\u{2264}') => Ok((i, Token::LessEqual, i + '\u{2264}'.len_utf8())),
(i, '>') => match self.chars.next_if(|(_, c)| *c == '=') {
Some(_) => Ok((i, Token::GreaterEqual, i + 2)),
_ => Ok((i, Token::Greater, i + 1)),
},
(i, '\u{2265}') => Ok((i, Token::GreaterEqual, i + '\u{2265}'.len_utf8())),
(i, '=') => match self.chars.next_if(|(_, c)| *c == '=') {
Some(_) => Ok((i, Token::EqualEqual, i + 2)),
_ => Ok((i, Token::Equal, i + 1)),
}
(i, '!') => match self.chars.next() {
Some((_, '=')) => Ok((i, Token::BangEqual, i + 2)),
Some((_, c)) => Err(LexerError::Unexpected(i+1, c)),
None => Err(LexerError::UnexpectedEof),
}
(i, '\u{2260}') => Ok((i, Token::BangEqual, i + '\u{2260}'.len_utf8())),
(i, ',') => Ok((i, Token::Comma, i + 1)), (i, ',') => Ok((i, Token::Comma, i + 1)),
(i, '=') => Ok((i, Token::Equal, i + 1)),
(i, ':') => Ok((i, Token::Colon, i + 1)), (i, ':') => Ok((i, Token::Colon, i + 1)),
(i, '\n') => Ok((i, Token::Newline, i + 1)),
(i, '0'..='9') => self.next_number(i, false), (i, '0'..='9') => self.next_number(i, false),
(i, '.') => self.next_number(i, true), (i, '.') => self.next_number(i, true),
(i, c) if is_ident_begin(c) => self.next_word(i, i + c.len_utf8()), (i, c) if is_ident_begin(c) => self.next_word(i, i + c.len_utf8()),

View file

@ -15,6 +15,26 @@ struct Uniforms {
@group(0) @binding(1) var<uniform> uniforms: Uniforms; @group(0) @binding(1) var<uniform> uniforms: Uniforms;
/////////////////
// constants //
/////////////////
const TAU = 6.283185307179586;
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);
const C_I = vec2f(0.0, 1.0);
const C_EMGAMMA = vec2f(0.5772156649015329, 0.0);
const C_PHI = vec2f(1.618033988749895, 0.0);
const C_ZERO = vec2f(0.0, 0.0);
const C_ONE = vec2f(1.0, 0.0);
/////////////// ///////////////
// utility // // utility //
/////////////// ///////////////
@ -43,23 +63,6 @@ fn vlength(v: vec2f) -> f32 {
return max(max(a, b), max(c, d)); return max(max(a, b), max(c, d));
} }
/////////////////
// constants //
/////////////////
const TAU = 6.283185307179586;
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);
const C_I = vec2f(0.0, 1.0);
const C_EMGAMMA = vec2f(0.5772156649015329, 0.0);
const C_PHI = vec2f(1.618033988749895, 0.0);
///////////////////////// /////////////////////////
// complex functions // // complex functions //
///////////////////////// /////////////////////////
@ -80,32 +83,16 @@ fn c_signim(z: vec2f) -> vec2f {
return vec2(sign(z.y), 0.0); return vec2(sign(z.y), 0.0);
} }
fn c_ifgt(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f { fn c_absre(z: vec2f) -> vec2f {
return select(w, z, p.x > q.x); return vec2(abs(z.x), 0.0);
} }
fn c_iflt(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f { fn c_absim(z: vec2f) -> vec2f {
return select(w, z, p.x < q.x); return vec2(abs(z.y), 0.0);
} }
fn c_ifge(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f { fn c_isnan(z: vec2f) -> vec2f {
return select(w, z, p.x >= q.x); return select(C_ZERO, C_ONE, z.x != z.x || z.y != z.y);
}
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 { fn c_conj(z: vec2f) -> vec2f {
@ -128,9 +115,12 @@ fn c_arg(z: vec2f) -> vec2f {
} }
fn c_argbr(z: vec2f, br: vec2f) -> vec2f { fn c_argbr(z: vec2f, br: vec2f) -> vec2f {
if z.x < 0.0 && z.y == 0.0 {
return vec2(TAU/2.0 + floor(br.x/TAU) * TAU, 0.0);
}
let r = vec2(cos(-br.x), sin(-br.x)); let r = vec2(cos(-br.x), sin(-br.x));
let zr = c_mul(z, r); let zr = c_mul(z, r);
return c_arg(zr) + vec2(br.x, 0.0); return vec2(br.x + atan2(zr.y, zr.x), 0.0);
} }
fn c_add(u: vec2f, v: vec2f) -> vec2f { fn c_add(u: vec2f, v: vec2f) -> vec2f {
@ -222,15 +212,18 @@ fn c_tanh(z: vec2f) -> vec2f {
} }
fn c_asin(z: vec2f) -> vec2f { fn c_asin(z: vec2f) -> vec2f {
let m = select(-1.0, 1.0, z.y < 0.0 || (z.y == 0.0 && z.x > 0.0));
let u = c_sqrt(vec2(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)); let v = c_log(u + m*vec2(-z.y, z.x));
return vec2(v.y, -v.x); return m*vec2(v.y, -v.x);
} }
// TODO fix
fn c_acos(z: vec2f) -> vec2f { fn c_acos(z: vec2f) -> vec2f {
let m = select(-1.0, 1.0, z.y < 0.0 || (z.y == 0.0 && z.x > 0.0));
let u = c_sqrt(vec2(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)); let v = c_log(u + m*vec2(-z.y, z.x));
return vec2(TAU*0.25 - v.y, v.x); return C_TAU/4.0 + m*vec2(-v.y, v.x);
} }
fn c_atan(z: vec2f) -> vec2f { fn c_atan(z: vec2f) -> vec2f {
@ -241,19 +234,19 @@ fn c_atan(z: vec2f) -> vec2f {
} }
fn c_asinh(z: vec2f) -> vec2f { fn c_asinh(z: vec2f) -> vec2f {
let m = select(-1.0, 1.0, z.x > 0.0 || (z.x == 0.0 && z.y > 0.0));
let u = c_sqrt(vec2(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); return c_log(u + z*m) * m;
} }
fn c_acosh(z: vec2f) -> vec2f { fn c_acosh(z: vec2f) -> vec2f {
let u = c_sqrt(vec2(-1.0, 0.0) + c_mul(z, z)); let b = select(0.0, TAU, z.x < 0.0 || (z.x == 0.0 && z.y < 0.0));
let u = c_sqrtbr(vec2(-1.0, 0.0) + c_mul(z, z), vec2(b, 0.0));
return c_log(u + z); return c_log(u + z);
} }
fn c_atanh(z: vec2f) -> vec2f { fn c_atanh(z: vec2f) -> vec2f {
let u = vec2(1.0, 0.0) + z; return 0.5 * (c_log(C_ONE + z) - c_log(C_ONE - z));
let v = vec2(1.0, 0.0) - z;
return 0.5 * c_log(c_div(u, v));
} }
// log gamma // // log gamma //
@ -321,6 +314,56 @@ fn c_digamma_inner2(z: vec2f) -> vec2f {
return w - l; return w - l;
} }
// lambert w //
fn c_lambertw(z: vec2f) -> vec2f {
var w = c_lambertw_init(z, 0.0);
return c_lambertw_iter(z, w);
}
fn c_lambertwbr(z: vec2f, br: vec2f) -> vec2f {
// branch number
let br_n = br.x / TAU;
// if -TAU/2 < br < TAU/2 then use -1/e as the branch point,
// otherwise use 0
let branch_point = select(C_ZERO, vec2(-1.0/E, 0.0), abs(br.x) < TAU / 2.0);
let arg = c_arg(z - branch_point).x;
// if we're past the branch cut then take branch ceil(br_n),
// otherwise take branch floor(br_n)
let take_ceil = (br_n - floor(br_n) >= arg / TAU + 0.5);
var init_br = select(floor(br_n), ceil(br_n), take_ceil);
var w = c_lambertw_init(z, init_br);
// newton's method
return c_lambertw_iter(z, w);
}
fn c_lambertw_iter(z: vec2f, init: vec2f) -> vec2f {
var w = init;
for(var i = 0; i < 5; i++) {
w = c_div(c_mul(w, w) + c_mul(z, c_exp(-w)), w + C_ONE);
}
return w;
}
fn c_lambertw_init(z: vec2f, br: f32) -> vec2f {
let b = vec2(TAU * br, 0.0);
let oz = z + vec2(1.25, 0.0);
if br == 0.0 && dot(z, z) <= 50.0
|| br == 1.0 && z.y < 0.0 && dot(oz, oz) < 1.0
|| br == -1.0 && z.y > 0.0 && dot(oz, oz) < 1.0 {
// accurate near 0, near principle branch
let w = C_ONE + c_sqrtbr(C_ONE + E*z, b);
return c_div(c_mul(E*z, c_log(w)), w + E*z);
} else {
// accurate asymptotically
let logz = c_logbr(z, b);
return logz - c_log(logz);
}
}
///////////////// /////////////////
// rendering // // rendering //
///////////////// /////////////////