@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.
- package/LICENSE +21 -0
- package/README.md +139 -0
- package/dist/examples.d.ts +2 -0
- package/dist/examples.js +469 -0
- package/dist/export-image.d.ts +10 -0
- package/dist/export-image.js +47 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.html +13 -0
- package/dist/index.js +18 -0
- package/dist/parser/ast.d.ts +118 -0
- package/dist/parser/ast.js +79 -0
- package/dist/parser/index.d.ts +3 -0
- package/dist/parser/index.js +2 -0
- package/dist/parser/parser.d.ts +2 -0
- package/dist/parser/parser.js +635 -0
- package/dist/renderer/astar-router.d.ts +16 -0
- package/dist/renderer/astar-router.js +532 -0
- package/dist/renderer/gates.d.ts +19 -0
- package/dist/renderer/gates.js +92 -0
- package/dist/renderer/graph.d.ts +31 -0
- package/dist/renderer/graph.js +403 -0
- package/dist/renderer/layout.d.ts +76 -0
- package/dist/renderer/layout.js +3121 -0
- package/dist/renderer/math-renderer.d.ts +16 -0
- package/dist/renderer/math-renderer.js +119 -0
- package/dist/renderer/svg-renderer.d.ts +3 -0
- package/dist/renderer/svg-renderer.js +599 -0
- package/dist/renderer/wires.d.ts +5 -0
- package/dist/renderer/wires.js +12 -0
- package/dist/theme/themes.d.ts +58 -0
- package/dist/theme/themes.js +104 -0
- package/docs/api.adoc +196 -0
- package/docs/ldl-for-llms.md +163 -0
- package/docs/user-guide.adoc +318 -0
- package/package.json +78 -0
- package/spec/render.sh +22 -0
- package/spec/sections/attributes.adoc +80 -0
- package/spec/sections/connections.adoc +39 -0
- package/spec/sections/examples.adoc +212 -0
- package/spec/sections/expressions.adoc +182 -0
- package/spec/sections/file-extension.adoc +5 -0
- package/spec/sections/function-blocks.adoc +120 -0
- package/spec/sections/grammar.adoc +64 -0
- package/spec/sections/hyperlinks.adoc +18 -0
- package/spec/sections/introduction.adoc +16 -0
- package/spec/sections/layout-rules.adoc +491 -0
- package/spec/sections/lexical-conventions.adoc +68 -0
- package/spec/sections/objects.adoc +31 -0
- package/spec/sections/options.adoc +146 -0
- package/spec/sections/ports.adoc +77 -0
- package/spec/sections/rendering-contract.adoc +11 -0
- package/spec/sections/revision-history.adoc +9 -0
- package/spec/sections/styling.adoc +83 -0
- package/spec/sections/svg-symbol-specification.adoc +149 -0
- package/spec/sections/symbol-definitions.adoc +143 -0
- package/spec/sections/templates.adoc +31 -0
- 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,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.
|