refactored
This commit is contained in:
parent
8702cddf44
commit
e254edabf7
20 changed files with 1158 additions and 772 deletions
1133
Cargo.lock
generated
1133
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -2,7 +2,6 @@
|
|||
|
||||
members = [
|
||||
"libcxgraph",
|
||||
"cxgraph-desktop",
|
||||
"cxgraph-web",
|
||||
]
|
||||
resolver = "2"
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
[package]
|
||||
name = "cxgraph-desktop"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
libcxgraph = { path = "../libcxgraph" }
|
||||
log = "0.4"
|
||||
env_logger = "0.10"
|
||||
winit = "0.28"
|
||||
pollster = "0.3"
|
|
@ -1,47 +0,0 @@
|
|||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use libcxgraph::{renderer::WgpuState, language::compile};
|
||||
use winit::{event_loop::EventLoop, window::Window, event::{Event, WindowEvent}};
|
||||
|
||||
fn main() {
|
||||
env_logger::builder()
|
||||
.filter_level(log::LevelFilter::Warn)
|
||||
.init();
|
||||
|
||||
let src = "plot(z) = 27^z - 9^z - 3^z";
|
||||
let wgsl = compile(src, &HashMap::new()).unwrap();
|
||||
println!("{wgsl}");
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
let window = Window::new(&event_loop).unwrap();
|
||||
window.set_title("window");
|
||||
|
||||
pollster::block_on(run(event_loop, window, &wgsl));
|
||||
}
|
||||
|
||||
async fn run(event_loop: EventLoop<()>, window: Window, code: &str) {
|
||||
let size = window.inner_size();
|
||||
let mut state = WgpuState::new(&window, size.into()).await;
|
||||
|
||||
state.load_shaders(code);
|
||||
|
||||
state.uniforms.bounds_min = (-5.0, -5.0).into();
|
||||
state.uniforms.bounds_max = ( 5.0, 5.0).into();
|
||||
state.uniforms.shading_intensity = 0.3;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
match event {
|
||||
Event::WindowEvent { event: WindowEvent::CloseRequested, .. }
|
||||
=> control_flow.set_exit(),
|
||||
Event::RedrawRequested(_)
|
||||
=> state.redraw(),
|
||||
Event::WindowEvent { event: WindowEvent::Resized(size), .. } => {
|
||||
state.resize(size.into());
|
||||
window.request_redraw();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
|
@ -9,7 +9,7 @@ crate-type = ["cdylib", "rlib"]
|
|||
[dependencies]
|
||||
libcxgraph = { path = "../libcxgraph", features = ["webgl"] }
|
||||
log = "0.4"
|
||||
winit = "0.28"
|
||||
winit = "0.29"
|
||||
console_error_panic_hook = "0.1"
|
||||
console_log = "1.0"
|
||||
wasm-bindgen = "0.2"
|
||||
|
|
161
cxgraph-web/editor.js
Normal file
161
cxgraph-web/editor.js
Normal file
|
@ -0,0 +1,161 @@
|
|||
//
|
||||
// Keyboard handling
|
||||
//
|
||||
|
||||
const TAB_WIDTH = 4;
|
||||
|
||||
let sourceFocused = false;
|
||||
|
||||
source_text.addEventListener("mousedown", () => { sourceFocused = true; });
|
||||
source_text.addEventListener("focusout", () => { sourceFocused = false; });
|
||||
|
||||
source_text.addEventListener("keydown", (event) => {
|
||||
console.log(event);
|
||||
if (event.key != "Tab") {
|
||||
sourceFocused = true;
|
||||
}
|
||||
|
||||
if (event.key == "Enter" && event.shiftKey) {
|
||||
event.preventDefault();
|
||||
button_graph.click();
|
||||
} else if (event.key == "Escape") {
|
||||
sourceFocused = false;
|
||||
} else if (event.key == "Backspace" && !event.ctrlKey) {
|
||||
let selStart = source_text.selectionStart;
|
||||
let selEnd = source_text.selectionEnd;
|
||||
|
||||
if (selStart == selEnd) {
|
||||
let pre = source_text.value.slice(0, selStart);
|
||||
let lineStart = pre.lastIndexOf("\n") + 1;
|
||||
let preLine = pre.slice(lineStart);
|
||||
|
||||
if (preLine.length > 2 && preLine.trim() == "") {
|
||||
let count = (selStart - lineStart - 1)%TAB_WIDTH + 1;
|
||||
for (let i = 0; i < count; i++) {
|
||||
if (pre[pre.length - i - 1] != " ") {
|
||||
count = i;
|
||||
}
|
||||
}
|
||||
|
||||
let post = source_text.value.slice(selStart);
|
||||
source_text.value = pre.slice(0, selStart - count) + post;
|
||||
source_text.selectionStart = selStart - count;
|
||||
source_text.selectionEnd = selEnd - count;
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
} else if (event.key == "Tab" && sourceFocused) {
|
||||
event.preventDefault();
|
||||
|
||||
let selStart = source_text.selectionStart;
|
||||
let selEnd = source_text.selectionEnd;
|
||||
let pre = source_text.value.slice(0, selStart);
|
||||
let post = source_text.value.slice(selStart);
|
||||
let lineStart = pre.lastIndexOf("\n") + 1;
|
||||
|
||||
if (event.shiftKey) {
|
||||
let count = (selStart - lineStart - 1)%TAB_WIDTH + 1;
|
||||
for (let i = 0; i < count; i++) {
|
||||
if (pre[pre.length - i - 1] != " ") {
|
||||
count = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
source_text.value = pre.slice(0, selStart - count) + post;
|
||||
source_text.selectionStart = selStart - count;
|
||||
source_text.selectionEnd = selEnd - count;
|
||||
}
|
||||
} else {
|
||||
let count = TAB_WIDTH - (selStart - lineStart)%TAB_WIDTH;
|
||||
source_text.value = pre + " ".repeat(count) + post;
|
||||
source_text.selectionStart = selStart + count;
|
||||
source_text.selectionEnd = selEnd + count;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// Special characters
|
||||
//
|
||||
|
||||
export let charMap = {
|
||||
"alpha": "\u03b1",
|
||||
"beta": "\u03b2",
|
||||
"gamma": "\u03b3",
|
||||
"delta": "\u03b4",
|
||||
"epsilon": "\u03b5",
|
||||
"zeta": "\u03b6",
|
||||
"eta": "\u03b7",
|
||||
"theta": "\u03b8",
|
||||
"iota": "\u03b9",
|
||||
"kappa": "\u03ba",
|
||||
"lambda": "\u03bb",
|
||||
"mu": "\u03bc",
|
||||
"nu": "\u03bd",
|
||||
"xi": "\u03be",
|
||||
"omicron": "\u03bf",
|
||||
"pi": "\u03c0",
|
||||
"rho": "\u03c1",
|
||||
"fsigma": "\u03c2",
|
||||
"sigma": "\u03c3",
|
||||
"tau": "\u03c4",
|
||||
"upsilon": "\u03c5",
|
||||
"phi": "\u03c6",
|
||||
"chi": "\u03c7",
|
||||
"psi": "\u03c8",
|
||||
"omega": "\u03c9",
|
||||
"Alpha": "\u0391",
|
||||
"Beta": "\u0392",
|
||||
"Gamma": "\u0393",
|
||||
"Delta": "\u0394",
|
||||
"Epsilon": "\u0395",
|
||||
"Zeta": "\u0396",
|
||||
"Eta": "\u0397",
|
||||
"Theta": "\u0398",
|
||||
"Iota": "\u0399",
|
||||
"Kappa": "\u039a",
|
||||
"Lambda": "\u039b",
|
||||
"Mu": "\u039c",
|
||||
"Nu": "\u039d",
|
||||
"Xi": "\u039e",
|
||||
"Omicron": "\u039f",
|
||||
"Pi": "\u03a0",
|
||||
"Rho": "\u03a1",
|
||||
"Sigma": "\u03a3",
|
||||
"Tau": "\u03a4",
|
||||
"Upsilon": "\u03a5",
|
||||
"Phi": "\u03a6",
|
||||
"Chi": "\u03a7",
|
||||
"Psi": "\u03a8",
|
||||
"Omega": "\u03a9",
|
||||
"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(
|
||||
`\\\\(${Object.keys(charMap).join("|")})`
|
||||
);
|
||||
|
||||
source_text.addEventListener("input", (event) => {
|
||||
if(event.isComposing) return;
|
||||
let e = source_text.selectionEnd;
|
||||
let amnt = 0;
|
||||
source_text.value = source_text.value.replace(
|
||||
specialChars,
|
||||
(m, p) => {
|
||||
amnt += m.length - charMap[p].length;
|
||||
return charMap[p];
|
||||
}
|
||||
);
|
||||
source_text.selectionEnd = e - amnt;
|
||||
});
|
||||
|
|
@ -1,7 +1,16 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>cxgraph</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<meta name="author" content="trimill">
|
||||
<meta name="description" content="Plot complex functions">
|
||||
<meta property="og:description" content="Plot complex functions">
|
||||
<meta property="og:title" content="CXGraph">
|
||||
<meta property="og:site_name" content="cx.trimill.xyz">
|
||||
|
||||
<title>CXGraph</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
@ -12,8 +21,8 @@
|
|||
<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" />
|
||||
<circle cx="0" cy="0" r="15" stroke="none" fill="#3337" />
|
||||
<circle cx="0" cy="0" r="5" stroke="none" fill="#dddc" />
|
||||
</g>
|
||||
<g id="overlay_points">
|
||||
</g>
|
||||
|
@ -32,7 +41,7 @@
|
|||
</div>
|
||||
|
||||
<div id="div_error_msg" hidden></div>
|
||||
<div><textarea id="source_text">f(z) = z^2 + 3i plot(z) = 5z^2 + f(1/z) - 1</textarea></div>
|
||||
<div><textarea id="source_text">f(z) = 6z^2 - 2i - 1 plot(z) = f(1 + sin(z)) / 8</textarea></div>
|
||||
</details>
|
||||
|
||||
<div>
|
||||
|
@ -52,11 +61,6 @@
|
|||
<input type="range" id="range_shading" min="0" max="1" step="0.01" value="0.3">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div><label for="range_contour">Contour intensity</label></div>
|
||||
<input type="range" id="range_contour" min="0" max="1" step="0.01" value="0.0">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<input type="checkbox" class="decor" id="checkbox_decor_1" data-value="1">
|
||||
|
@ -77,11 +81,16 @@
|
|||
<input type="checkbox" class="decor" id="checkbox_decor_8" data-value="8">
|
||||
<label for="checkbox_decor_8">Magnitude contours</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div><label for="range_contour">Contour intensity</label></div>
|
||||
<input type="range" id="range_contour" min="0" max="1" step="0.01" value="0.0">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>Coloring</div>
|
||||
<input type="radio" name="color_mode" id="radio_color_0" data-value="0" checked>
|
||||
<input type="radio" name="color_mode" id="radio_color_0" data-value="0">
|
||||
<label for="radio_color_0">Standard</label><br>
|
||||
<input type="radio" name="color_mode" id="radio_color_1" data-value="1">
|
||||
<label for="radio_color_1">Uniform</label><br>
|
||||
|
@ -106,7 +115,7 @@
|
|||
<summary>Variables</summary>
|
||||
<div id="slider_template" hidden>
|
||||
<div>
|
||||
<input type="text" class="var-name" style="width: 5ch;"">
|
||||
<input type="text" class="var-name" style="width: 5ch;" placeholder="a">
|
||||
=
|
||||
<input type="number" class="var-value" style="width: 10ch;" value="0" required>
|
||||
<input type="button" class="var-delete" value="X">
|
||||
|
@ -122,7 +131,7 @@
|
|||
<hr>
|
||||
</div>
|
||||
<div id="point_template" hidden>
|
||||
<input type="text" class="var-name" style="width: 5ch;">
|
||||
<input type="text" class="var-name" style="width: 5ch;" placeholder="b">
|
||||
=
|
||||
<input type="number" class="var-value-re" style="width: 8ch;" required value="0">
|
||||
+
|
||||
|
@ -140,7 +149,9 @@
|
|||
</div>
|
||||
|
||||
<script>
|
||||
import('./index.js').then(m => window.module = m)
|
||||
window.modules = {};
|
||||
import('./index.js').then(m => window.modules.index = m);
|
||||
import('./editor.js').then(m => window.modules.editor = m);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -12,7 +12,6 @@ let graphView = {
|
|||
};
|
||||
|
||||
let graphPoints = [];
|
||||
let graphSliders = [];
|
||||
|
||||
let mouseX = 0.0;
|
||||
let mouseY = 0.0;
|
||||
|
@ -111,20 +110,20 @@ function onWheel(e) {
|
|||
onViewChange();
|
||||
}
|
||||
|
||||
function onMouseDown(e) {
|
||||
function onPointerDown(e) {
|
||||
mousePressed = true;
|
||||
mouseX = e.offsetX;
|
||||
mouseY = e.offsetY;
|
||||
}
|
||||
|
||||
function onMouseUp(e) {
|
||||
function onPointerUp() {
|
||||
mousePressed = false;
|
||||
for(let point of graphPoints) {
|
||||
point.mousePressed = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseMove(e) {
|
||||
function onPointerMove(e) {
|
||||
if(mousePressed) {
|
||||
let dX = e.offsetX - mouseX;
|
||||
let dY = e.offsetY - mouseY;
|
||||
|
@ -135,16 +134,16 @@ function onMouseMove(e) {
|
|||
onViewChange();
|
||||
} else {
|
||||
for(let point of graphPoints) {
|
||||
point.onMouseMove(e);
|
||||
point.onPointerMove(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("resize", onResize);
|
||||
canvas.addEventListener("wheel", onWheel);
|
||||
canvas.addEventListener("mousedown", onMouseDown);
|
||||
canvas.addEventListener("mouseup", onMouseUp);
|
||||
canvas.addEventListener("mousemove", onMouseMove);
|
||||
canvas.addEventListener("pointerdown", onPointerDown);
|
||||
canvas.addEventListener("pointerup", onPointerUp);
|
||||
canvas.addEventListener("pointermove", onPointerMove);
|
||||
|
||||
//
|
||||
// Graph/redraw
|
||||
|
@ -166,86 +165,6 @@ function onGraph() {
|
|||
button_graph.addEventListener("click", onGraph);
|
||||
button_redraw.addEventListener("click", redraw);
|
||||
|
||||
let charMap = {
|
||||
"alpha": "\u03b1",
|
||||
"beta": "\u03b2",
|
||||
"gamma": "\u03b3",
|
||||
"delta": "\u03b4",
|
||||
"epsilon": "\u03b5",
|
||||
"zeta": "\u03b6",
|
||||
"eta": "\u03b7",
|
||||
"theta": "\u03b8",
|
||||
"iota": "\u03b9",
|
||||
"kappa": "\u03ba",
|
||||
"lambda": "\u03bb",
|
||||
"mu": "\u03bc",
|
||||
"nu": "\u03bd",
|
||||
"xi": "\u03be",
|
||||
"omicron": "\u03bf",
|
||||
"pi": "\u03c0",
|
||||
"rho": "\u03c1",
|
||||
"fsigma": "\u03c2",
|
||||
"sigma": "\u03c3",
|
||||
"tau": "\u03c4",
|
||||
"upsilon": "\u03c5",
|
||||
"phi": "\u03c6",
|
||||
"chi": "\u03c7",
|
||||
"psi": "\u03c8",
|
||||
"omega": "\u03c9",
|
||||
"Alpha": "\u0391",
|
||||
"Beta": "\u0392",
|
||||
"Gamma": "\u0393",
|
||||
"Delta": "\u0394",
|
||||
"Epsilon": "\u0395",
|
||||
"Zeta": "\u0396",
|
||||
"Eta": "\u0397",
|
||||
"Theta": "\u0398",
|
||||
"Iota": "\u0399",
|
||||
"Kappa": "\u039a",
|
||||
"Lambda": "\u039b",
|
||||
"Mu": "\u039c",
|
||||
"Nu": "\u039d",
|
||||
"Xi": "\u039e",
|
||||
"Omicron": "\u039f",
|
||||
"Pi": "\u03a0",
|
||||
"Rho": "\u03a1",
|
||||
"Sigma": "\u03a3",
|
||||
"Tau": "\u03a4",
|
||||
"Upsilon": "\u03a5",
|
||||
"Phi": "\u03a6",
|
||||
"Chi": "\u03a7",
|
||||
"Psi": "\u03a8",
|
||||
"Omega": "\u03a9",
|
||||
"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(
|
||||
`\\\\(${Object.keys(charMap).join("|")})`
|
||||
);
|
||||
|
||||
source_text.addEventListener("input", (event) => {
|
||||
if(event.isComposing) return;
|
||||
let e = source_text.selectionEnd;
|
||||
let amnt = 0;
|
||||
source_text.value = source_text.value.replace(
|
||||
specialChars,
|
||||
(m, p) => {
|
||||
amnt += m.length - charMap[p].length;
|
||||
return charMap[p];
|
||||
}
|
||||
);
|
||||
source_text.selectionEnd = e - amnt;
|
||||
});
|
||||
|
||||
//
|
||||
// Options
|
||||
//
|
||||
|
@ -298,7 +217,8 @@ for(let e of nameColorMode) {
|
|||
});
|
||||
e.checked = false;
|
||||
}
|
||||
nameColorMode[0].checked = true;
|
||||
nameColorMode[1].checked = true;
|
||||
cxgraph.set_coloring(1);
|
||||
|
||||
overlay_axes.addEventListener("change", () => {
|
||||
let vis = overlay_axes.checked ? "visible" : "hidden";
|
||||
|
@ -429,24 +349,24 @@ class Point {
|
|||
tryRedraw();
|
||||
});
|
||||
|
||||
svgPoint.addEventListener("mousedown", (e) => {
|
||||
svgPoint.addEventListener("pointerdown", (e) => {
|
||||
this.mousePressed = true;
|
||||
mouseX = e.offsetX;
|
||||
mouseY = e.offsetY;
|
||||
});
|
||||
|
||||
svgPoint.addEventListener("mouseup", () => {
|
||||
svgPoint.addEventListener("pointerup", () => {
|
||||
this.mousePressed = false;
|
||||
mousePressed = false;
|
||||
});
|
||||
|
||||
svgPoint.addEventListener("mousemove", (e) => this.onMouseMove(e));
|
||||
svgPoint.addEventListener("pointermove", (e) => this.onPointerMove(e));
|
||||
|
||||
this.onViewChange();
|
||||
genVarNames();
|
||||
}
|
||||
|
||||
onMouseMove(e) {
|
||||
onPointerMove(e) {
|
||||
if(this.mousePressed) {
|
||||
mouseX = e.offsetX;
|
||||
mouseY = e.offsetY;
|
||||
|
@ -496,3 +416,5 @@ onGraph();
|
|||
export function show_ast() {
|
||||
console.info(cxgraph.show_shader_ast(source_text.value));
|
||||
}
|
||||
|
||||
export function get_cxgraph() { return cxgraph; }
|
||||
|
|
|
@ -20,7 +20,6 @@ where F: Fn(&mut WgpuState) {
|
|||
#[wasm_bindgen(start)]
|
||||
pub async fn start() {
|
||||
use winit::dpi::PhysicalSize;
|
||||
use winit::platform::web::WindowExtWebSys;
|
||||
|
||||
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||
console_log::init_with_level(log::Level::Info).expect("Couldn't initialize logger");
|
||||
|
@ -34,16 +33,18 @@ pub async fn start() {
|
|||
.dyn_into()
|
||||
.expect("Canvas was not a canvas");
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
let window = WindowBuilder::new()
|
||||
.with_canvas(Some(canvas))
|
||||
.with_prevent_default(false)
|
||||
.with_inner_size(PhysicalSize::new(100, 100))
|
||||
.with_title("window")
|
||||
.build(&event_loop)
|
||||
.expect("Failed to build window");
|
||||
|
||||
let size = window.inner_size();
|
||||
let mut state = WgpuState::new(&window, size.into()).await;
|
||||
let window_ref = Box::leak(Box::new(window));
|
||||
|
||||
let mut state = WgpuState::new(window_ref, (100, 100)).await;
|
||||
state.uniforms.bounds_min = (-5.0, -5.0).into();
|
||||
state.uniforms.bounds_max = ( 5.0, 5.0).into();
|
||||
state.uniforms.shading_intensity = 0.3;
|
||||
|
|
|
@ -26,6 +26,7 @@ canvas, #overlay {
|
|||
|
||||
#canvas {
|
||||
z-index: 0;
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
#overlay {
|
||||
|
|
230
docs/language.md
230
docs/language.md
|
@ -1,47 +1,123 @@
|
|||
# cxgraph language
|
||||
# CXGraph language
|
||||
|
||||
cxgraph uses a custom expression language that is compiled to WGSL.
|
||||
CXGraph uses a custom expression language that is compiled to WGSL.
|
||||
|
||||
## names
|
||||
## 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.
|
||||
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`, `iter`,
|
||||
and `if` may not be used for names. Names may refer to either functions
|
||||
or variables.
|
||||
|
||||
examples of names include:
|
||||
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.
|
||||
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
|
||||
## Declarations
|
||||
|
||||
a **function declaration** declares a new function. functions may have zero or more arguments.
|
||||
A **function declaration** declares a new function. Functions may have zero
|
||||
or more arguments.
|
||||
|
||||
```
|
||||
f(x) = 3
|
||||
```
|
||||
|
||||
a **constant declaration** declares a new constant.
|
||||
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
|
||||
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
|
||||
The `plot` function is special and serves as the entry point. It must exist and have exactly one argument.
|
||||
|
||||
## Operators
|
||||
|
||||
Below is a reference to all operators in the CXGraph language.
|
||||
|
||||
| operator | description | precedence |
|
||||
|----------|----------------------------|------------|
|
||||
| `,` | separate expressions | 0 |
|
||||
| `->` | assign to | 1 |
|
||||
| `==` | equal | 2 |
|
||||
| `!=` | not equal | 2 |
|
||||
| `>` | real part greater | 3 |
|
||||
| `<` | real part less | 3 |
|
||||
| `>=` | real part greater or equal | 3 |
|
||||
| `<=` | real part less or equal | 3 |
|
||||
| `+` | addition | 4 |
|
||||
| `-` | subtraction | 4 |
|
||||
| `*` | multiplication | 5 |
|
||||
| `/` | division | 5 |
|
||||
| `^` | power | 6 |
|
||||
|
||||
The comma `,` separates expressions in locations where multiple are allowed (ie.
|
||||
in a definition or block).
|
||||
|
||||
The arrow `->` stores the value of the expression to its left to the local
|
||||
variable on the right.
|
||||
|
||||
The equality operators `==` and `!=` compare two values, considering both their
|
||||
real and imaginary components. The comparison operators `>`, `<`, `>=`, and `<=`
|
||||
only consider the real component.
|
||||
|
||||
`+`, `-`, and `*` also function as the unary plus, minus, and conugation operators.
|
||||
|
||||
Multiplication can also be done via juxtaposition - `2x(x+1)` is equivalent to `2*x*(x+1)`.
|
||||
Juxtaposition takes precedence over other operations, so `1/2x` is `1/(2*x)`, not `1/2*x`,
|
||||
and `z^5(x+1)` is `z^(5*(x+1))`, not `z^5*(x+1)`.
|
||||
|
||||
## Grouping
|
||||
|
||||
Parentheses `( )` can be used to group within an expression. Braces `{ }` can be used to group multiple expressions, separated by commas. Newlines, which ordinarily separate declarations, are treated as whitespace between grouping symbols, allowing for multiline definitions.
|
||||
|
||||
## Repetition and conditionals
|
||||
|
||||
To allow for finite sums and products, the `sum` and `prod` expressions can be used.
|
||||
|
||||
```
|
||||
plot(z) = sum(n: 0, 20) { z^n / n }
|
||||
```
|
||||
|
||||
The first parameter to `sum` or `prod` is the name of the summation index, the next two
|
||||
values are the lower and upper bounds (inclusive). These may be arbitrary expressions,
|
||||
they are converted to integers by rounding down the real component. The value of the body
|
||||
for each index value is summed or multiplied and the result is returned.
|
||||
|
||||
`iter` works similarly:
|
||||
|
||||
```
|
||||
plot(c) = iter(20, 0 -> z) { z^2 + c }
|
||||
```
|
||||
|
||||
The first parameter is the number of iterations, the second is an assignment to initialize
|
||||
the iteration variable. For each iteration, the iteration variable will be updated to the
|
||||
new value of the body.
|
||||
|
||||
`if` can be used to choose between two expressions based on a condition:
|
||||
|
||||
```
|
||||
if(z > 0) { z^2 } { 2z }
|
||||
```
|
||||
|
||||
If the argument's real part is positive, the first body will be evaluated, and otherwise the
|
||||
second will be.
|
||||
|
||||
|
||||
## Built-in functions and constants
|
||||
|
||||
arithmetic functions:
|
||||
| name | description |
|
||||
|---------------|-------------------------------------------------------|
|
||||
| `pos(z)` | equivalent to unary `+` |
|
||||
| `pos(z)` | identity, equivalent to unary `+` |
|
||||
| `neg(z)` | equivalent to unary `-` |
|
||||
| `conj(z)` | complex conjugate, equivalent to unary `*` |
|
||||
| `re(z)` | real part |
|
||||
|
@ -57,63 +133,62 @@ arithmetic functions:
|
|||
| `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 |
|
||||
| name | description |
|
||||
|----------------|-------------------------------------------|
|
||||
| `exp(z)` | Exponential function, equivalent to `e^z` |
|
||||
| `log(z)` | Natural logarithm |
|
||||
| `logbr(z,br)` | Natural logarithm 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 |
|
||||
| `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) |
|
||||
| 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) |
|
||||
| `lambertw(z)` | [Lambert W function](https://en.wikipedia.org/wiki/Lambert_W_function) |
|
||||
| `lambertwbr(z,br)` | Lambert W function on specfied branch |
|
||||
| `erf(z)` | The [Error function](https://en.wikipedia.org/wiki/Error_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` |
|
||||
| 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 |
|
||||
| `absre(z)` | Absolute value of real part |
|
||||
| `absim(z)` | Absolute value of imaginary part |
|
||||
| `isnan(z)` | 1 if `z` is NaN, 0 otherwise |
|
||||
|
||||
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` |
|
||||
| `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
|
||||
|
||||
|
@ -129,7 +204,17 @@ Exprs := (Expr ",")* Expr ","?
|
|||
|
||||
Expr := Store
|
||||
|
||||
Store := Store "->" NAME | Sum
|
||||
Store := Equality "->" NAME | Equality
|
||||
|
||||
Equality := Compare "==" Compare
|
||||
| Compare "!=" Compare
|
||||
| Compare
|
||||
|
||||
Compare := Sum ">" Sum
|
||||
| Sum "<" Sum
|
||||
| Sum ">=" Sum
|
||||
| Sum "<=" Sum
|
||||
| Sum
|
||||
|
||||
Sum := Sum "+" Product
|
||||
| Sum "-" Product
|
||||
|
@ -151,15 +236,16 @@ Power := FnCall "^" Unary | FnCall
|
|||
|
||||
FnCall := NAME "(" Exprs ")" | Item
|
||||
|
||||
PreJuxtapose := Number | "(" <Expr> ")"
|
||||
PreJuxtapose := NUMBER | "(" <Expr> ")"
|
||||
|
||||
Item := Number
|
||||
Block := "{" Exprs "}"
|
||||
|
||||
Item := NUMBER
|
||||
| NAME
|
||||
| "(" Expr ")"
|
||||
| "{" Exprs "}"
|
||||
| "sum" "(" NAME ":" INT "," INT ")" "{" Exprs "}"
|
||||
| "prod" "(" NAME ":" INT "," INT ")" "{" Exprs "}"
|
||||
| "iter" "(" INT "," NAME ":" Expr ")" "{" Exprs "}"
|
||||
|
||||
Number = FLOAT | INT
|
||||
| Block
|
||||
| "sum" "(" NAME ":" Expr "," Expr ")" Block
|
||||
| "prod" "(" NAME ":" Expr "," Expr ")" block
|
||||
| "iter" "(" Expr "," Expr "->" NAME ")" Block
|
||||
| "if" "(" Expr ")" Block Block
|
||||
```
|
||||
|
|
33
docs/web.md
33
docs/web.md
|
@ -0,0 +1,33 @@
|
|||
# CXGraph Web UI
|
||||
|
||||
## Source
|
||||
|
||||
Enter the program to plot into the text area. For more information, see [the language docs](language.md).
|
||||
|
||||
The Graph button compiles the program and redraws the screen. This must be pressed after changes are made to the program to see them. This can also be accomplished by pressing `Shift`+`Enter` in the text area.
|
||||
|
||||
The Redraw button redraws the screen. If Auto Redraw is enabled, the screen will be redrawn automatically after every change (eg. dragging, zooming, changing options or variables).
|
||||
|
||||
## The plot
|
||||
|
||||
Most of the screen is occupied by the plot. Click and drag with the mouse to move around, and use the scroll wheel to zoom in and out.
|
||||
|
||||
## Options
|
||||
|
||||
Reset View resets the plot's position and scale. Help opens the documentation.
|
||||
|
||||
The Resolution slider controls the canvas's resolution scale on a range from x0.25 to x4. This is set to x1 by default. Higher values provide better visuals at the expense of performance.
|
||||
|
||||
Shading Intensity controls how intense the black and white shading near zero/infinity is. Setting this to zero disables shading.
|
||||
|
||||
Contours can be toggled with the contour checkboxes. Real and imaginary contours show the integer grid, argument contours show angles around the origin divided into 16 segments, and magnitude contours show magnitudes delineated by powers of two.
|
||||
|
||||
Standard coloring directly maps argument to hue in HSV while keeping saturation and value constant. Uniform coloring uses a modified mapping that tries to avoid variation in perceptual brightness. None disables coloring entirely.
|
||||
|
||||
Draw Axes and Unit Circle enable or disable the axes and circle overlays.
|
||||
|
||||
## Variables
|
||||
|
||||
Sliders can be added with the "+slider" button. Once the slider has been named, the variable name can be used in the program and the plot will redraw automatically when the slider's value is changed. The slider's start, step, and end default to 1, 0.01, and -1, respectively. The slider's value can also be edited directly.
|
||||
|
||||
Points can be added with the "+point" button. They behave similarly to sliders, but add a draggable point to the plot. The position of the point can also be edited directly.
|
|
@ -9,11 +9,11 @@ webgl = ["wgpu/webgl"]
|
|||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
lalrpop-util = { version = "0.20.0", features = ["lexer", "unicode"] }
|
||||
lalrpop-util = { version = "0.21.0", features = ["lexer", "unicode"] }
|
||||
num-complex = "0.4"
|
||||
wgpu = "0.16"
|
||||
raw-window-handle = "0.5"
|
||||
wgpu = "22.1"
|
||||
raw-window-handle = "0.6"
|
||||
unicode-xid = "0.2"
|
||||
|
||||
[build-dependencies]
|
||||
lalrpop = "0.20.0"
|
||||
lalrpop = "0.21.0"
|
||||
|
|
|
@ -23,9 +23,9 @@ pub enum ExpressionType<'a> {
|
|||
FnCall(&'a str),
|
||||
Store(&'a str),
|
||||
If,
|
||||
Sum { countvar: &'a str, min: i32, max: i32 },
|
||||
Prod { countvar: &'a str, min: i32, max: i32 },
|
||||
Iter { itervar: &'a str, count: i32 },
|
||||
Sum { countvar: &'a str },
|
||||
Prod { countvar: &'a str },
|
||||
Iter { itervar: &'a str },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -70,24 +70,24 @@ impl<'a> Expression<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn new_sum(countvar: &'a str, min: i32, max: i32, body: Self) -> Self {
|
||||
pub fn new_sum(countvar: &'a str, min: Self, max: Self, body: Self) -> Self {
|
||||
Self {
|
||||
ty: ExpressionType::Sum { countvar, min, max },
|
||||
children: vec![body],
|
||||
ty: ExpressionType::Sum { countvar },
|
||||
children: vec![min, max, body],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_prod(accvar: &'a str, min: i32, max: i32, body: Self) -> Self {
|
||||
pub fn new_prod(countvar: &'a str, min: Self, max: Self, body: Self) -> Self {
|
||||
Self {
|
||||
ty: ExpressionType::Prod { countvar: accvar, min, max },
|
||||
children: vec![body],
|
||||
ty: ExpressionType::Prod { countvar },
|
||||
children: vec![min, max, body],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_iter(itervar: &'a str, count: i32, init: Self, body: Self) -> Self {
|
||||
pub fn new_iter(itervar: &'a str, count: Self, init: Self, body: Self) -> Self {
|
||||
Self {
|
||||
ty: ExpressionType::Iter { itervar, count },
|
||||
children: vec![init, body],
|
||||
ty: ExpressionType::Iter { itervar },
|
||||
children: vec![count, init, body],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -108,9 +108,9 @@ fn display_expr(w: &mut impl fmt::Write, expr: &Expression, depth: usize) -> fmt
|
|||
ExpressionType::FnCall(f) => write!(w, "{:indent$}CALL {f}", "", 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::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::Sum { countvar } => write!(w, "{:indent$}SUM {countvar}", "", indent=indent)?,
|
||||
ExpressionType::Prod { countvar } => write!(w, "{:indent$}PROD {countvar}", "", indent=indent)?,
|
||||
ExpressionType::Iter { itervar } => write!(w, "{:indent$}ITER {itervar}", "", indent=indent)?,
|
||||
}
|
||||
writeln!(w)?;
|
||||
for child in &expr.children {
|
||||
|
|
|
@ -60,6 +60,7 @@ thread_local! {
|
|||
m.insert("\u{03C8}", ("c_digamma", 1));
|
||||
m.insert("lambertw", ("c_lambertw", 1));
|
||||
m.insert("lambertwbr", ("c_lambertwbr", 2));
|
||||
m.insert("erf", ("c_erf", 1));
|
||||
|
||||
m
|
||||
};
|
||||
|
|
|
@ -250,8 +250,15 @@ impl<'w, 'i, W: fmt::Write> Compiler<'w, 'i, W> {
|
|||
writeln!(self.buf, "}}")?;
|
||||
Ok(result)
|
||||
},
|
||||
ExpressionType::Sum { countvar, min, max }
|
||||
| ExpressionType::Prod { countvar, min, max } => {
|
||||
ExpressionType::Sum { countvar }
|
||||
| ExpressionType::Prod { countvar } => {
|
||||
let min = local.next_tmp();
|
||||
let max = local.next_tmp();
|
||||
let v = self.compile_expr(local, &expr.children[0])?;
|
||||
writeln!(self.buf, "var {min} = i32(floor({v}.x));")?;
|
||||
let v = self.compile_expr(local, &expr.children[1])?;
|
||||
writeln!(self.buf, "var {max} = i32(floor({v}.x));")?;
|
||||
|
||||
let acc = local.next_tmp();
|
||||
let ivar = local.next_tmp();
|
||||
if matches!(expr.ty, ExpressionType::Sum { .. }) {
|
||||
|
@ -259,11 +266,11 @@ impl<'w, 'i, W: fmt::Write> Compiler<'w, 'i, W> {
|
|||
} else {
|
||||
writeln!(self.buf, "var {acc} = vec2f(1.0, 0.0);")?;
|
||||
}
|
||||
writeln!(self.buf, "for(var {ivar}: i32 = {min}; {ivar} <= {max}; {ivar}++) {{")?;
|
||||
writeln!(self.buf, "for(var {ivar} = {min}; {ivar} <= {max}; {ivar}++) {{")?;
|
||||
writeln!(self.buf, "var {} = vec2f(f32({ivar}), 0.0);", format_local(countvar))?;
|
||||
let mut loop_local = local.clone();
|
||||
loop_local.local_vars.insert(countvar);
|
||||
let body = self.compile_expr(&mut loop_local, &expr.children[0])?;
|
||||
let body = self.compile_expr(&mut loop_local, &expr.children[2])?;
|
||||
if matches!(expr.ty, ExpressionType::Sum { .. }) {
|
||||
writeln!(self.buf, "{acc} = {acc} + {body};")?;
|
||||
} else {
|
||||
|
@ -272,16 +279,21 @@ impl<'w, 'i, W: fmt::Write> Compiler<'w, 'i, W> {
|
|||
writeln!(self.buf, "}}")?;
|
||||
Ok(acc)
|
||||
},
|
||||
ExpressionType::Iter { itervar, count } => {
|
||||
let init = &expr.children[0];
|
||||
ExpressionType::Iter { itervar } => {
|
||||
let countvar = local.next_tmp();
|
||||
let v = self.compile_expr(local, &expr.children[0])?;
|
||||
writeln!(self.buf, "var {countvar} = i32(floor({v}.x));")?;
|
||||
|
||||
let init = &expr.children[1];
|
||||
let itervar_fmt = format_local(itervar);
|
||||
let v = self.compile_expr(local, init)?;
|
||||
writeln!(self.buf, "var {itervar_fmt} = {v};")?;
|
||||
|
||||
let ivar = local.next_tmp();
|
||||
writeln!(self.buf, "for(var {ivar}: i32 = 0; {ivar} < {count}; {ivar}++) {{")?;
|
||||
writeln!(self.buf, "for(var {ivar}: i32 = 0; {ivar} < {countvar}; {ivar}++) {{")?;
|
||||
let mut loop_local = local.clone();
|
||||
loop_local.local_vars.insert(itervar);
|
||||
let body = self.compile_expr(&mut loop_local, &expr.children[1])?;
|
||||
let body = self.compile_expr(&mut loop_local, &expr.children[2])?;
|
||||
writeln!(self.buf, "{itervar_fmt} = {body};")?;
|
||||
writeln!(self.buf, "}}")?;
|
||||
Ok(itervar_fmt)
|
||||
|
|
|
@ -32,8 +32,7 @@ extern {
|
|||
"prod" => Token::Prod,
|
||||
"iter" => Token::Iter,
|
||||
"if" => Token::If,
|
||||
Float => Token::Float(<f64>),
|
||||
Int => Token::Int(<i32>),
|
||||
Number => Token::Number(<f64>),
|
||||
Name => Token::Name(<&'input str>),
|
||||
}
|
||||
}
|
||||
|
@ -122,7 +121,7 @@ FnCall: Expression<'input> = {
|
|||
}
|
||||
|
||||
PreJuxtapose: Expression<'input> = {
|
||||
Number,
|
||||
<n:Number> => Expression::new_number(n),
|
||||
"(" <Expr> ")",
|
||||
}
|
||||
|
||||
|
@ -131,21 +130,16 @@ Block: Expression<'input> = {
|
|||
}
|
||||
|
||||
Item: Expression<'input> = {
|
||||
Number,
|
||||
<n:Number> => Expression::new_number(n),
|
||||
<n:Name> => Expression::new_name(n),
|
||||
"(" <Expr> ")",
|
||||
Block,
|
||||
"sum" "(" <name:Name> ":" <min:Int> "," <max:Int> ")" <body:Block>
|
||||
"sum" "(" <name:Name> ":" <min:Expr> "," <max:Expr> ")" <body:Block>
|
||||
=> Expression::new_sum(name, min, max, body),
|
||||
"prod" "(" <name:Name> ":" <min:Int> "," <max:Int> ")" <body:Block>
|
||||
"prod" "(" <name:Name> ":" <min:Expr> "," <max:Expr> ")" <body:Block>
|
||||
=> Expression::new_prod(name, min, max, body),
|
||||
"iter" "(" <count:Int> "," <name:Name> ":" <init:Expr> ")" <body:Block>
|
||||
"iter" "(" <count:Expr> "," <init:Equality> "->" <name:Name> ")" <body:Block>
|
||||
=> Expression::new_iter(name, count, init, body),
|
||||
"if" "(" <cond:Expr> ")" <t:Block> <f:Block>
|
||||
=> Expression::new_if(cond, t, f),
|
||||
}
|
||||
|
||||
Number: Expression<'input> = {
|
||||
<n:Float> => Expression::new_number(n),
|
||||
<n:Int> => Expression::new_number(n as f64),
|
||||
}
|
||||
|
|
|
@ -5,8 +5,7 @@ use unicode_xid::UnicodeXID;
|
|||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Token<'i> {
|
||||
Float(f64),
|
||||
Int(i32),
|
||||
Number(f64),
|
||||
Name(&'i str),
|
||||
Sum, Prod, Iter, If,
|
||||
LParen, RParen,
|
||||
|
@ -21,8 +20,7 @@ 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::Number(n) => write!(f, "{n}"),
|
||||
Token::Name(n) => write!(f, "{n}"),
|
||||
Token::Sum => f.write_str("sum"),
|
||||
Token::Prod => f.write_str("prod"),
|
||||
|
@ -94,7 +92,7 @@ impl<'i> Lexer<'i> {
|
|||
}
|
||||
}
|
||||
|
||||
fn next_number(&mut self, i: usize, mut has_dot: bool) -> Spanned<Token<'i>, usize, LexerError> {
|
||||
fn next_number(&mut self, i: usize, has_dot: bool) -> Spanned<Token<'i>, usize, LexerError> {
|
||||
let mut j = i;
|
||||
|
||||
while self.chars.peek().is_some_and(|(_, c)| c.is_ascii_digit()) {
|
||||
|
@ -103,20 +101,14 @@ impl<'i> Lexer<'i> {
|
|||
|
||||
if !has_dot && matches!(self.chars.peek(), Some((_, '.'))) {
|
||||
j = self.chars.next().unwrap().0;
|
||||
has_dot = true;
|
||||
while self.chars.peek().is_some_and(|(_, c)| c.is_ascii_digit()) {
|
||||
j = self.chars.next().unwrap().0;
|
||||
}
|
||||
}
|
||||
|
||||
let s = &self.src[i..j+1];
|
||||
if !has_dot {
|
||||
if let Ok(n) = s.parse::<i32>() {
|
||||
return Ok((i, Token::Int(n), j+1))
|
||||
}
|
||||
}
|
||||
match s.parse::<f64>() {
|
||||
Ok(n) => Ok((i, Token::Float(n), j+1)),
|
||||
Ok(n) => Ok((i, Token::Number(n), j+1)),
|
||||
Err(_) => Err(LexerError::InvalidNumber(i, j+1)),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -364,6 +364,30 @@ fn c_lambertw_init(z: vec2f, br: f32) -> vec2f {
|
|||
}
|
||||
}
|
||||
|
||||
fn c_erf(z: vec2f) -> vec2f {
|
||||
if z.x >= 0 {
|
||||
return c_erf_plus(z);
|
||||
} else {
|
||||
return -c_erf_plus(-z);
|
||||
}
|
||||
}
|
||||
|
||||
const ERF_P = 0.3275911;
|
||||
const ERF_A1 = 0.2548295922;
|
||||
const ERF_A2 = -0.2844967358;
|
||||
const ERF_A3 = 1.4214137412;
|
||||
const ERF_A4 = -1.4531520268;
|
||||
const ERF_A5 = 1.0614054292;
|
||||
fn c_erf_plus(z: vec2f) -> vec2f {
|
||||
let t = c_recip(vec2(1.0, 0.0) + ERF_P * z);
|
||||
let m = c_exp(-c_mul(z, z));
|
||||
let r = c_mul(t, vec2f(ERF_A1, 0.0)
|
||||
+ c_mul(t, vec2f(ERF_A2, 0.0)
|
||||
+ c_mul(t, vec2f(ERF_A3, 0.0)
|
||||
+ c_mul(t, vec2f(ERF_A4, 0.0) + t * ERF_A5))));
|
||||
return vec2f(1.0, 0.0) - c_mul(m, r);
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// rendering //
|
||||
/////////////////
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::{num::NonZeroU64, io::Cursor};
|
||||
|
||||
use wgpu::util::DeviceExt;
|
||||
use wgpu::{util::DeviceExt, MemoryHints};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
|
@ -37,9 +37,9 @@ impl Uniforms {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct WgpuState {
|
||||
pub struct WgpuState<'a> {
|
||||
pub uniforms: Uniforms,
|
||||
surface: wgpu::Surface,
|
||||
surface: wgpu::Surface<'a>,
|
||||
device: wgpu::Device,
|
||||
config: wgpu::SurfaceConfiguration,
|
||||
render_pipeline: Option<wgpu::RenderPipeline>,
|
||||
|
@ -49,12 +49,12 @@ pub struct WgpuState {
|
|||
queue: wgpu::Queue
|
||||
}
|
||||
|
||||
impl WgpuState {
|
||||
pub async fn new<W>(window: &W, size: (u32, u32)) -> Self
|
||||
where W: raw_window_handle::HasRawWindowHandle + raw_window_handle::HasRawDisplayHandle {
|
||||
impl<'a> WgpuState<'a> {
|
||||
pub async fn new<W>(window: &'a W, size: (u32, u32)) -> Self
|
||||
where W: raw_window_handle::HasWindowHandle + raw_window_handle::HasDisplayHandle + Sync {
|
||||
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default());
|
||||
let surface = unsafe { instance.create_surface(&window) }.unwrap();
|
||||
let surface = instance.create_surface(window).unwrap();
|
||||
|
||||
let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::default(),
|
||||
|
@ -65,8 +65,12 @@ impl WgpuState {
|
|||
let (device, queue) = adapter.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
label: None,
|
||||
features: wgpu::Features::empty(),
|
||||
limits: wgpu::Limits::downlevel_webgl2_defaults(),
|
||||
required_features: wgpu::Features::empty(),
|
||||
required_limits: wgpu::Limits {
|
||||
max_texture_dimension_2d: 8192,
|
||||
..wgpu::Limits::downlevel_webgl2_defaults()
|
||||
},
|
||||
memory_hints: MemoryHints::Performance,
|
||||
},
|
||||
None
|
||||
).await.map_err(|e| e.to_string()).unwrap();
|
||||
|
@ -81,6 +85,7 @@ impl WgpuState {
|
|||
present_mode: wgpu::PresentMode::Fifo,
|
||||
alpha_mode: wgpu::CompositeAlphaMode::Auto,
|
||||
view_formats: vec![format],
|
||||
desired_maximum_frame_latency: 2,
|
||||
};
|
||||
surface.configure(&device, &config);
|
||||
|
||||
|
@ -168,12 +173,14 @@ impl WgpuState {
|
|||
let vertex = wgpu::VertexState {
|
||||
module: &vertex_module,
|
||||
entry_point: "main",
|
||||
compilation_options: Default::default(),
|
||||
buffers: &[]
|
||||
};
|
||||
|
||||
let fragment = wgpu::FragmentState {
|
||||
module: &fragment_module,
|
||||
entry_point: "main",
|
||||
compilation_options: Default::default(),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: self.config.format,
|
||||
blend: Some(wgpu::BlendState {
|
||||
|
@ -201,6 +208,7 @@ impl WgpuState {
|
|||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
self.render_pipeline = Some(render_pipeline);
|
||||
|
@ -216,7 +224,7 @@ impl WgpuState {
|
|||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
||||
store: true,
|
||||
store: wgpu::StoreOp::Store,
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -224,6 +232,8 @@ impl WgpuState {
|
|||
label: None,
|
||||
color_attachments: &[Some(color_attachment)],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
if let Some(pipeline) = &self.render_pipeline {
|
||||
rpass.set_pipeline(pipeline);
|
||||
|
|
Loading…
Reference in a new issue