Language for Document Layout
I'm thinking about making a lisp-y language for laying out complicated documents like rpg character sheets, so I can get the advantage of programmatic sizing and the ability to make reusable components. This is probably impractically large in scope.
First, take a lispy syntax
(Grid [columns (Repeat [times 2] 1fr)] [gap 24pt] (Column (Grid [columns 1fr 48pt] (Field "Names") (Field "Pronouns"))))
And then add semantic whitespace that you can optionally use instead of parentheses.
Grid [columns (Repeat [times 2] 1fr)] [gap 24pt] Column Grid [columns 1fr 48pt] Field "Pronouns" FIeld "Names"
Named arguments maybe can also use semantic whitespace
Grid columns: (Repeat [times 2] 1fr) gap: 24pt Column Grid [columns 1fr 48pt] Field [name "Names"] Field [name "Pronouns"]
S-Expressions
S-expressions use angle brackets. Bare strings inside S-expressions are treated as symbols.
<hello <<name "Clarity">> "world">
The whitespace version of S-expressions use tildes.
~ hello ~ ~ name "Clarity "world"
It's possible to call functions and reference variables inside of S-expressions:
Eq? <number (Add 1 1)> <number 2>
Eq? (Set 'hello 3) <number (hello)> <number 3>
Quoting
Quoting a function turns it into a S-expression with the function names replaced with symbols.
Eq? Quote (MyFn [name "world"] "hello") <MyFn <<name "world">> "hello">
Eq? Quote(Hello "world") <Hello <> "world">
Rendering
The renderer probably takes s-expressions that represent a subset of the svg spec
~ path ~ ~ stroke 30 ~ color "blue" ~ move-abs 10 30 ~ arc-abs 20 20 0 0 0 50 30 ~ arc-abs 20 20 0 0 0 90 30 ~ quad-abs 90 60 50 90 ~ quad-abs 10 60 10 30 ~ close-path # same as...
<path <<stroke 30> <color "blue">> <move-abs 10 30> <arc-abs 20 20 0 0 0 50 30> <arc-abs 20 20 0 0 0 90 30> <quad-abs 90 60 50 90> <quad-abs 10 60 10 30> <close-path>>
And then layout functions like Row go through and modify any absolute positions to reflect their actual position.
Row gap: 10pt justify: 'space-between align: 'center max-width: 100pt Circle [r 5pt] Circle [r 7pt]
Row gap: 10pt justify: 'space-between align: 'center max-width: 100pt Circle [r 5pt] Circle [r 7pt] <circle <<cx 7> <cy 7> <r 5>>> <circle <<cx 93> <cy 7> <r 7>>>
Hmm.. probably a "component" has to actually produce two functions, one for sizing and one for rendering.
So a naive circle function might be defined as
Define [r Measurement?] 'Circle Pair # Given a size, return the min size and max size of this Lambda [w Measurement?] [h Measurement?] Pair (* 2 r) (* 2 r) Pair (* 2 r) (* 2 r) Lambda [x Measurement?] [y Measurement?] [w Measurement?] [h Measurement?] ~ circle ~ ~ cx (+ x r) ~ cy (+ x r) ~ r (r)
And... god, okay, the row function might be
Define [gap] [max-width] [_ children] Pair λ [w] [h] Define measurements children<-Map λ [component] Call (component->First) w h Pair + Map (λ [m] m->First->First)) measurements If (Empty? measurements) 0 * (- measurements->Count 1) (Or gap 0) measurements<-Map (λ [m] m->First->last) λ [x] [y] [w] [h] # fuck idk
Syntax Brainstorming
Thinking through other syntax things...
Define Field [^name text] Column [pad 8 0 0 0] HorizontalLine String name
Define Title "My Font"
Define Dot # Maybe you can include arbitrary svg? or maybe I should define my own vector format???
Define Approach [^name text] Repeat [times 4] (Dot) String name
Define Attribute [name text] [^appr1 text] [^appr2 text] [^appr3 text] Column String [size 24pt] [font (Title)] name Row Grid [rows 4 8 8 8] [cols 8 8 8 8 8 auto] [merge 0 1 0 3] Map Lambda [value] (String (value)) "d4" "d6" "d8" "d10" "d12" Nothing VerticalLine Approach appr1 Approach appr2 Approach appr3
Grid [columns 1fr 1fr] [gap 24pt] Column Box Right [size 48pt] (Field "Pronouns") FIeld "Names" Box Left [pad 0 8pt 0 0] Column Attribute [name "Strength"] "Battle" "Kindle" "Scramble" Attribute [name "Poise"] "Charm" "Hunt" "Pass" Attribute [name "Insight"] "Cobble" "Connect" "Study" Grid [columns (Repeat [times 12] 1fr)] [rows 2] [column-gap 8pt] Header Column