aboutsummaryrefslogtreecommitdiffstats
path: root/include/expr.h
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-19 13:46:57 +0200
committeruser <user@clank>2026-08-19 13:46:57 +0200
commit42b3c10995eb67778d9dd3aa2fd80624896acd5f (patch)
tree751bcb8604967dc8687cf23fbeb1033aa26ca427 /include/expr.h
parentslosh: a website that is a page, not a mock session (diff)
downloadslosh-42b3c10995eb67778d9dd3aa2fd80624896acd5f.tar.gz
slosh-42b3c10995eb67778d9dd3aa2fd80624896acd5f.tar.xz
slosh-42b3c10995eb67778d9dd3aa2fd80624896acd5f.zip
slosh: defaults most people can live in, said by shaders
The out-of-the-box look, rethought as one piece: - the compiled-in palette calms down: muted blue #7aa2f7 on the same neutral darks, with the colours that mean something kept semantic -- amber bell, red-orange dead. sl0p stays one include away as the house style, and contrib/themes/default.kdl is the starting point written down (theme-tour shows it). - derived state defaults now actually follow the theme: the scrolled wash is rebuilt from scroll_bg after the theme block (it used to bake the compiled accent), and declaring a state -- in any file of an include chain -- still wins. The dump writes derived states as comments so C-a e cannot pin what is meant to keep following. - a bell you can see: the frame blinks three times in theme's bell colour then breathes until answered, and a soft diagonal shimmer -- the bell colour halved towards default_fg, width breathing on a sine -- crosses the body for the same 900ms. bell_indicator false silences all of it; states { bell { } } replaces it like any state. since carries the clock dependency, so an idle session pays nothing. - scrollback says where it continues: while scrolled, the viewport's top and bottom rows melt towards an edge with content past it and turn solid at the buffer's end. New expression variables above/below (lines hidden past each edge, own dependency bit, cached per scroll position), carried in shade_ctx_t -- SLOSH_SHADER_ABI 3 -> 4. - expressions survive the dump: a program keeps its source and cb_chain writes it back, so amount="(y % 2) * 40" no longer dumps as a number. - headers move to include/, -Iinclude everywhere; the plugin Makefile's SLOSH var becomes SLOSH_DIR because slosh exports SLOSH=1 into every pane it runs, and building from inside a session read that as a path. - comment drift culled where features outran the prose, and the derivation boilerplate folded into derived_pass()/ctx_scroll().
Diffstat (limited to 'include/expr.h')
-rw-r--r--include/expr.h102
1 files changed, 102 insertions, 0 deletions
diff --git a/include/expr.h b/include/expr.h
new file mode 100644
index 0000000..d593319
--- /dev/null
+++ b/include/expr.h
@@ -0,0 +1,102 @@
+/* Shader expressions: a tiny integer language for the one number a shader
+ * takes, compiled to bytecode at config load.
+ *
+ * The idea is smaller than "shaders in a language". Every built-in shader here
+ * is already `colour_op(amount)` where the amount is a pure function of where
+ * the cell is:
+ *
+ * vignette amount = f(x, y) ruler amount = (x == col) * amount
+ * zebra amount = f(y) margin amount = (x >= col) * amount
+ *
+ * So the part worth handing to a config is the *amount*, not the colour maths.
+ * That keeps the mixing in C where it is fast and correct, gives an expression
+ * no way to produce an invalid cell (it produces one byte), and \u2014 because the
+ * result is a pure function of position \u2014 lets the whole program be evaluated
+ * once into a per-cell map and then reused, which is what makes an interpreter
+ * affordable at all. Measured: 56ns/cell interpreted, 2ns/cell from the map.
+ *
+ * The language is deliberately total. No loops, no recursion, no jumps: even
+ * `?:` compiles to evaluating both sides and selecting, which is safe because
+ * nothing in here has an effect. Evaluation is therefore O(program length),
+ * a bad expression cannot hang a session, and there is nothing to sandbox.
+ *
+ * variables x y cols rows curx cury cursor focused t since above below
+ * operators + - * / % < > <= >= == != && || ! ?:
+ * functions min max abs clamp dist
+ *
+ * `x`/`y` are cell positions inside the rect the pass runs over (a pane's
+ * contents, or its frame for a chrome pass), `t` is a millisecond clock for
+ * animation, `since` is how many milliseconds the pane has been in the state
+ * it is in — which is what a one-shot effect needs, because `t` says what time
+ * it is and not how long ago something happened. `above`/`below` are the lines
+ * of scrollback hidden past the viewport's top and bottom edges, both 0 at
+ * the present — which is what lets an effect say "there is more this way".
+ * `dist` corrects for a cell being about twice as tall as it is wide. Division and modulo by zero are 0
+ * rather than a trap: an expression that is wrong should look wrong, not take
+ * the session with it.
+ */
+#ifndef SLOSH_EXPR_H
+#define SLOSH_EXPR_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+typedef struct expr_prog expr_prog_t;
+
+/* What a program reads, worked out by the compiler rather than declared. This
+ * is what decides whether its output can be cached and against what, so it is
+ * derived from the source and can never disagree with it. */
+enum {
+ EXPR_DEP_POS = 1 << 0, /* x, y */
+ EXPR_DEP_CURSOR = 1 << 1, /* curx, cury, cursor */
+ EXPR_DEP_TIME = 1 << 2, /* t: recomputed every frame, never cached */
+ EXPR_DEP_FOCUS = 1 << 3, /* focused */
+ /* cols, rows. Not part of the cache key -- the key always carries the rect,
+ * because a map is per rect by construction -- but it still has to be a
+ * dependency, or a program reading only the size would look constant and be
+ * folded at compile time, where the size is not known yet and is zero. */
+ EXPR_DEP_SIZE = 1 << 4,
+ EXPR_DEP_SCROLL = 1 << 5, /* above, below */
+};
+
+/* Compile, or NULL with a reason in `err`. A program that reads nothing is
+ * folded to its value at compile time and still returned, so callers have one
+ * shape to handle. */
+expr_prog_t *expr_compile(const char *src, char *err, size_t errcap);
+void expr_free(expr_prog_t *p);
+
+/* What it reads (EXPR_DEP_*). 0 means the result is a constant. */
+unsigned expr_deps(const expr_prog_t *p);
+/* The value of a program that reads nothing; undefined otherwise. */
+int expr_constant(const expr_prog_t *p);
+
+/* Everything a program can see. Filled by the shader pass. */
+typedef struct {
+ int x, y, cols, rows;
+ int curx, cury, cursor;
+ int focused;
+ int64_t t;
+ /* Milliseconds since the pane entered its current state, clamped at 0. */
+ int64_t since;
+ /* Lines of scrollback hidden above and below the viewport, both 0 at the
+ * present (and for a pass over anything that is not a pane's contents). */
+ int above, below;
+} expr_env_t;
+
+/* Evaluate for one cell. Clamped to 0..255 by the caller, not here: the
+ * language has no opinion about what its number is for. */
+int expr_eval(const expr_prog_t *p, const expr_env_t *env);
+
+/* The text this program was compiled from, owned by the program. For the
+ * config dump, which has to write back the line somebody wrote rather than
+ * pretend an expression was a number. */
+const char *expr_source(const expr_prog_t *p);
+
+/* Evaluate for every cell of a cols x rows rect into `out` (cols*rows bytes,
+ * clamped to 0..255), reusing the last result when nothing it reads has
+ * changed. Returns false when the program reads the clock, which is the one
+ * case there is nothing to reuse. */
+bool expr_amount_map(expr_prog_t *p, const expr_env_t *env, uint8_t **out);
+
+#endif /* SLOSH_EXPR_H */