@openpowershift/logic-diagram-language 0.1.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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +139 -0
  3. package/dist/examples.d.ts +2 -0
  4. package/dist/examples.js +469 -0
  5. package/dist/export-image.d.ts +10 -0
  6. package/dist/export-image.js +47 -0
  7. package/dist/index.d.ts +10 -0
  8. package/dist/index.html +13 -0
  9. package/dist/index.js +18 -0
  10. package/dist/parser/ast.d.ts +118 -0
  11. package/dist/parser/ast.js +79 -0
  12. package/dist/parser/index.d.ts +3 -0
  13. package/dist/parser/index.js +2 -0
  14. package/dist/parser/parser.d.ts +2 -0
  15. package/dist/parser/parser.js +635 -0
  16. package/dist/renderer/astar-router.d.ts +16 -0
  17. package/dist/renderer/astar-router.js +532 -0
  18. package/dist/renderer/gates.d.ts +19 -0
  19. package/dist/renderer/gates.js +92 -0
  20. package/dist/renderer/graph.d.ts +31 -0
  21. package/dist/renderer/graph.js +403 -0
  22. package/dist/renderer/layout.d.ts +76 -0
  23. package/dist/renderer/layout.js +3121 -0
  24. package/dist/renderer/math-renderer.d.ts +16 -0
  25. package/dist/renderer/math-renderer.js +119 -0
  26. package/dist/renderer/svg-renderer.d.ts +3 -0
  27. package/dist/renderer/svg-renderer.js +599 -0
  28. package/dist/renderer/wires.d.ts +5 -0
  29. package/dist/renderer/wires.js +12 -0
  30. package/dist/theme/themes.d.ts +58 -0
  31. package/dist/theme/themes.js +104 -0
  32. package/docs/api.adoc +196 -0
  33. package/docs/ldl-for-llms.md +163 -0
  34. package/docs/user-guide.adoc +318 -0
  35. package/package.json +78 -0
  36. package/spec/render.sh +22 -0
  37. package/spec/sections/attributes.adoc +80 -0
  38. package/spec/sections/connections.adoc +39 -0
  39. package/spec/sections/examples.adoc +212 -0
  40. package/spec/sections/expressions.adoc +182 -0
  41. package/spec/sections/file-extension.adoc +5 -0
  42. package/spec/sections/function-blocks.adoc +120 -0
  43. package/spec/sections/grammar.adoc +64 -0
  44. package/spec/sections/hyperlinks.adoc +18 -0
  45. package/spec/sections/introduction.adoc +16 -0
  46. package/spec/sections/layout-rules.adoc +491 -0
  47. package/spec/sections/lexical-conventions.adoc +68 -0
  48. package/spec/sections/objects.adoc +31 -0
  49. package/spec/sections/options.adoc +146 -0
  50. package/spec/sections/ports.adoc +77 -0
  51. package/spec/sections/rendering-contract.adoc +11 -0
  52. package/spec/sections/revision-history.adoc +9 -0
  53. package/spec/sections/styling.adoc +83 -0
  54. package/spec/sections/svg-symbol-specification.adoc +149 -0
  55. package/spec/sections/symbol-definitions.adoc +143 -0
  56. package/spec/sections/templates.adoc +31 -0
  57. package/spec/spec.adoc +49 -0
