lispgram 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,568 @@
1
+ Here’s a clean **Lispgram surface-language grammar spec** for the user-friendly lispy language that compiles to NCF.
2
+
3
+ ## Lispgram grammar
4
+
5
+ Lispgram is a small lispy language with three main surface forms:
6
+
7
+ * **tree forms** for nodes and parent/child structure
8
+ * **arrow forms** for arrows between nodes
9
+ * **layout forms** for non-semantic renderer intent
10
+
11
+ A document may be either:
12
+
13
+ * a sequence of forms, or
14
+ * wrapped in a top-level `(lispgram ...)`
15
+
16
+ Both shapes are accepted by `parseDocument(source)` in `src/layers/01-surface/parse.js`.
17
+
18
+ Important: label/field braces are a **suffix on the same token** as the id.
19
+
20
+ - ✅ `(somenode{"hi"})`
21
+ - ❌ `(somenode {"hi"})`
22
+
23
+ Whitespace between the id and `{...}` splits them into separate forms/tokens and is not valid attributed-ref syntax.
24
+
25
+ ## EBNF
26
+
27
+ ```ebnf
28
+ document = { ws_or_comment , form } , ws_or_comment
29
+ | "(" , "lispgram" , { ws_or_comment , form } , ws_or_comment , ")" ;
30
+
31
+ form = tree_form | arrow_form | layout_form ;
32
+
33
+ tree_form = "(" , ws ,
34
+ node_ref ,
35
+ { ws , tree_item } ,
36
+ ws , ")" ;
37
+
38
+ tree_item = node_ref | tree_form ;
39
+
40
+ arrow_form = "(" , ws ,
41
+ "->" , ws ,
42
+ edge_ref , ws ,
43
+ endpoint , ws ,
44
+ endpoint ,
45
+ ws , ")" ;
46
+
47
+ endpoint = node_ref | vector ;
48
+
49
+ layout_form = "(" , ws , ("$layout" | "$metalayout") ,
50
+ { ws , s_expression } ,
51
+ ws , ")" ;
52
+
53
+ vector = "[" , ws ,
54
+ node_ref ,
55
+ { ws , node_ref } ,
56
+ ws , "]" ;
57
+
58
+ node_ref = atom_ref | string_ref | attributed_ref ;
59
+ edge_ref = atom_ref | string_ref | attributed_ref ;
60
+
61
+ attributed_ref
62
+ = atom_ref , "{" , label_text , "}"
63
+ | atom_ref , "{" , label_text , field_map , "}"
64
+ | atom_ref , "{" , field_map , "}" ;
65
+ (* no whitespace is allowed between atom_ref and the opening "{" *)
66
+
67
+ field_map = "{" , field_pair , { ws , field_pair } , "}" ;
68
+ field_pair = field_key , ws , field_value ;
69
+ field_key = field_value ;
70
+ field_value = quoted_string | number | boolean | atom_ref ;
71
+
72
+ label_text = quoted_string | bare_label ;
73
+
74
+ atom_ref = atom_token ;
75
+ string_ref = quoted_string ;
76
+ atom_token = atom_char , { atom_char } ;
77
+ atom_char = any_char_except_ws_or_paren_or_bracket_or_brace ;
78
+
79
+ bare_label = { any_char_except_brace_or_newline } ;
80
+ number = integer | float ;
81
+ boolean = "true" | "false" ;
82
+
83
+ quoted_string = '"' , { string_char } , '"' ;
84
+
85
+ string_char = any_char_except_quote_or_backslash
86
+ | "\" , '"'
87
+ | "\" , "\"
88
+ | "\" , "n"
89
+ | "\" , "r"
90
+ | "\" , "t" ;
91
+
92
+ ws = { " " | "\t" | "\r" | "\n" } ;
93
+ comment = ";" , { any_char_except_newline } ;
94
+ ws_or_comment = ws | comment ;
95
+ ```
96
+
97
+
98
+ ---
99
+
100
+ ### Layout metadata
101
+
102
+ ```lispgram
103
+ ($layout
104
+ (same-row [A B])
105
+ (view product :product P :factors [A B] :test-object X)
106
+ (attach-at-boundary h :from Ω :to P :boundary C :side left)
107
+ (parallel-to-boundary q :from M :to N :boundary C :side left))
108
+ ```
109
+
110
+ Self-loops are not authored as layout directives. Declare an ordinary arrow whose source and target are the same, e.g. `(-> loopP P P)`, and the route layer will autodetect a loop.
111
+
112
+ `$layout` and `$metalayout` are surface metadata. They are preserved through NCF as top-level `(layout ...)` forms, are intentionally omitted from the official semantic core, and lower into the constraint-layout engine when rendered.
113
+
114
+ `$metalayout` remains accepted for older arrow-like layout clauses such as `(-> $samerow [A B C])`; new work should prefer `$layout` relative constraints and template views.
115
+
116
+ ## Meaning of each form
117
+
118
+ ### 1. Node declaration
119
+
120
+ ```lispgram
121
+ (P)
122
+ ```
123
+
124
+ Ensures node `P` exists.
125
+
126
+ Default label is the node id, so this behaves like:
127
+
128
+ * id = `P`
129
+ * label = `P`
130
+
131
+ Example:
132
+
133
+ ```lispgram
134
+ (A)
135
+ (B)
136
+ ```
137
+
138
+ Creates two nodes: `A`, `B`.
139
+
140
+ ---
141
+
142
+ ### 2. Node with label
143
+
144
+ ```lispgram
145
+ (P{hi})
146
+ ```
147
+
148
+ Ensures node `P` exists with label `hi`.
149
+
150
+ Example:
151
+
152
+ ```lispgram
153
+ (P{Person})
154
+ (Q{"Hello world"})
155
+ ```
156
+
157
+ Creates:
158
+
159
+ * node `P` labeled `Person`
160
+ * node `Q` labeled `Hello world`
161
+
162
+ Use quotes when the label contains spaces.
163
+
164
+ ---
165
+
166
+ ### 3. Parent with children
167
+
168
+ ```lispgram
169
+ (X Y Z)
170
+ ```
171
+
172
+ Means:
173
+
174
+ * `X` exists
175
+ * `Y` exists and is a child of `X`
176
+ * `Z` exists and is a child of `X`
177
+
178
+ Example:
179
+
180
+ ```lispgram
181
+ (Folder File1 File2)
182
+ ```
183
+
184
+ `Folder` is the container, `File1` and `File2` are inside it.
185
+
186
+ ---
187
+
188
+ ### 4. Nested parent structure
189
+
190
+ ```lispgram
191
+ (X Y (Z P))
192
+ ```
193
+
194
+ Means:
195
+
196
+ * `Y` is a child of `X`
197
+ * `Z` is a child of `X`
198
+ * `P` is a child of `Z`
199
+
200
+ Example:
201
+
202
+ ```lispgram
203
+ (App Header (Body Sidebar))
204
+ ```
205
+
206
+ This gives:
207
+
208
+ * `Header` inside `App`
209
+ * `Body` inside `App`
210
+ * `Sidebar` inside `Body`
211
+
212
+ ---
213
+
214
+ ### 5. Chained nesting
215
+
216
+ ```lispgram
217
+ (X (Y (Z P)))
218
+ ```
219
+
220
+ Means:
221
+
222
+ * `Y` is a child of `X`
223
+ * `Z` is a child of `Y`
224
+ * `P` is a child of `Z`
225
+
226
+ Example:
227
+
228
+ ```lispgram
229
+ (World (Continent (Country City)))
230
+ ```
231
+
232
+ This creates a straight nesting chain.
233
+
234
+ ---
235
+
236
+ ### 6. Arrow between two nodes
237
+
238
+ ```lispgram
239
+ (-> e A B)
240
+ ```
241
+
242
+ Creates arrow `e` from `A` to `B`.
243
+
244
+ If `A` or `B` do not already exist, they are created automatically.
245
+
246
+ Example:
247
+
248
+ ```lispgram
249
+ (-> f Login Dashboard)
250
+ ```
251
+
252
+ Creates:
253
+
254
+ * node `Login`
255
+ * node `Dashboard`
256
+ * arrow `f` from `Login` to `Dashboard`
257
+
258
+ ---
259
+
260
+ ### 7. Arrow with label
261
+
262
+ ```lispgram
263
+ (-> e{hi} A B)
264
+ ```
265
+
266
+ Creates arrow `e` from `A` to `B` with label `hi`.
267
+
268
+ Example:
269
+
270
+ ```lispgram
271
+ (-> auth{"sign in"} User Session)
272
+ ```
273
+
274
+ Creates arrow `auth` labeled `sign in`.
275
+
276
+ ---
277
+
278
+ ### 8. Fan-out arrow
279
+
280
+ ```lispgram
281
+ (-> e A [B C D])
282
+ ```
283
+
284
+ Means one source, many targets.
285
+
286
+ This expands to multiple concrete arrows. Each concrete arrow keeps the visible label `e`; generated carrier ids are compiler-private NCF details, not user-facing labels:
287
+
288
+ * `e` from `A` to `B`
289
+ * `e` from `A` to `C`
290
+ * `e` from `A` to `D`
291
+
292
+ Example:
293
+
294
+ ```lispgram
295
+ (-> send Server [Client1 Client2 Client3])
296
+ ```
297
+
298
+ ---
299
+
300
+ ### 9. Fan-in arrow
301
+
302
+ ```lispgram
303
+ (-> e [A B C] D)
304
+ ```
305
+
306
+ Means many sources, one target.
307
+
308
+ This expands to multiple concrete arrows. Each concrete arrow keeps the visible label `e`:
309
+
310
+ * `e` from `A` to `D`
311
+ * `e` from `B` to `D`
312
+ * `e` from `C` to `D`
313
+
314
+ Example:
315
+
316
+ ```lispgram
317
+ (-> collect [Sensor1 Sensor2 Sensor3] Database)
318
+ ```
319
+
320
+ ---
321
+
322
+ ## Full examples
323
+
324
+ ### Minimal document
325
+
326
+ ```lispgram
327
+ (A)
328
+ (B)
329
+ (-> f A B)
330
+ ```
331
+
332
+ ### With grouping
333
+
334
+ ```lispgram
335
+ (App Header Footer)
336
+ (App (Main Sidebar))
337
+ (-> nav Header Main)
338
+ (-> info Sidebar Footer)
339
+ ```
340
+
341
+ ### With top-level wrapper
342
+
343
+ ```lispgram
344
+ (lispgram
345
+ (A{Start})
346
+ (B{End})
347
+ (-> flow A B))
348
+ ```
349
+
350
+ ### Mixed hierarchy and arrows
351
+
352
+ ```lispgram
353
+ (lispgram
354
+ (System
355
+ API
356
+ (UI Button Panel)
357
+ (DB Table))
358
+
359
+ (-> req UI API)
360
+ (-> read API DB)
361
+ (-> write API DB)
362
+ (-> click Button API))
363
+ ```
364
+
365
+ ### Fan-out and fan-in
366
+
367
+ ```lispgram
368
+ (lispgram
369
+ (Hub)
370
+ (A)
371
+ (B)
372
+ (C)
373
+ (D)
374
+
375
+ (-> out Hub [A B C])
376
+ (-> in [A B C] D))
377
+ ```
378
+
379
+ ---
380
+
381
+ ## Semantic rules
382
+
383
+ These are the intended compile-time rules.
384
+
385
+ ### Auto-creation
386
+
387
+ Any node mentioned in a tree or arrow form is created if it does not already exist.
388
+
389
+ Example:
390
+
391
+ ```lispgram
392
+ (-> f X Y)
393
+ ```
394
+
395
+ Creates `X` and `Y` automatically.
396
+
397
+ ### Labels and fields
398
+
399
+ A node or arrow may be given a label with `{...}`. The label is stored as `data.label`.
400
+
401
+ Example:
402
+
403
+ ```lispgram
404
+ (A{Alpha})
405
+ (-> e{"goes to"} A B)
406
+ ```
407
+
408
+ A node or arrow may also carry arbitrary data fields. The compact form puts the label first, followed by a field map:
409
+
410
+ ```lispgram
411
+ (A{Person { color blue rank 2 active true }})
412
+ (-> e{maps { color purple weight 2 }} A B)
413
+ ```
414
+
415
+ The fully explicit form is a field map only. This is equivalent to the compact node example above:
416
+
417
+ ```lispgram
418
+ (A{{ label Person color blue rank 2 active true }})
419
+ ```
420
+
421
+ Bare atom values are strings, numbers parse as numbers, and `true` / `false` parse as booleans. Quoted strings may be used when values contain spaces:
422
+
423
+ ```lispgram
424
+ (A{{ label "Person Account" color "deep blue" rank 2 }})
425
+ ```
426
+
427
+ If no label is provided, the id is used as the default label. A later explicit label overrides that earlier implicit id-based label. Labels are display-only; ids are the referenceable names.
428
+
429
+ Example:
430
+
431
+ ```lispgram
432
+ (lispgram
433
+ (-> e A B)
434
+ (A{hi}))
435
+ ```
436
+
437
+ This gives node `A` the label `hi`, not `A`.
438
+
439
+ This is rejected because the shorthand label conflicts with the explicit `label` field:
440
+
441
+ ```lispgram
442
+ (A{Person { label Account }})
443
+ ```
444
+
445
+ ### Unique ids and arrow groups
446
+
447
+ Node ids must be unique among nodes.
448
+
449
+ Arrow ids are referenceable names. Reusing an arrow id creates an implicit arrow group: each occurrence is a distinct concrete arrow with the same visible label. Referencing that repeated id as an endpoint fans over the whole group.
450
+
451
+ ```lispgram
452
+ (-> e A B)
453
+ (-> e C D)
454
+ (-> meta e X)
455
+ ```
456
+
457
+ The `meta` arrow above expands over both concrete `e` arrows.
458
+
459
+ If you need finer-grained control while keeping the same visible label, use distinct ids with explicit labels:
460
+
461
+ ```lispgram
462
+ (-> e1{e} A B)
463
+ (-> e2{e} C D)
464
+ (-> meta e1 X)
465
+ ```
466
+
467
+ Here `e1` and `e2` both display as `e`, but `meta` refers only to `e1`.
468
+
469
+ ### Parenting
470
+
471
+ A node may have at most one parent.
472
+
473
+ This is valid:
474
+
475
+ ```lispgram
476
+ (X Y)
477
+ ```
478
+
479
+ This should be rejected:
480
+
481
+ ```lispgram
482
+ (X Y)
483
+ (Z Y)
484
+ ```
485
+
486
+ because `Y` would have two parents.
487
+
488
+ ### Repeated references
489
+
490
+ Referring to the same node multiple times is allowed.
491
+
492
+ Example:
493
+
494
+ ```lispgram
495
+ (A)
496
+ (-> f A B)
497
+ (-> g B A)
498
+ ```
499
+
500
+ ### Conflicting labels and fields
501
+
502
+ This should be treated as an error:
503
+
504
+ ```lispgram
505
+ (A{One})
506
+ (A{Two})
507
+ ```
508
+
509
+ because node `A` gets two different explicit labels. An explicit label may override an earlier implicit id-based label, but two different explicit labels still conflict. The same rule applies to explicit non-label fields, such as `color` or `rank`.
510
+
511
+ ---
512
+
513
+ ## Lexical notes
514
+
515
+ ### Comments
516
+
517
+ A semicolon starts a comment to the end of the line.
518
+
519
+ ```lispgram
520
+ ; this is a comment
521
+ (A)
522
+ (-> f A B) ; inline comment
523
+ ```
524
+
525
+ ### Whitespace
526
+
527
+ Whitespace is ignored except as a separator.
528
+
529
+ So these are equivalent:
530
+
531
+ ```lispgram
532
+ (A B C)
533
+ ```
534
+
535
+ ```lispgram
536
+ (
537
+ A
538
+ B
539
+ C
540
+ )
541
+ ```
542
+
543
+ ### Reserved tokens
544
+
545
+ `->` is reserved only in list-head position where it denotes an arrow form.
546
+
547
+ `lispgram` is special only as an optional top-level wrapper head. As a plain atom elsewhere, it is parsed like any other identifier.
548
+
549
+ ### Labels with spaces
550
+
551
+ Prefer quoted labels when they contain spaces:
552
+
553
+ ```lispgram
554
+ (Node{"Hello world"})
555
+ (-> e{"edge label"} A B)
556
+ ```
557
+
558
+ ---
559
+
560
+ ## Practical summary
561
+
562
+ The language has only four ideas:
563
+
564
+ * `(A)` → create a node
565
+ * `(A B C)` → make `B` and `C` children of `A`
566
+ * `(-> e A B)` → create an arrow from `A` to `B`
567
+ * `[A B C]` inside an arrow → fan-in or fan-out
568
+