commit fd0ec5aabcd4de9258fde0b85d7f473f152621b5 Author: ajet Date: Fri Jan 30 09:11:34 2026 -1000 ideate diff --git a/dual-mode.md b/dual-mode.md new file mode 100644 index 0000000..ba7e7c1 --- /dev/null +++ b/dual-mode.md @@ -0,0 +1,391 @@ +# Dual Mode: Hiccup + Raw Escapes + +Both systems coexist. Use whichever fits the moment. + +## The Two Modes + +```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 (use :block for container) + [: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. + "] +``` + +## Escape Syntax Options + +```clojure +;; Short string +#t"= Heading" + +;; Multi-line block +#t" +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 "..." +``` + +## 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 {:dest url} label]) to continue." + +;; Conditional +#t"Status: ~(if done? 'Complete' 'Pending')" + +;; Loop +#t" += Items +~(for [i items] + (str '- ' (:name i) '\n')) +" + +;; Or loop returning hiccup (items are direct list children) +#t" += Items +~[:list (for [i items] (:name i))] +" +``` + +Same mental model as macro unquote: +```clojure +`(let [x ~expr] ...) ; macro: escape to eval +#t"Value: ~(expr)" ; template: escape to eval +``` + +## 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 {:dest (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 + [[:heading {:level 1} "A Very Important Paper"] + + ;; Programmatic author list + [:block {:align "center"} + (interpose [:h "2em"] + (map author-card authors))] + + ;; Prose in raw mode - much nicer to write + #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. + + In this work, we propose a new framework that addresses these + limitations directly. Our key contributions are: + + + A novel architecture for processing data + + Theoretical analysis of convergence properties + + Extensive empirical evaluation + + = Methods + " + + ;; Back to hiccup for the technical diagram + [:figure {:caption "System architecture overview."} + [:image {:width "80%"} "architecture.png"]] + + #t" + Our method works by first processing the input through + the encoder (see @fig:arch). The encoded representation + is then passed to the decoder which produces the output. + + = Results + " + + ;; Programmatic table + (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"). + Future work will explore extensions to other domains. + " + + ;; Bibliography could be generated + [:bibliography "refs.bib"]]) + +;; Compile it +(compile-to-typst paper "paper.typ") +``` + +## Macros + +It's a Lisp - of course we have macros. + +### Standard Code Macros + +```clojure +;; Threading for cleaner data transforms +(defmacro -> [x & forms] + ...) + +(-> data + (filter :active) + (map :name) + (sort)) + +;; Custom control flow +(defmacro when-let [[binding expr] & body] + `(let [~binding ~expr] + (when ~binding ~@body))) +``` + +### Content Macros + +Macros that expand to hiccup/content: + +```clojure +;; Define a reusable content pattern +(defmacro defcomponent [name args & body] + `(defn ~name ~args + (list ~@body))) ; sequences flatten into parent content + +;; Conditional content sections +(defmacro when-content [test & body] + `(when ~test + (list ~@body))) + +;; Use it +(when-content show-abstract? + [:heading {:level 2} "Abstract"] + abstract-text) +``` + +### Document Structure Macros + +```clojure +;; Academic paper structure +(defmacro paper [& {:keys [title authors abstract] :as opts} & sections] + `[[:heading {:level 1} ~title] + (author-block ~authors) + (when ~abstract + (list + [:heading {:level 2} "Abstract"] + ~abstract)) + ~@sections]) + +;; Usage +(paper + :title "My Great Paper" + :authors ["Alice" "Bob"] + :abstract "We did cool stuff." + + [:heading {:level 2} "Introduction"] + "..." + + [:heading {:level 2} "Methods"] + "...") +``` + +### DSL Macros + +Build mini-languages for specific domains: + +```clojure +;; Math DSL that's easier to write than raw Typst math +(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! $ + +;; Table DSL +(defmacro deftable [name headers & row-specs] + ...) + +(deftable results-table + ["Method" "Precision" "Recall" "F1"] + :from results + :row (fn [{:keys [method p r f1]}] + [method (fmt p) (fmt r) (fmt f1)])) + +;; Figure DSL with auto-numbering +(defmacro figure [id & {:keys [src caption width]}] + `(do + (register-figure! ~id) + [:figure {:label ~id :caption ["Figure " (figure-num ~id) ": " ~caption]} + [:image {:width ~(or width "100%")} ~src]])) + +(figure :arch + :src "architecture.png" + :caption "System overview" + :width "80%") +``` + +### Syntax Extension Macros + +```clojure +;; Custom reader syntax for common patterns +;; (would require reader macro support) + +;; Shorthand for references +(defmacro ref [id] + `[:ref ~(str "@" (name id))]) + +;; Shorthand for citations +(defmacro cite [& keys] + `[:cite ~@(map #(str "@" (name %)) keys)]) + +;; Usage +(ref :fig:arch) ; => @fig:arch +(cite :smith2020 :jones2021) ; => @smith2020 @jones2021 +``` + +### Template Macros + +```clojure +;; Define document templates +(defmacro deftemplate [name args & structure] + `(defmacro ~name ~args + ~@structure)) + +(deftemplate ieee-paper [title authors abstract] + [[:set :page {:paper "us-letter" :columns 2}] + [:heading {:level 1 :align "center"} ~title] + (render-authors ~authors) + (render-abstract ~abstract) + ~'&body]) ; splice remaining content + +;; Use template +(ieee-paper + "Neural Networks for Fun and Profit" + [{:name "Alice" :affil "MIT"}] + "We present..." + + [:heading {:level 2} "Introduction"] + "..." + [:heading {:level 2} "Methods"] + "...") +``` + +### Why Macros Matter Here + +| Pattern | Without Macros | With Macros | +|---------|---------------|-------------| +| Repeated structure | Copy-paste hiccup | `defcomponent` | +| Conditional sections | Verbose `if` expressions | `when-content` | +| Domain languages | Manual compilation | DSL macros | +| Document templates | Function composition | Template macros | +| Boilerplate elimination | Nowhere to hide it | Macro it away | + +**The power:** Your document language grows to fit your domain. + +## Rules + +1. **`#t"..."`** - raw Typst content, returned as-is +2. **`~(...)`** inside raw - interpolate Clojure expression (like unquote) +3. **`[:tag ...]`** - hiccup, compiled to Typst function calls +4. **Strings in hiccup** - become Typst content directly +5. **Seqs in hiccup** - flattened (so `map`/`for` just work) +6. **Sequences flatten** - `(for ...)`, `(map ...)`, `(list ...)` results merge into parent + +## Why Both? + +| 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 | +| Quick one-off formatting | Either, your preference | + +**The principle:** Structure when you need it, prose when you don't. diff --git a/hiccup-prototype.md b/hiccup-prototype.md new file mode 100644 index 0000000..f6557dc --- /dev/null +++ b/hiccup-prototype.md @@ -0,0 +1,209 @@ +# Hiccup for Typst Content - Prototype + +## The Idea + +Hiccup represents markup as nested data structures: +```clojure +[:tag {attrs} & children] +``` + +## Syntax Mapping + +### Clojure-Typst Hiccup → Typst Output + +```clojure +;; Heading +[:heading {:level 2} "My Section"] +;; => #heading(level: 2)[My Section] + +;; Inline formatting (paragraphs are implicit in Typst) +["Hello " [:strong "world"] " and " [:emph "goodbye"]] +;; => Hello #strong[world] and #emph[goodbye] + +;; Lists (children are direct content, not :item tags) +[:list + "First thing" + "Second thing" + [[:strong "Important"] " third thing"]] +;; => #list( +;; [First thing], +;; [Second thing], +;; [#strong[Important] third thing] +;; ) + +;; Tables (cells are direct content, not :cell tags) +[:table {:columns 3} + "A" "B" "C" + "1" "2" "3"] +;; => #table(columns: 3, +;; [A], [B], [C], +;; [1], [2], [3] +;; ) + +;; Raw content blocks +[:raw "= Direct Typst Markup\nJust passed through"] + +;; Code blocks (use :raw with :lang) +[:raw {:lang "python" :block true} "print('hello')"] +;; => #raw(lang: "python", block: true, "print('hello')") + +;; Math +[:math "x^2 + y^2 = z^2"] +;; => $x^2 + y^2 = z^2$ + +[:math {:block true} "sum_(i=0)^n i = (n(n+1))/2"] +;; => $ sum_(i=0)^n i = (n(n+1))/2 $ + +;; Images +[:image {:width "50%"} "diagram.png"] +;; => #image("diagram.png", width: 50%) + +;; Links +[:link {:dest "https://typst.app"} "Typst"] +;; => #link("https://typst.app")[Typst] +``` + +## Full Document Example + +```clojure +(def my-doc + [[:heading {:level 1} "My Paper"] + + "This is the introduction. We discuss " + [:emph "important things"] "." + + [:parbreak] + + [:heading {:level 2} "Methods"] + + "We used the following approach:" + + [:list + "Step one" + "Step two" + "Step three"] + + [:heading {:level 2} "Results"] + + "The result is " [:$ "x = 42"] "." + + [:table {:columns 2} + [:strong "Input"] [:strong "Output"] + "1" "1" + "2" "4" + "3" "9"]]) +``` + +**Compiles to:** + +```typst +#heading(level: 1)[My Paper] + +This is the introduction. We discuss #emph[important things]. + +#heading(level: 2)[Methods] + +We used the following approach: + +#list( + [Step one], + [Step two], + [Step three] +) + +#heading(level: 2)[Results] + +The result is $x = 42$. + +#table(columns: 2, + [#strong[Input]], [#strong[Output]], + [1], [1], + [2], [4], + [3], [9] +) +``` + +## Mixing Code and Content + +```clojure +;; Define a reusable component +(defn author-block [name institution] + [:block {:align "center"} + [:strong name] + [:linebreak] + [:emph institution]]) + +;; Use it with data +(def authors + [{:name "Alice" :inst "MIT"} + {:name "Bob" :inst "Stanford"}]) + +;; Generate content programmatically +[[:heading "Authors"] + (map #(author-block (:name %) (:inst %)) authors)] +``` + +## Special Forms for Content + +```clojure +;; Conditional content (strings become paragraphs naturally) +(if peer-reviewed? + "This paper was peer reviewed." + "Preprint - not yet reviewed.") + +;; Loop over data (items are direct content in lists) +[:list + (for [item items] + (format-item item))] + +;; Let bindings for intermediate content +(let [title (get-title data) + abstract (get-abstract data)] + [[:heading title] + abstract]) +``` + +## The Alternative: Lisp for Code Only + +If hiccup feels too heavy, we could just use Lisp for logic and escape to raw Typst: + +```clojure +(defn factorial [n] + (if (<= n 1) + 1 + (* n (factorial (dec n))))) + +;; Escape to typst content +#typst{ + = Results + + The factorial of 5 is ~(factorial 5). + + Here's a table of factorials: + ~(for [i (range 1 11)] + (str "- " i "! = " (factorial i) "\n")) +} +``` + +## Questions + +1. **Implicit paragraphs?** Should adjacent strings auto-wrap in `[:p ...]`? +2. **Shorthand syntax?** Maybe `:#strong` instead of `[:strong ...]`? +3. **Component system?** How fancy do we get with reusable components? +4. **Escaping?** How to include literal `[` or other special chars? +5. **Whitespace handling?** Typst is whitespace-sensitive in content mode + +## Verdict? + +Hiccup gives us: +- ✅ Pure data representation of documents +- ✅ Full programmatic control +- ✅ Composable components +- ✅ Familiar to Clojure devs + +But: +- ❌ More verbose than raw Typst markup +- ❌ Loses Typst's nice content syntax +- ❌ Another layer to learn + +**Hybrid approach might be best:** Lisp for code/logic, with easy escape to Typst content syntax when you just want to write prose. diff --git a/plan.md b/plan.md new file mode 100644 index 0000000..016dae0 --- /dev/null +++ b/plan.md @@ -0,0 +1,84 @@ +# Clojure-Typst: A Clojure-style Lisp for Typst + +## The Problem + +Typst is a modern typesetting system with a nice programming language, but it's not a Lisp. We only like Lisps. + +## The Vision + +Create a Clojure-inspired Lisp that compiles to Typst, bringing: +- S-expression syntax +- Functional programming idioms +- Macros and metaprogramming +- Immutable-first data structures +- REPL-driven development (maybe?) + +## Open Questions + +### Syntax & Semantics +- [ ] How closely do we follow Clojure syntax? +- [ ] What Typst constructs map to Lisp naturally? +- [ ] How do we handle Typst's content mode vs code mode distinction? +- [ ] Do we support Clojure-style destructuring? +- [ ] What about namespaces/modules? + +### Data Structures +- [ ] Vectors `[]`, maps `{}`, sets `#{}`? +- [ ] How do these map to Typst arrays and dictionaries? +- [ ] Lazy sequences - possible in Typst? + +### Interop +- [ ] How do we call Typst functions from our Lisp? +- [ ] How do we emit Typst content/markup? +- [ ] Can we import Typst packages? + +### Implementation +- [ ] What language for the compiler? (Clojure? Rust? Typst itself?) +- [ ] AST representation +- [ ] Compilation strategy (source-to-source? interpreter?) +- [ ] Error messages and source maps + +## Typst Features to Leverage + +- Functions are first-class +- Has closures +- Pattern matching in function args +- Content as a first-class type +- Scripting mode `#` vs content mode + +## Example Syntax Ideas + +```clojure +; Define a function +(defn greet [name] + (str "Hello, " name "!")) + +; Emit typst content +(content + (heading "My Document") + (greet "World")) + +; Maybe something like hiccup for content? +[:heading {:level 1} "My Document"] +["Hello, " [:strong "World"] "!"] +``` + +## Next Steps + +1. Study Typst's language semantics more deeply +2. Identify the minimal viable feature set +3. Decide on implementation language +4. Build a basic parser for s-expressions +5. Implement core special forms (def, fn, let, if, do) +6. Add Typst content emission +7. Iterate + +## Resources + +- [Typst Documentation](https://typst.app/docs) +- [Clojure Reference](https://clojure.org/reference) +- [Make a Lisp](https://github.com/kanaka/mal) + +## Notes + +(Add brainstorming notes here) diff --git a/typst-mapping.md b/typst-mapping.md new file mode 100644 index 0000000..84d1faa --- /dev/null +++ b/typst-mapping.md @@ -0,0 +1,330 @@ +# Hiccup → Typst Mapping Reference + +## Audit of Current Tags + +### ✅ Valid - Direct Typst Functions + +| Hiccup | Typst | Notes | +|--------|-------|-------| +| `[:heading {:level 2} "Text"]` | `#heading(level: 2)[Text]` | ✅ Works | +| `[:strong "text"]` | `#strong[text]` | ✅ Works | +| `[:emph "text"]` | `#emph[text]` | ✅ Works | +| `[:link {:dest "url"} "text"]` | `#link("url")[text]` | ✅ Works | +| `[:image {:width "50%"} "path.png"]` | `#image("path.png", width: 50%)` | ✅ Works | +| `[:block {:inset "1em"} ...]` | `#block(inset: 1em)[...]` | ✅ Works | +| `[:bibliography "refs.bib"]` | `#bibliography("refs.bib")` | ✅ Works | + +### ❌ Invalid - Don't Exist in Typst + +| Bad Hiccup | Problem | Fix | +|------------|---------|-----| +| `[:p "text"]` | No paragraph function | Just use strings, paragraphs are implicit | +| `[:item "text"]` | Not a function | Items are args to `list()` | +| `[:cell "text"]` | Not a function | Cells are args to `table()` | +| `[:div ...]` | HTML, not Typst | Use `[:block ...]` | +| `[:span ...]` | HTML, not Typst | Just inline content, or `[:box ...]` | +| `[:aside ...]` | Doesn't exist | Use `[:block ...]` with styling | +| `[:section ...]` | Doesn't exist | Just use headings | +| `[:doc ...]` | Doesn't exist | Top-level is just content | +| `[:table-row ...]` | Doesn't exist | Tables are flat | + +### ⚠️ Needs Adjustment + +| Current | Problem | Correct | +|---------|---------|---------| +| `[:linebreak]` | Function name | `[:linebreak]` → `#linebreak()` ✅ actually works | +| `[:h-space "2em"]` | Wrong name | `[:h "2em"]` → `#h(2em)` | +| `[:figure [:image ...] [:caption ...]]` | Caption isn't a child | See below | +| `[:math "x^2"]` | Need to distinguish inline/block | `$x^2$` vs `$ x^2 $` | +| `[:code {:lang "py"} "..."]` | Function is `raw` | `#raw(lang: "python", "...")` | +| `[:ref :label]` | Takes label not string | `#ref(