@@ -0,0 +1,212 @@
1
+ == Complete Examples
2
+
3
+ === Simple Logic
4
+
5
+ [source,ldl]
6
+ ----
7
+ // Protection logic for CBFPS
8
+ CBFPS = AB AND DC OR (NOT DC AND GF)
9
+
10
+ // Style overrides
11
+ STYLE
12
+ #G1 { stroke: red; }
13
+ END STYLE
14
+ ----
15
+
16
+ This expands to:
17
+
18
+ . A NOT gate (anonymous) with input `DC` and output to the right AND gate.
19
+ . An AND gate (`G1`) with inputs `AB`, `DC`.
20
+ . An AND gate (anonymous) with inputs `(NOT DC)`, `GF`.
21
+ . An OR gate (anonymous) combining the two AND gates' outputs.
22
+ . Final output `CBFPS`.
23
+
24
+ === Timer Object
25
+
26
+ [source,ldl]
27
+ ----
28
+ // Timer-based protection
29
+ SVT#1
30
+ SVT#1.INPUT = A OR B
31
+ SVT#1.PICKUP = 30 s
32
+ SVT#1.DROPOUT = 10 s
33
+
34
+ SVT#1.LINK = "https://docs.example.com/svt-timer"
35
+ ----
36
+
37
+ === Interlocking Example
38
+
39
+ A realistic protection interlocking for a collector feeder close operation:
40
+
41
+ [source,ldl]
42
+ ----
43
+ // Interlocking Example for Collector Feeder Q01 (Close)
44
+
45
+ I1.Name = "CBQ 00 Open"
46
+ I1.Description = "(BI 3.1)"
47
+
48
+ I2.Name = "BB Not Earthed"
49
+ I2.Description = "(BI 3.24)"
50
+
51
+ I3.Name = "D/S Q01 Open"
52
+ I3.Description = "(BI 3.3)"
53
+
54
+ I4.Name = "E/S Q05 Open"
55
+ I4.Description = "(BI 3.5)"
56
+
57
+ I5.Name = "KF1 Release"
58
+ I5.Description = "(BI 3.15)"
59
+
60
+ I6.Name = "In Remote"
61
+ I6.Description = "(BI 3.11)"
62
+
63
+ I7.Name = "SCADA ON"
64
+ I7.Description = "(BI 3.23)"
65
+
66
+ I8.Name = "DNP Close Command"
67
+ I8.Description = "(via RTU)"
68
+
69
+ I9.Name = "In Local"
70
+ I9.Description = "(BI 3.12)"
71
+
72
+ I10.Name = "Close Switch"
73
+ I10.Description = "(BI 3.20)"
74
+
75
+ O1 = (I1 AND I2) AND (I3 AND I4 AND NOT I5) AND ((I6 AND I7 AND I8) OR (I9 AND I10))
76
+
77
+ O1.Name = "Output"
78
+ O1.Description = "(BO 3.2)"
79
+ ----
80
+
81
+ This expands to:
82
+
83
+ . Ten input ports (`I1` through `I10`) with human-readable names and descriptions.
84
+ . A NOT gate for `NOT I5`.
85
+ . An AND gate combining `I3`, `I4`, and the output of `NOT I5`.
86
+ . An AND gate combining `I1` and `I2`.
87
+ . An AND gate combining `I6`, `I7`, and `I8`.
88
+ . An AND gate combining `I9` and `I10`.
89
+ . An OR gate combining the two sub-expressions.
90
+ . A final AND gate combining all three AND outputs.
91
+ . One output port `O1` with name "Output" and description "(BO 3.2)".
92
+
93
+ === Inversion Bubbles
94
+
95
+ Using `OPTION INVERSION = BUBBLES` renders NOT operations as inversion circles on the downstream gate's input port, eliminating the separate NOT gate column:
96
+
97
+ [source,ldl]
98
+ ----
99
+ OPTION INVERSION = BUBBLES
100
+
101
+ I1.Name = "Start Permitted"
102
+ I1.Description = "(BI 1.1)"
103
+ I2.Name = "Stop Condition"
104
+ I2.Description = "(BI 1.2)"
105
+ I3.Name = "Reset"
106
+ I3.Description = "(BI 1.3)"
107
+
108
+ O1 = I1 AND NOT I2 AND I3
109
+ O1.Name = "Run"
110
+ O1.Description = "(BO 1.1)"
111
+ ----
112
+
113
+ With `GATES` mode, this would render as: `I2 → [NOT] → [AND] ← I1, I3 → O1`.
114
+
115
+ With `BUBBLES` mode, the NOT gate is eliminated and a bubble (○) appears on the AND gate's input that receives `I2`: `I2 → ○[AND] ← I1, I3 → O1`.
116
+
117
+ === Input Bars
118
+
119
+ Using `OPTION GATE_INPUT_STYLE = BARS` keeps multi-input gates at their 2-input base size with vertical input bars for additional inputs:
120
+
121
+ [source,ldl]
122
+ ----
123
+ OPTION GATE_INPUT_STYLE = BARS
124
+
125
+ I1.Name = "CBQ 00 Open"
126
+ I2.Name = "BB Not Earthed"
127
+ I3.Name = "D/S Q01 Open"
128
+ I4.Name = "E/S Q05 Open"
129
+ I5.Name = "KF1 Release"
130
+
131
+ O1 = I1 AND I2 AND I3 AND I4 AND I5
132
+ O1.Name = "Output"
133
+ ----
134
+
135
+ With `EXPAND` mode, the AND gate expands vertically to accommodate five input ports. With `BARS` mode, the AND gate stays at 2-input height and inputs 3-5 connect via a vertical bar on the left side.
136
+
137
+ === Square Ports
138
+
139
+ Using `OPTION PORT_STYLE = SQUARE` renders all port markers as squares instead of circles:
140
+
141
+ [source,ldl]
142
+ ----
143
+ OPTION PORT_STYLE = SQUARE
144
+
145
+ A.Name = "Signal A"
146
+ B.Name = "Signal B"
147
+ O1 = A AND B
148
+ O1.Name = "Result"
149
+ ----
150
+
151
+ Individual ports can override the document default:
152
+
153
+ [source,ldl]
154
+ ----
155
+ OPTION PORT_STYLE = CIRCLE
156
+ I1.Style = SQUARE
157
+
158
+ O1 = I1 AND I2
159
+ ----
160
+
161
+ In this case, all ports are circles except `I1` which is a square.
162
+
163
+ === Combined Options
164
+
165
+ Options can be combined freely:
166
+
167
+ [source,ldl]
168
+ ----
169
+ OPTION INVERSION = BUBBLES
170
+ OPTION GATE_INPUT_STYLE = BARS
171
+ OPTION PORT_STYLE = SQUARE
172
+
173
+ I1.Name = "Start"
174
+ I2.Name = "Stop"
175
+ I3.Name = "Reset"
176
+ I4.Name = "Enable"
177
+ I5.Name = "Supervision"
178
+ I6.Name = "Override"
179
+
180
+ O1 = (I1 AND NOT I2) AND (I3 AND I4 AND I5 AND I6)
181
+ O1.Name = "Master Trip"
182
+ O1.Description = "(BO 1.1)"
183
+ ----
184
+
185
+ This renders with:
186
+ . Inversion bubbles instead of NOT gates (the `NOT I2` input shows as a bubble on the AND gate).
187
+ . Input bars on the 4-input AND gate (I3 through I6 connect via a vertical bar).
188
+ . Square port markers throughout.
189
+
190
+ === Full Diagram
191
+
192
+ [source,ldl]
193
+ ----
194
+ // Trip Logic Diagram
195
+ // File: trip_logic.ldl
196
+
197
+ IMPORT TEMPLATE "templates/timer.ldlt" AS T
198
+
199
+ SVT#1
200
+ SVT#1.INPUT = OVERCURRENT_A OR OVERCURRENT_B
201
+ SVT#1.PICKUP = 30 s
202
+ SVT#1.DROPOUT = 10 s
203
+
204
+ TRIP = SVT#1.OUTPUT AND MANUAL_TRIP
205
+
206
+ STYLESHEET "styles/protection.css"
207
+
208
+ STYLE
209
+ #1 { fill: #ffe0e0; }
210
+ SVT { stroke: #cc0000; }
211
+ END STYLE
212
+ ----
@@ -0,0 +1,182 @@
1
+ == Expressions
2
+
3
+ === Boolean Expressions
4
+
5
+ A boolean expression defines the logical relationship between inputs and an output.
6
+
7
+ [literal]
8
+ ....
9
+ OUTPUT = EXPR
10
+ ....
11
+
12
+ Where `EXPR` is composed of identifiers and the operators `AND`, `OR`, `NOT`, with parenthetical grouping.
13
+
14
+ === Intermediate Signals and Outputs
15
+
16
+ Every assignment names a signal. A name that is *referenced inside another assignment* is a
17
+ *consumed intermediate*: it is shared (its driving gate/block fans out to the consumers) and is
18
+ **not drawn as an output** on the right. Only names that are not consumed — the sinks, furthest
19
+ to the right — are drawn as outputs.
20
+
21
+ [literal]
22
+ ....
23
+ B = COMPARE(IA, IPICKUP) // consumed by A and others -> internal, not an output
24
+ A = SR(B, RESET) OR B OR C // consumed by TRIP -> internal, not an output
25
+ TRIP = TIMER(A, 0, 30cyc) // not consumed -> output
26
+ ....
27
+
28
+ To force a consumed intermediate to also be shown as an output, set `NAME.OUT = TRUE`:
29
+
30
+ [literal]
31
+ ....
32
+ B.OUT = TRUE // B is now drawn as an output as well as feeding its consumers
33
+ ....
34
+
35
+ A name that references *itself* (a feedback/seal-in, e.g. `Q = SET OR (Q AND NOT RESET)`) is
36
+ always drawn — the self-reference is a loop-back, not a consumer.
37
+
38
+ A consumed intermediate can still be *labelled at its fan-out junction* — a net label on the
39
+ shared signal — by giving it a name and/or description. The label is drawn just above the
40
+ driving gate/block's output, and wires route around it:
41
+
42
+ [literal]
43
+ ....
44
+ A = SR(B, RESET) OR B OR C
45
+ A.Name = "Trip Permit"
46
+ A.Description = "Seal-in" // A stays internal but its junction is labelled
47
+ ....
48
+
49
+ This is also how a *gate* is labelled: name the gate by assigning it to an intermediate, then
50
+ set the name/description — they appear at the gate's output and wires route around them. For a
51
+ gate that only drives an output, use a pass-through assignment so the gate stays internal:
52
+
53
+ [literal]
54
+ ....
55
+ PH = O51 OR O50 OR NEGSEQ // name the OR gate "PH"
56
+ PH.Name = "Phase OC"
57
+ PH.Description = "51/50/46"
58
+ TRIP = PH AND ENABLE // ... used here, so PH is internal and labelled
59
+
60
+ OC = E51N OR E50N // a gate that only drives an output:
61
+ TRIP2 = OC // pass-through keeps OC internal (labelled), TRIP2 is the output
62
+ OC.Name = "Earth Fault"
63
+ ....
64
+
65
+ === Operator Precedence
66
+
67
+ Operators are listed from highest to lowest precedence:
68
+
69
+ [cols="1m,4m,2m"]
70
+ |===
71
+ | Precedence | Operator | Arity
72
+ | 1 (highest) | `NOT` | Unary prefix
73
+ | 2 | `AND` | Binary infix
74
+ | 3 (lowest) | `OR` | Binary infix
75
+ |===
76
+
77
+ Parentheses override precedence.
78
+
79
+ === Examples
80
+
81
+ [literal]
82
+ ....
83
+ CBFPS = AB AND DC OR (NOT DC AND GF)
84
+ ....
85
+
86
+ Parses as:
87
+
88
+ [literal]
89
+ ....
90
+ CBFPS = (AB AND DC) OR ((NOT DC) AND GF)
91
+ ....
92
+
93
+ === Expanded Form
94
+
95
+ For readability, an expression may be written across multiple lines using continuation:
96
+
97
+ [literal]
98
+ ....
99
+ CBFPS = AB AND DC
100
+ OR (NOT DC AND GF)
101
+ ....
102
+
103
+ A continuation line is one that begins with whitespace and follows a prior expression line. The leading operator on a continuation line is treated as part of the expression.
104
+
105
+ === Derived Operators
106
+
107
+ The following compound operators are syntactic sugar:
108
+
109
+ [cols="2m,4m,4m"]
110
+ |===
111
+ | Operator | Expansion | Description
112
+ | `NAND` | `NOT (... AND ...)` | Negated AND
113
+ | `NOR` | `NOT (... OR ...)` | Negated OR
114
+ | `XOR` | Exclusive OR | One but not both
115
+ | `XNOR` | `NOT (... XOR ...)` | Equivalence
116
+ |===
117
+
118
+ === Inversion Rendering
119
+
120
+ When `OPTION INVERSION = BUBBLES` is set, all `NOT` operations in expressions are rendered as inversion bubbles rather than as separate NOT gate symbols.
121
+
122
+ ==== Input-Side Bubbles
123
+
124
+ When a `NOT` operation feeds into a gate input, the bubble appears on that input port:
125
+
126
+ [literal]
127
+ ....
128
+ O1 = I1 AND NOT I2
129
+ ....
130
+
131
+ With `OPTION INVERSION = GATES`: `I2 → [NOT] → [AND] ← I1 → O1`
132
+
133
+ With `OPTION INVERSION = BUBBLES`: `I2 → ○[AND] ← I1 → O1`
134
+
135
+ ==== Output-Side Bubbles
136
+
137
+ When a `NOT` operation wraps a gate's output, the bubble appears on that gate's output port:
138
+
139
+ [literal]
140
+ ....
141
+ O1 = NOT (I1 AND I2)
142
+ ....
143
+
144
+ With `OPTION INVERSION = GATES`: `[AND] → [NOT] → O1`
145
+
146
+ With `OPTION INVERSION = BUBBLES`: `[AND]○ → O1`
147
+
148
+ When a `NOT` operation feeds directly into an output, the bubble appears on the output port's input:
149
+
150
+ [literal]
151
+ ....
152
+ O1 = NOT I1
153
+ ....
154
+
155
+ With `OPTION INVERSION = GATES`: `I1 → [NOT] → O1`
156
+
157
+ With `OPTION INVERSION = BUBBLES`: `I1 → ○O1`
158
+
159
+ ==== Double-Inversion Cancellation
160
+
161
+ Consecutive `NOT` operations are algebraically simplified:
162
+
163
+ * `NOT NOT X = X` (even number of inversions cancels out; no bubble)
164
+ * `NOT NOT NOT X = NOT X` (odd number reduces to a single inversion; one bubble)
165
+
166
+ For example:
167
+
168
+ [literal]
169
+ ....
170
+ O1 = NOT NOT I1
171
+ ....
172
+
173
+ Renders as: `I1 → O1` (no bubble; both inversions cancel)
174
+
175
+ [literal]
176
+ ....
177
+ O1 = NOT NOT NOT I1
178
+ ....
179
+
180
+ Renders as: `I1 → ○O1` (triple inversion reduces to single inversion)
181
+
182
+ The NOT gate is never placed as a separate column when `OPTION INVERSION = BUBBLES`. Instead, the downstream port (input or output) is marked with a bubble, and the wire from the NOT's source connects directly to that bubbled port.
@@ -0,0 +1,5 @@
1
+ == File Extension
2
+
3
+ LDL source files use the extension `.ldl`.
4
+
5
+ Template files use the extension `.ldlt`.
@@ -0,0 +1,120 @@
1
+ == Function Blocks (SEL-style)
2
+
3
+ In addition to the boolean operators, an expression may use *function-block primitives*
4
+ modelled on SEL relay SELogic elements. Each is written as a function call and returns a
5
+ logic signal, so it composes with `AND`/`OR`/`NOT` and nests like any sub-expression.
6
+
7
+ [cols="2m,4m,5"]
8
+ |===
9
+ | Block | Syntax | Description
10
+
11
+ | Pickup/Dropout timer
12
+ | `TIMER(in, pu, do)`
13
+ | Output asserts `pu` after `in` rises and remains asserted `do` after `in` falls. Settings
14
+ may be positional (`TIMER(IN, 2cyc, 5cyc)`) or named (`TIMER(IN, PU=2cyc, DO=5cyc)`). A bare
15
+ number is read as cycles; `0` means no delay. Either delay may be omitted (defaults to 0).
16
+
17
+ | SR latch
18
+ | `SR(set, reset)`
19
+ | Set/reset latch. `.Q` (default) asserts on `set` and de-asserts on `reset`; `.NQ` is the
20
+ inverted output Q̄. **Reset-dominant** by default (when both inputs are asserted, the output
21
+ is reset); override with `DOMINANT=SET`. Each output port is drawn only when referenced.
22
+
23
+ | Rising-edge trigger
24
+ | `RISING(in)`
25
+ | A one-interval pulse on each 0→1 transition of `in` (SEL rising-edge / `R_TRIG`).
26
+
27
+ | Falling-edge trigger
28
+ | `FALLING(in)`
29
+ | A one-interval pulse on each 1→0 transition of `in`.
30
+
31
+ | Comparator
32
+ | `COMPARE(plus, minus)`
33
+ | Asserts when the `+` (first) input is greater than or equal to the `−` (second) input.
34
+ Drawn as a comparator triangle. Inputs are analog quantities; the output is logic.
35
+ |===
36
+
37
+ === Generic Blocks (FB)
38
+
39
+ `FB` is a *generic user block* — a square box with whatever inputs and outputs you need, for
40
+ representing a function whose internals are not drawn. Inputs come from the call arguments
41
+ (optionally labelled `NAME=expr`); outputs are the `.port` selectors you reference. The instance
42
+ `name` is centred in the box, the `description` below it.
43
+
44
+ [literal]
45
+ ....
46
+ TRIP = FB#PROT(PHASE=IA, EARTH=IN, EN=ENABLE).TRIP
47
+ ALARM = FB#PROT(PHASE=IA, EARTH=IN, EN=ENABLE).ALARM
48
+ PROT.Name = "Feeder Protection"
49
+ PROT.Description = "SEL-751A"
50
+ ....
51
+
52
+ * Each argument is an input port (left side). `NAME=expr` labels that port; a bare argument is
53
+ an unlabelled input.
54
+ * Each distinct `.port` referenced is an output port (right side), labelled with the port name.
55
+ The block instance is de-duplicated by `#id`, so the example above is one box with two outputs.
56
+ * The box is sized to its name, port labels and port counts.
57
+
58
+ === Instance IDs and Settings
59
+
60
+ A block may be given an explicit *instance id* as part of its name, written `BLOCK#id`
61
+ immediately before the argument list (matching the `SYMBOL_NAME#ID` convention, see
62
+ <<symbol-definitions>>). The id can then carry a name, description, or other settings via
63
+ `id.Property`:
64
+
65
+ [literal]
66
+ ....
67
+ HOT = COMPARE#C1(IA, IPK)
68
+ C1.Name = "50P1"
69
+ C1.Description = "Phase overcurrent element"
70
+ ....
71
+
72
+ When no id is given, the block is anonymous and the assigned output name (the left-hand side)
73
+ is its identity.
74
+
75
+ === Output Port Selection
76
+
77
+ A block's output is selected with a trailing `.port` after the call. Most blocks have a single
78
+ output, used by default. The SR latch has two: `.Q` (the default) and `.NQ` (the inverted
79
+ output Q̄). Selecting `.NQ` yields only Q̄, and **only the referenced output port is drawn**:
80
+
81
+ [literal]
82
+ ....
83
+ PERMIT = SR#L1(START, STOP) // uses Q (default); only Q is drawn
84
+ BLOCK = SR#L1(START, STOP).NQ // uses Q̄ only; only NQ is drawn
85
+ ....
86
+
87
+ If both `.Q` and `.NQ` of the same instance are referenced, both ports are drawn.
88
+
89
+ === Composition and Feedback
90
+
91
+ Blocks nest and chain freely:
92
+
93
+ [literal]
94
+ ....
95
+ LATCH = SR(RISING(START), STOP)
96
+ RUN = TIMER(A AND B, 0, 10cyc) AND ENABLE
97
+ ....
98
+
99
+ A latch output fed back into its own set/reset logic (the canonical seal-in) is drawn as a
100
+ loop-back wire (see <<feedback-loops>>).
101
+
102
+ A previously-defined output used as a *block argument* is substituted with its driving signal —
103
+ the block taps the same source (a fan-out) rather than gaining a duplicate input. This lets a
104
+ named intermediate be threaded into a block:
105
+
106
+ [literal]
107
+ ....
108
+ A = SR(COMPARE(IA, IPICKUP), RESET)
109
+ TRIP = TIMER(A, 0, 30cyc) // the SR latch feeds the timer; A is not duplicated
110
+ ....
111
+
112
+ === Rendering (SEL Style)
113
+
114
+ * *TIMER* — a rectangle with a diagonal ramp from the bottom-left to the top-right corner; the
115
+ pickup (`PU`) delay is shown in the upper-left, the dropout (`DO`) delay in the lower-right.
116
+ * *SR* — a rectangle with `S` (top-left) and `R` (bottom-left) input ports and `Q` on the
117
+ right; the inverted output `Q̄` is added at the lower right only when referenced.
118
+ * *RISING* / *FALLING* — a small block bearing a rising/falling step glyph.
119
+ * *COMPARE* — a comparator triangle with `+` and `−` input ports on the left and the output
120
+ on the right.
@@ -0,0 +1,64 @@
1
+ == Grammar
2
+
3
+ The following is the formal grammar for LDL in EBNF notation:
4
+
5
+ [source,ebnf]
6
+ ----
7
+ diagram ::= ( statement )* EOF ;
8
+
9
+ statement ::= object_decl
10
+ | port_meta
11
+ | attribute_decl
12
+ | option_decl
13
+ | link_decl
14
+ | style_block
15
+ | stylesheet_decl
16
+ | import_decl
17
+ | symbol_def
18
+ | expression
19
+ | connect_stmt
20
+ | comment
21
+ ;
22
+
23
+ object_decl ::= SYMBOL_NAME ( '#' IDENTIFIER )? ;
24
+ port_meta ::= ( IDENTIFIER | SYMBOL_NAME ) '.' ( 'Name' | 'Description' | 'Style' ) '=' STRING ;
25
+ attribute_decl ::= OBJECT_REF '.' IDENTIFIER '=' literal ;
26
+ option_decl ::= 'OPTION' IDENTIFIER '=' IDENTIFIER ;
27
+ link_decl ::= OBJECT_REF ( '.' PORT_NAME )? '.LINK' '=' STRING ;
28
+
29
+ OBJECT_REF ::= SYMBOL_NAME ( '#' IDENTIFIER )? ;
30
+
31
+ expression ::= or_expr ;
32
+ or_expr ::= and_expr ( 'OR' and_expr )* ;
33
+ and_expr ::= not_expr ( 'AND' not_expr )* ;
34
+ not_expr ::= 'NOT' not_expr | primary ;
35
+ primary ::= '(' expression ')' | IDENTIFIER | OBJECT_REF '.' PORT_NAME ;
36
+
37
+ connect_stmt ::= 'CONNECT' port_ref port_ref ;
38
+ port_ref ::= OBJECT_REF '.' PORT_NAME ;
39
+
40
+ style_block ::= 'STYLE' ( CSS_CONTENT ) 'END' 'STYLE' ;
41
+ stylesheet_decl::= 'STYLESHEET' STRING ;
42
+
43
+ import_decl ::= 'IMPORT' 'TEMPLATE' STRING ( 'AS' IDENTIFIER )? ;
44
+
45
+ symbol_def ::= 'SYMBOL' SYMBOL_NAME
46
+ ( 'SVG' '=' STRING )?
47
+ ( port_decl )*
48
+ ( attr_decl_in_symbol )*
49
+ ( 'LOGIC' '=' expression )?
50
+ 'END' 'SYMBOL' ;
51
+ port_decl ::= 'PORT' ( 'INPUT' | 'OUTPUT' | 'BIDI' ) IDENTIFIER
52
+ ( ':' ( 'LEFT' | 'RIGHT' | 'TOP' | 'BOTTOM' ) )? ;
53
+ attr_decl_in_symbol ::= 'ATTRIBUTE' IDENTIFIER '=' literal ;
54
+
55
+ literal ::= INTEGER | FLOAT | STRING | DURATION | BOOL ;
56
+ DURATION ::= NUMBER ( 'ms' | 's' | 'm' ) ;
57
+ IDENTIFIER ::= [a-zA-Z_][a-zA-Z0-9_]* ;
58
+ SYMBOL_NAME ::= [A-Z][A-Z0-9_]* ;
59
+ STRING ::= '"' [^"]* '"' ;
60
+
61
+ comment ::= line_comment | block_comment ;
62
+ line_comment ::= '//' ~NEWLINE ;
63
+ block_comment ::= '/*' ( block_comment | ~('*/') )* '*/' ;
64
+ ----
@@ -0,0 +1,18 @@
1
+ == Hyperlinks
2
+
3
+ Any object or port may carry a hyperlink:
4
+
5
+ [literal]
6
+ ....
7
+ OBJECT#ID.LINK = "https://example.com/doc"
8
+ PORT OBJECT#ID.PORT_NAME.LINK = "https://example.com/signal"
9
+ ....
10
+
11
+ In the rendered SVG, the symbol or port is wrapped in an `<a>` element:
12
+
13
+ [source,xml]
14
+ ----
15
+ <a xlink:href="https://example.com/doc" target="_blank">
16
+ <g class="ldl-symbol" id="G1">...</g>
17
+ </a>
18
+ ----
@@ -0,0 +1,16 @@
1
+ == Introduction
2
+
3
+ The Logic Diagram Language (LDL) is a domain-specific language for describing logic diagrams composed of boolean gates, composite symbols, and their interconnections. LDL targets rendering to SVG and PDF via a TypeScript toolchain.
4
+
5
+ The language has two primary concerns:
6
+
7
+ . **Expression logic** -- declaring boolean relationships between inputs and outputs.
8
+ . **Symbol definition** -- describing reusable SVG-based components with typed ports and configurable attributes.
9
+
10
+ === Design Principles
11
+
12
+ * *Expression-first*: A simple boolean expression should produce a complete diagram.
13
+ * *Symbol-oriented*: Every rendered element is an SVG symbol; gates are just symbols with predefined logic.
14
+ * *Declarative*: The language describes *what*, not *how* -- routing and layout are the renderer's concern.
15
+ * *Extensible*: New symbols can be defined or imported without changing the language.
16
+ * *Stylable*: Every element can carry an identifier for CSS-based styling.