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