From 064481283e95a0ff2daa1e947242992eb57aefea Mon Sep 17 00:00:00 2001 From: ajet Date: Fri, 30 Jan 2026 09:58:55 -1000 Subject: [PATCH] ideate --- README.md | 500 ++++++++++++++++++++++++++++++++++++++++++++ dual-mode.md | 16 +- hiccup-prototype.md | 17 +- 3 files changed, 512 insertions(+), 21 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..e3843d7 --- /dev/null +++ b/README.md @@ -0,0 +1,500 @@ +# Clojure-Typst + +A Clojure-inspired Lisp that compiles to Typst, bringing s-expressions, functional programming, macros, and REPL-driven development to modern typesetting. + +## Dual Mode: Hiccup + Raw Escapes + +Two modes coexist. Use whichever fits the moment. + +```clojure +;; HICCUP - when you need programmatic structure +[:heading {:level 1} "Title"] + +;; RAW - when you just want to write +#t"= Title" +``` + +### Mixing Freely + +```clojure +[;; Hiccup for structured parts + [:heading {:level 1} title] + + ;; Raw escape for prose-heavy sections + #t" + This is just regular Typst content. I can write *bold* and _italic_ + without all the brackets. Much nicer for prose. + + - Bullet one + - Bullet two + " + + ;; Back to hiccup for programmatic stuff + [:table {:columns 2} + (for [row data] + (list (:name row) (:value row)))] + + ;; Raw with interpolation + #t"The answer is ~(calculate-answer data)." + + ;; Nested: raw inside hiccup + [:block {:inset "1em" :fill "gray.lighten(90%)"} + #t"This is a _sidebar_ with easy formatting."] + + ;; Nested: hiccup inside raw + #t" + = Section + + Here's a dynamic table: + ~(render-table results) + + And back to prose. + "] +``` + +### Raw Escape Syntax + +```clojure +;; Short string +#t"= Heading" + +;; Multi-line block +#t" +Multiple lines +of typst content +" + +;; Content with quotes (must escape) +#t"He said \"hello\" and *left*." +``` + +### Interpolation Inside Raw + +Using `~()` - the unquote concept from Clojure's syntax-quote: + +```clojure +;; Simple expression +#t"The value is ~(expr)." + +;; Hiccup expression returns content +#t"Click ~([:link {:src url} label]) to continue." + +;; Conditional +#t"Status: ~(if done? 'Complete' 'Pending')" + +;; Loop +#t" += Items +~(for [i items] + (str '- ' (:name i) '\n')) +" +``` + +## Hiccup Syntax + +All elements follow standard Hiccup: + +```clojure +[:tag {attrs} children...] +``` + +- **tag**: Element type (keyword) +- **attrs**: Optional map of attributes +- **children**: Content (strings, nested elements, or expressions) + +### Element Categories + +| Category | Examples | Children | +|----------|----------|----------| +| Content elements | `heading`, `strong`, `emph`, `block` | Display content | +| Source elements | `link`, `image`, `bibliography` | `link` has display text; others have none | +| List elements | `list`, `enum`, `table` | Each child = one item/cell | +| Container elements | `figure` | Single content child | +| Math | `math` | Math expression string | +| References | `ref`, `cite` | None | +| Code | `raw` | Code string to display | +| Rules | `set`, `show` | Configuration | + +### Reserved Attributes + +| Attr | Used by | Purpose | +|------|---------|---------| +| `:src` | `link`, `image`, `bibliography` | Source URL/path | +| `:label` | Any element | Makes element referenceable | +| `:caption` | `figure` | Figure caption text | +| `:target` | `ref` | Label to reference | +| `:keys` | `cite` | Citation key(s) | +| `:element` | `set`, `show` | Element type to configure | +| `:block` | `math`, `raw` | Display as block (default: inline) | +| `:lang` | `raw` | Code language for syntax highlighting | + +### Basic Elements + +```clojure +;; Heading +[:heading {:level 2} "My Section"] +;; => #heading(level: 2)[My Section] + +;; Inline formatting +["Hello " [:strong "world"] " and " [:emph "goodbye"]] +;; => Hello #strong[world] and #emph[goodbye] + +;; Link +[:link {:src "https://typst.app"} "Typst"] +;; => #link("https://typst.app")[Typst] + +;; Image +[:image {:src "diagram.png" :width "50%"}] +;; => #image("diagram.png", width: 50%) +``` + +### Lists and Tables + +Children are direct content, not wrapped in `:item` or `:cell` tags: + +```clojure +;; List +[:list "First" "Second" "Third"] +;; => #list([First], [Second], [Third]) + +;; Table +[:table {:columns 2} + [:strong "Header 1"] [:strong "Header 2"] + "Data 1" "Data 2"] +;; => #table(columns: 2, [#strong[Header 1]], [#strong[Header 2]], [Data 1], [Data 2]) +``` + +### Figures + +Caption is an attribute; child element is the content: + +```clojure +[:figure {:caption "Architecture diagram" :label :fig/arch} + [:image {:src "arch.png" :width "80%"}]] +;; => #figure(image("arch.png", width: 80%), caption: [Architecture diagram]) +``` + +### Math + +```clojure +;; Inline math (default) +[:math "x^2 + y^2"] +;; => $x^2 + y^2$ + +;; Block/display math +[:math {:block true} "sum_(i=0)^n i"] +;; => $ sum_(i=0)^n i $ +``` + +### Code Display + +```clojure +;; Inline code +[:raw "let x = 1"] +;; => `let x = 1` + +;; Code block with language +[:raw {:lang "python" :block true} "print('hello')"] +;; => ```python print('hello') ``` +``` + +### References and Citations + +```clojure +[:ref {:target :fig/arch}] ;; => @fig:arch +[:cite {:keys [:smith2020]}] ;; => @smith2020 +``` + +### Document Settings + +```clojure +;; Set rules +[:set {:element :page :paper "a4" :margin "2cm"}] +;; => #set page(paper: "a4", margin: 2cm) + +[:set {:element :text :font "New Computer Modern" :size "11pt"}] +;; => #set text(font: "New Computer Modern", size: 11pt) + +;; Show rules +[:show {:element :heading} + [:set {:element :text :fill "blue"}]] +;; => #show heading: set text(fill: blue) +``` + +### Spacing and Paragraphs + +```clojure +[:h "2em"] ;; => #h(2em) +[:v "1em"] ;; => #v(1em) +[:parbreak] ;; paragraph break +[:linebreak] ;; line break within paragraph +``` + +## Visualization + +Three levels of visualization, all mappable to hiccup. + +### Built-in Shapes + +```clojure +[:rect {:width "2cm" :height "1cm" :fill "blue"} "Label"] +;; => #rect(width: 2cm, height: 1cm, fill: blue)[Label] + +[:circle {:radius "0.5cm" :fill "green"}] +;; => #circle(radius: 0.5cm, fill: green) + +[:line {:start [0 0] :end [100 50] :stroke "2pt"}] +;; => #line(start: (0pt, 0pt), end: (100pt, 50pt), stroke: 2pt) + +[:polygon {:fill "yellow"} [0 0] [1 0] [0.5 1]] +;; => #polygon(fill: yellow, (0pt, 0pt), (1pt, 0pt), (0.5pt, 1pt)) +``` + +### CeTZ - Canvas Drawing + +For complex custom drawings. Like TikZ for LaTeX. + +```clojure +[:cetz/canvas + [:cetz/line [0 0] [2 2]] + [:cetz/circle [1 1] {:radius 0.5}] + [:cetz/rect [0 0] [2 1] {:fill "blue"}]] +``` + +Compiles to: + +```typst +#import "@preview/cetz:0.4.2" + +#cetz.canvas({ + import cetz.draw: * + + line((0, 0), (2, 2)) + circle((1, 1), radius: 0.5) + rect((0, 0), (2, 1), fill: blue) +}) +``` + +### Fletcher - Diagrams & Flowcharts + +High-level diagram DSL for architecture, flowcharts, state machines. + +```clojure +[:fletcher/diagram {:node-stroke "1pt" :spacing "2em"} + [:node [0 0] "Start"] + [:edge "->"] + [:node [1 0] "Process"] + [:edge "->"] + [:node [2 0] "End"]] +``` + +Node shapes: `:circle`, `:diamond`, `:pill`, `:hexagon`, `:cylinder` + +Edge styles: `"->"`, `"->>"`, `"--"`, `"<->"`, `"hook->"` + +```clojure +;; Flowchart example +[:fletcher/diagram {:node-stroke "1pt" :node-fill "white" :spacing "3em"} + [:node [0 0] "Start" {:shape :pill}] + [:node [0 1] "Input Data"] + [:node [0 2] "Valid?" {:shape :diamond}] + [:node [1 2] "Error" {:fill "red.lighten(80%)"}] + [:node [0 3] "Process"] + [:node [0 4] "End" {:shape :pill}] + + [:edge [0 0] [0 1] "->"] + [:edge [0 1] [0 2] "->"] + [:edge [0 2] [1 2] "->" {:label "no"}] + [:edge [0 2] [0 3] "->" {:label "yes"}] + [:edge [1 2] [0 1] "->" {:bend -40}] + [:edge [0 3] [0 4] "->"]] +``` + +### DSL Helpers + +Build higher-level abstractions: + +```clojure +(defn service [id name & {:keys [x y color] :or {color "white"}}] + [:node [x y] name {:id id :shape :pill :fill color}]) + +(defn database [id name & {:keys [x y color] :or {color "gray.lighten(80%)"}}] + [:node [x y] name {:id id :shape :cylinder :fill color}]) + +(defn connects [from to & {:keys [label style] :or {style "->"}}] + [:edge from to style (when label {:label label})]) + +[:fletcher/diagram {:spacing "3em"} + (service :auth "Auth" :x 0 :y 0) + (service :users "Users" :x 1 :y 0) + (database :pg "PostgreSQL" :x 0 :y 1) + (database :redis "Redis" :x 1 :y 1 :color "red.lighten(80%)") + + (connects :auth :pg) + (connects :users :redis :label "cache")] +``` + +## Macros + +It's a Lisp - of course we have macros. + +### Content Macros + +```clojure +;; Define a reusable content pattern +(defmacro defcomponent [name args & body] + `(defn ~name ~args + (list ~@body))) + +;; Conditional content sections +(defmacro when-content [test & body] + `(when ~test + (list ~@body))) + +;; Use it +(when-content show-abstract? + [:heading {:level 2} "Abstract"] + abstract-text) +``` + +### DSL Macros + +```clojure +;; Math DSL +(defmacro math-block [& exprs] + `[:math {:block true} + ~(compile-math-dsl exprs)]) + +(math-block + (sum :i 0 :n + (frac (pow x i) (factorial i)))) +;; => $ sum_(i=0)^n x^i / i! $ + +;; Shorthand for references +(defmacro ref [id] + `[:ref {:target ~id}]) + +(defmacro cite [& keys] + `[:cite {:keys [~@keys]}]) + +(ref :fig/arch) ; => [:ref {:target :fig/arch}] +(cite :smith2020 :jones2021) ; => [:cite {:keys [:smith2020 :jones2021]}] +``` + +### Template Macros + +```clojure +(deftemplate ieee-paper [title authors abstract] + [[:set {:element :page :paper "us-letter" :columns 2}] + [:heading {:level 1 :align "center"} ~title] + (render-authors ~authors) + (render-abstract ~abstract) + ~'&body]) + +(ieee-paper + "Neural Networks for Fun and Profit" + [{:name "Alice" :affil "MIT"}] + "We present..." + + [:heading {:level 2} "Introduction"] + "..." + [:heading {:level 2} "Methods"] + "...") +``` + +## Full Example + +```clojure +(ns my-paper.core + (:require [clojure-typst.core :refer :all])) + +(def authors + [{:name "Alice Chen" :affiliation "MIT" :email "alice@mit.edu"} + {:name "Bob Smith" :affiliation "Stanford" :email "bob@stanford.edu"}]) + +(def results + [{:method "Baseline" :accuracy 0.72} + {:method "Ours" :accuracy 0.94}]) + +(defn author-card [{:keys [name affiliation email]}] + [:block {:inset "1em"} + [:strong name] [:linebreak] + [:emph affiliation] [:linebreak] + [:link {:src (str "mailto:" email)} email]]) + +(defn results-table [data] + [:table {:columns 2 :align ["left" "right"]} + [:strong "Method"] [:strong "Accuracy"] + (for [{:keys [method accuracy]} data] + (list + method + (format "%.1f%%" (* 100 accuracy))))]) + +(def paper + [[:set {:element :page :paper "a4"}] + [:set {:element :text :font "Linux Libertine"}] + + [:heading {:level 1} "A Very Important Paper"] + + [:block {:align "center"} + (interpose [:h "2em"] + (map author-card authors))] + + #t" + = Abstract + + We present a novel approach to solving important problems. + Our method achieves *state-of-the-art* results on several + benchmarks while maintaining computational efficiency. + + = Introduction + + The problem of _important things_ has long challenged researchers. + Previous approaches @citation1 @citation2 have made progress but + fundamental limitations remain. + " + + [:figure {:caption "System architecture overview." :label :fig/arch} + [:image {:src "architecture.png" :width "80%"}]] + + #t" + Our method works by first processing the input through + the encoder (see @fig:arch). + + = Results + " + + (results-table results) + + #t" + As shown in the table above, our method significantly + outperforms the baseline. + + = Conclusion + + We have demonstrated that our approach is ~(if (> (:accuracy (last results)) 0.9) + \"highly effective\" + \"somewhat effective\"). + " + + [:bibliography {:src "refs.bib"}]]) + +(compile-to-typst paper "paper.typ") +``` + +## Rules Summary + +1. **`#t"..."`** - raw Typst content, returned as-is +2. **`~(...)`** inside raw - interpolate Clojure expression +3. **`[:tag ...]`** - hiccup, compiled to Typst function calls +4. **Strings in hiccup** - become Typst content directly +5. **Sequences flatten** - `(for ...)`, `(map ...)`, `(list ...)` results merge into parent + +| Situation | Use | +|-----------|-----| +| Prose, paragraphs, natural writing | Raw `#t"..."` | +| Tables, figures, structured layout | Hiccup | +| Loops, conditionals, data-driven | Hiccup or interpolated raw | +| Components, reusable parts | Hiccup functions | + +**The principle:** Structure when you need it, prose when you don't. diff --git a/dual-mode.md b/dual-mode.md index f2cc90f..1925ba9 100644 --- a/dual-mode.md +++ b/dual-mode.md @@ -50,7 +50,7 @@ Both systems coexist. Use whichever fits the moment. "] ``` -## Escape Syntax Options +## Escape Syntax ```clojure ;; Short string @@ -62,18 +62,8 @@ Multiple lines of typst content " -;; Triple-quote for content with quotes -#t""" -He said "hello" and *left*. -""" - -;; With explicit delimiters (if we need) -#typst{ - Content here with {braces} works fine -} - -;; Tagged for syntax highlighting in editors? -#typst/content "..." +;; Content with quotes (must escape) +#t"He said \"hello\" and *left*." ``` ## Interpolation Inside Raw diff --git a/hiccup-prototype.md b/hiccup-prototype.md index ebf6e19..23688c9 100644 --- a/hiccup-prototype.md +++ b/hiccup-prototype.md @@ -138,7 +138,8 @@ The result is $x = 42$. ;; Generate content programmatically [[:heading "Authors"] - (map #(author-block (:name %) (:inst %)) authors)] + (for [{:keys [name inst]} authors] + (author-block name inst))] ``` ## Special Forms for Content @@ -172,15 +173,15 @@ If hiccup feels too heavy, we could just use Lisp for logic and escape to raw Ty (* n (factorial (dec n))))) ;; Escape to typst content -#typst{ - = Results +#t" += Results - The factorial of 5 is ~(factorial 5). +The factorial of 5 is ~(factorial 5). - Here's a table of factorials: - ~(for [i (range 1 11)] - (str "- " i "! = " (factorial i) "\n")) -} +Here's a table of factorials: +~(for [i (range 1 11)] + (str \"- \" i \"! = \" (factorial i) \"\\n\")) +" ``` ## Questions