@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,318 @@
|
|
|
1
|
+
= LDL User Guide
|
|
2
|
+
:toc: macro
|
|
3
|
+
:toclevels: 3
|
|
4
|
+
:sectnums:
|
|
5
|
+
:sectlinks:
|
|
6
|
+
|
|
7
|
+
_A practical guide to writing Logic Diagram Language (LDL) source. It covers the
|
|
8
|
+
language as implemented by this renderer. For the full formal grammar and the
|
|
9
|
+
rendering contract, see the link:../spec/spec.adoc[LDL Specification]._
|
|
10
|
+
|
|
11
|
+
toc::[]
|
|
12
|
+
|
|
13
|
+
== Introduction
|
|
14
|
+
|
|
15
|
+
LDL is a small text language for describing logic/protection schematics. You write
|
|
16
|
+
boolean equations; the renderer draws the gates, routes the wires, and lays the
|
|
17
|
+
diagram out automatically. There are no coordinates to place by hand — the layout
|
|
18
|
+
engine positions everything and minimises wire crossings for you.
|
|
19
|
+
|
|
20
|
+
The quickest way to try it is the in-browser playground (`npm run dev`): type LDL on
|
|
21
|
+
the left, see the SVG update live on the right. Pick any built-in example from the
|
|
22
|
+
dropdown to see a feature in action — every construct below appears in one.
|
|
23
|
+
|
|
24
|
+
A minimal document is a single assignment:
|
|
25
|
+
|
|
26
|
+
[source]
|
|
27
|
+
----
|
|
28
|
+
OUT = A AND B
|
|
29
|
+
----
|
|
30
|
+
|
|
31
|
+
This draws two boundary inputs `A` and `B`, an AND gate, and a boundary output `OUT`.
|
|
32
|
+
|
|
33
|
+
== Expressions
|
|
34
|
+
|
|
35
|
+
An assignment `NAME = EXPRESSION` is the core statement. The left-hand side names a
|
|
36
|
+
signal; the right-hand side is a boolean expression built from **identifiers** and the
|
|
37
|
+
operators `AND`, `OR`, and `NOT`.
|
|
38
|
+
|
|
39
|
+
[source]
|
|
40
|
+
----
|
|
41
|
+
TRIP = OVERCURRENT AND NOT BLOCK
|
|
42
|
+
----
|
|
43
|
+
|
|
44
|
+
=== Operator precedence
|
|
45
|
+
|
|
46
|
+
From highest to lowest: `NOT` (unary), then `AND`, then `OR`. Use parentheses to
|
|
47
|
+
override precedence.
|
|
48
|
+
|
|
49
|
+
[source]
|
|
50
|
+
----
|
|
51
|
+
CBFPS = AB AND DC OR (NOT DC AND GF)
|
|
52
|
+
// parses as: (AB AND DC) OR ((NOT DC) AND GF)
|
|
53
|
+
----
|
|
54
|
+
|
|
55
|
+
Only `AND`, `OR` and `NOT` are available as operators. A NAND / NOR is written with an
|
|
56
|
+
explicit `NOT` around the group:
|
|
57
|
+
|
|
58
|
+
[source]
|
|
59
|
+
----
|
|
60
|
+
NAND_RESULT = NOT (A AND B)
|
|
61
|
+
NOR_RESULT = NOT (A OR B)
|
|
62
|
+
----
|
|
63
|
+
|
|
64
|
+
=== Multi-line expressions
|
|
65
|
+
|
|
66
|
+
A long expression may be broken across lines. A continuation line simply begins with
|
|
67
|
+
whitespace; the leading operator joins it to the expression above.
|
|
68
|
+
|
|
69
|
+
[source]
|
|
70
|
+
----
|
|
71
|
+
TRIP = PHASE AND ENABLE
|
|
72
|
+
OR (EARTH AND NOT BLOCK)
|
|
73
|
+
OR MANUAL
|
|
74
|
+
----
|
|
75
|
+
|
|
76
|
+
=== Nested / double negation
|
|
77
|
+
|
|
78
|
+
`NOT NOT X` cancels to `X`. An odd number of `NOT` s reduces to a single inversion.
|
|
79
|
+
|
|
80
|
+
== Inputs, outputs and intermediate signals
|
|
81
|
+
|
|
82
|
+
Any bare identifier that is never assigned becomes a **boundary input** on the left.
|
|
83
|
+
Any assigned name that is *not referenced anywhere else* becomes a **boundary output**
|
|
84
|
+
on the right.
|
|
85
|
+
|
|
86
|
+
A name that *is* referenced inside another assignment is a **consumed intermediate**:
|
|
87
|
+
its driving gate fans out to the consumers, and it is **not** drawn as an output.
|
|
88
|
+
|
|
89
|
+
[source]
|
|
90
|
+
----
|
|
91
|
+
TRIP = OVERCURRENT OR NOT EARTH_FAULT // consumed below -> internal, not an output
|
|
92
|
+
MAIN_TRIP = TRIP AND (MANUAL OR REMOTE) // not consumed -> drawn as an output
|
|
93
|
+
----
|
|
94
|
+
|
|
95
|
+
=== Forcing an intermediate to an output
|
|
96
|
+
|
|
97
|
+
To draw a consumed intermediate as an output *as well* as feeding its consumers, set
|
|
98
|
+
`.OUT = TRUE`:
|
|
99
|
+
|
|
100
|
+
[source]
|
|
101
|
+
----
|
|
102
|
+
B.OUT = TRUE // B now appears as an output and still fans out to its consumers
|
|
103
|
+
----
|
|
104
|
+
|
|
105
|
+
=== Feedback / seal-in
|
|
106
|
+
|
|
107
|
+
A name that references *itself* is a feedback loop (a seal-in), not a consumer, so it is
|
|
108
|
+
always drawn. The renderer routes the self-reference as a loop-back wire.
|
|
109
|
+
|
|
110
|
+
[source]
|
|
111
|
+
----
|
|
112
|
+
BFT = BFI OR BFT AND ((CB52A AND CB52ABFY) OR (I1I2 AND INOM))
|
|
113
|
+
----
|
|
114
|
+
|
|
115
|
+
== Labels
|
|
116
|
+
|
|
117
|
+
Every input, output and named gate can carry a `.Name` (primary label) and a
|
|
118
|
+
`.Description` (smaller secondary line). When the *labels* layer is on, the `.Name`
|
|
119
|
+
replaces the bare identifier on the diagram.
|
|
120
|
+
|
|
121
|
+
[source]
|
|
122
|
+
----
|
|
123
|
+
I1.Name = "Close Switch"
|
|
124
|
+
I1.Description = "(BI 3.20)"
|
|
125
|
+
O1.Name = "Launch Command"
|
|
126
|
+
----
|
|
127
|
+
|
|
128
|
+
=== TeX math in labels
|
|
129
|
+
|
|
130
|
+
`.Name` and `.Description` support inline TeX math between dollar signs, rendered with
|
|
131
|
+
MathJax:
|
|
132
|
+
|
|
133
|
+
[source]
|
|
134
|
+
----
|
|
135
|
+
I1.Name = "$I_a$"
|
|
136
|
+
I1.Description = "Phase A current"
|
|
137
|
+
O1.Name = "$I > I_{set}$"
|
|
138
|
+
O2.Name = "$\overline{A \cdot B}$"
|
|
139
|
+
----
|
|
140
|
+
|
|
141
|
+
Subscripts (`I_{set}`), superscripts (`I^2`), Greek (`\Delta`, `\omega`), operators
|
|
142
|
+
(`\cdot`, `\geq`, `\leq`), and the overline/negation bar (`\overline{A}`) are supported.
|
|
143
|
+
Plain text and math may be mixed: `"Phase $I_a$ + $I_b$"`.
|
|
144
|
+
|
|
145
|
+
== Naming gates
|
|
146
|
+
|
|
147
|
+
A gate can be labelled. There are two ways.
|
|
148
|
+
|
|
149
|
+
**Pass-through intermediate** — name the gate by assigning it to an intermediate, then
|
|
150
|
+
label that name. The label is drawn at the gate's output and wires route around it.
|
|
151
|
+
|
|
152
|
+
[source]
|
|
153
|
+
----
|
|
154
|
+
PH = O51 OR O50 OR NEGSEQ // this OR gate is now called "PH"
|
|
155
|
+
PH.Name = "Phase OC"
|
|
156
|
+
PH.Description = "51/50/46"
|
|
157
|
+
TRIP = PH AND ENABLE // PH is consumed, so it stays internal and labelled
|
|
158
|
+
----
|
|
159
|
+
|
|
160
|
+
**Direct gate id** — give the gate an instance id inline with `AND#ID(...)` /
|
|
161
|
+
`OR#ID(...)`, then attach a name/description to the id. No pass-through needed.
|
|
162
|
+
|
|
163
|
+
[source]
|
|
164
|
+
----
|
|
165
|
+
OC = OR#OC1(I51, I50, I46)
|
|
166
|
+
OC1.Name = "Overcurrent"
|
|
167
|
+
OC1.Description = "51/50N"
|
|
168
|
+
----
|
|
169
|
+
|
|
170
|
+
== Function blocks
|
|
171
|
+
|
|
172
|
+
Beyond plain gates, expressions may call *function-block primitives* modelled on SEL
|
|
173
|
+
SELogic elements. Each returns a logic signal, so it nests and composes like any
|
|
174
|
+
sub-expression.
|
|
175
|
+
|
|
176
|
+
[cols="1m,3m,4"]
|
|
177
|
+
|===
|
|
178
|
+
| Block | Call | Meaning
|
|
179
|
+
|
|
180
|
+
| Timer | `TIMER(in, pu, do)`
|
|
181
|
+
| Pickup/dropout timer. `pu` delays the rising edge, `do` delays the falling edge.
|
|
182
|
+
Settings are positional or named (`PU=`, `DO=`); a bare number is cycles; `0` = no
|
|
183
|
+
delay. Units: `cyc`, `ms`, `s`, `m`.
|
|
184
|
+
|
|
185
|
+
| SR latch | `SR(set, reset)`
|
|
186
|
+
| Set/reset latch. `.Q` (default) is the output, `.NQ` the inverted output Q̄.
|
|
187
|
+
Reset-dominant unless `DOMINANT=SET`. Only referenced outputs are drawn.
|
|
188
|
+
|
|
189
|
+
| Rising edge | `RISING(in)`
|
|
190
|
+
| One-interval pulse on each 0→1 transition of `in`.
|
|
191
|
+
|
|
192
|
+
| Falling edge | `FALLING(in)`
|
|
193
|
+
| One-interval pulse on each 1→0 transition of `in`.
|
|
194
|
+
|
|
195
|
+
| Comparator | `COMPARE(plus, minus)`
|
|
196
|
+
| Asserts when the first (analog) input ≥ the second. Drawn as a comparator triangle.
|
|
197
|
+
|
|
198
|
+
| Generic block | `FB#id(a, NAME=b, ...).PORT`
|
|
199
|
+
| A user-defined box with whatever inputs/outputs you need. Bare or `NAME=`-labelled
|
|
200
|
+
arguments are input ports; each `.PORT` you reference is an output port.
|
|
201
|
+
|===
|
|
202
|
+
|
|
203
|
+
Blocks nest and chain freely, and a named intermediate threaded into a block taps the
|
|
204
|
+
same source (a fan-out) rather than being duplicated:
|
|
205
|
+
|
|
206
|
+
[source]
|
|
207
|
+
----
|
|
208
|
+
A = SR(COMPARE(IA, IPICKUP), RESET)
|
|
209
|
+
TRIP = TIMER(A, 0, 30cyc) // the SR latch feeds the timer; A is not duplicated
|
|
210
|
+
----
|
|
211
|
+
|
|
212
|
+
=== Block instance ids and settings
|
|
213
|
+
|
|
214
|
+
A block may carry an explicit instance id (`BLOCK#id`) so it can be named or reused. The
|
|
215
|
+
id then takes a name/description like any object. Referencing the same id twice reuses
|
|
216
|
+
one block (e.g. an SR latch drawn with both `.Q` and `.NQ`):
|
|
217
|
+
|
|
218
|
+
[source]
|
|
219
|
+
----
|
|
220
|
+
PERMIT = SR#L1(START, STOP, DOMINANT=SET).Q
|
|
221
|
+
BLOCKED = SR#L1(START, STOP).NQ
|
|
222
|
+
L1.Name = "Enable Latch"
|
|
223
|
+
----
|
|
224
|
+
|
|
225
|
+
For a generic `FB`, the instance is de-duplicated by id, so multiple outputs bind to one
|
|
226
|
+
box:
|
|
227
|
+
|
|
228
|
+
[source]
|
|
229
|
+
----
|
|
230
|
+
TRIP = FB#PROT(PHASE=IA, EARTH=IN, EN=ENABLE).TRIP
|
|
231
|
+
ALARM = FB#PROT.ALARM
|
|
232
|
+
PROT.Name = "Feeder Protection"
|
|
233
|
+
PROT.Description = "SEL-751A"
|
|
234
|
+
----
|
|
235
|
+
|
|
236
|
+
== Options
|
|
237
|
+
|
|
238
|
+
Document-level directives, written `OPTION NAME = VALUE`, control rendering. Set each at
|
|
239
|
+
most once; place them before the logic. Combine freely.
|
|
240
|
+
|
|
241
|
+
[cols="1m,2m,4"]
|
|
242
|
+
|===
|
|
243
|
+
| Option | Values | Effect
|
|
244
|
+
|
|
245
|
+
| `INVERSION` | `GATES` (default), `BUBBLES`
|
|
246
|
+
| Draw `NOT` as separate NOT gates, or as inversion bubbles on the downstream port.
|
|
247
|
+
|
|
248
|
+
| `GATE_INPUT_STYLE` | `EXPAND` (default), `BARS`
|
|
249
|
+
| Grow multi-input gates vertically, or keep them 2-input-sized with input bars.
|
|
250
|
+
|
|
251
|
+
| `PORT_STYLE` | `CIRCLE` (default), `SQUARE`
|
|
252
|
+
| Port marker shape.
|
|
253
|
+
|
|
254
|
+
| `OUTPUT_ORDER` | `AUTO` (default), `DECLARATION`
|
|
255
|
+
| Reorder outputs by their driver to reduce crossings, or keep them in declared order.
|
|
256
|
+
|
|
257
|
+
| `INPUT_ORDER` | `AUTO` (default), `DECLARATION`
|
|
258
|
+
| Reorder gate inputs to minimise crossings, or keep declared order.
|
|
259
|
+
|
|
260
|
+
| `COMPACTNESS` | `NORMAL`, `COMPACT_V`, `COMPACT_H`, `COMPACT`, `SPACIOUS`, or factors
|
|
261
|
+
| Spacing preset, or explicit `vertical,horizontal` factors (e.g. `70,70` or `[60,60]`).
|
|
262
|
+
|
|
263
|
+
| `COLUMN_SPACING` | `ADAPTIVE` (default), `UNIFORM`
|
|
264
|
+
| Size each column gap to its content for a narrower diagram, or use a fixed column pitch.
|
|
265
|
+
|
|
266
|
+
| `MARGIN` | integer px (default `8`)
|
|
267
|
+
| Blank border around the diagram content in the rendered/exported SVG.
|
|
268
|
+
|
|
269
|
+
| `WIRE_LABEL_LEADER` | `TRUE` (default), `FALSE`
|
|
270
|
+
| Draw a dashed leader line from each consumed-intermediate net label to the wire it names, so the
|
|
271
|
+
label reads unambiguously even when placed in nearby clear space.
|
|
272
|
+
|
|
273
|
+
| `HIDE_JUNCTIONS` | `TRUE`, `FALSE`
|
|
274
|
+
| Hide every junction (fan-out) dot.
|
|
275
|
+
|===
|
|
276
|
+
|
|
277
|
+
NOTE: `INPUT_ORDER`, `OUTPUT_ORDER` and `COLUMN_SPACING` default to the crossing-minimising /
|
|
278
|
+
content-sized behaviour. Set them to `DECLARATION` / `UNIFORM` on a diagram to opt back out.
|
|
279
|
+
|
|
280
|
+
[source]
|
|
281
|
+
----
|
|
282
|
+
OPTION INVERSION = BUBBLES
|
|
283
|
+
OPTION GATE_INPUT_STYLE = BARS
|
|
284
|
+
OPTION COLUMN_SPACING = ADAPTIVE
|
|
285
|
+
----
|
|
286
|
+
|
|
287
|
+
== Styling
|
|
288
|
+
|
|
289
|
+
Declare an object with an id and target it from a `STYLE` block of raw CSS. Every symbol
|
|
290
|
+
also carries stable classes (`ldl-gate-and`, `ldl-port`, `ldl-wire`, …) for broad rules.
|
|
291
|
+
|
|
292
|
+
[source]
|
|
293
|
+
----
|
|
294
|
+
STYLE
|
|
295
|
+
#G1 { stroke: #c0392b; stroke-width: 2; }
|
|
296
|
+
.ldl-gate-or { fill: #eef; }
|
|
297
|
+
END STYLE
|
|
298
|
+
|
|
299
|
+
AND#G1
|
|
300
|
+
TRIP = A AND B
|
|
301
|
+
----
|
|
302
|
+
|
|
303
|
+
== Comments
|
|
304
|
+
|
|
305
|
+
[source]
|
|
306
|
+
----
|
|
307
|
+
// line comment
|
|
308
|
+
/* block comment,
|
|
309
|
+
which may /* nest */ */
|
|
310
|
+
----
|
|
311
|
+
|
|
312
|
+
== Not covered here
|
|
313
|
+
|
|
314
|
+
The link:../spec/spec.adoc[specification] also describes constructs that are reserved or
|
|
315
|
+
planned but **not yet implemented** by this renderer: the `NAND`/`NOR`/`XOR`/`XNOR`
|
|
316
|
+
operators, `CONNECT` statements, custom `SYMBOL` definitions, `IMPORT`/`STYLESHEET`,
|
|
317
|
+
hyperlinks (`.LINK`), and bidirectional ports. Use the equivalent working forms above
|
|
318
|
+
(e.g. `NOT (A AND B)` for NAND).
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openpowershift/logic-diagram-language",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Render SEL-style protection-relay logic diagrams from a compact text language (LDL) to SVG, PNG, or PDF.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"logic-diagram",
|
|
7
|
+
"protection-relay",
|
|
8
|
+
"sel",
|
|
9
|
+
"svg",
|
|
10
|
+
"schematic",
|
|
11
|
+
"ladder-logic",
|
|
12
|
+
"diagram"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "Daniel Mulholland",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/openpowershift/logic-diagram-language.git"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/openpowershift/logic-diagram-language#readme",
|
|
21
|
+
"type": "module",
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=20"
|
|
24
|
+
},
|
|
25
|
+
"main": "./dist/index.js",
|
|
26
|
+
"module": "./dist/index.js",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./package.json": "./package.json"
|
|
34
|
+
},
|
|
35
|
+
"sideEffects": false,
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"docs",
|
|
39
|
+
"spec",
|
|
40
|
+
"README.md",
|
|
41
|
+
"LICENSE"
|
|
42
|
+
],
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"dev": "vite",
|
|
48
|
+
"build": "tsc && vite build",
|
|
49
|
+
"build:lib": "tsc -p tsconfig.lib.json",
|
|
50
|
+
"prepublishOnly": "npm run build:lib",
|
|
51
|
+
"preview": "vite preview",
|
|
52
|
+
"test": "vitest run",
|
|
53
|
+
"test:watch": "vitest",
|
|
54
|
+
"test:visual": "playwright test",
|
|
55
|
+
"typecheck": "tsc --noEmit",
|
|
56
|
+
"lint": "eslint src/"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"@mathjax/src": "^4.1.2",
|
|
60
|
+
"jspdf": "^4.2.1"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@codemirror/commands": "^6.8.0",
|
|
64
|
+
"@codemirror/language": "^6.11.0",
|
|
65
|
+
"@codemirror/state": "^6.5.0",
|
|
66
|
+
"@codemirror/view": "^6.36.0",
|
|
67
|
+
"@playwright/test": "^1.52.0",
|
|
68
|
+
"@types/node": "^26.0.0",
|
|
69
|
+
"codemirror": "^6.0.0",
|
|
70
|
+
"eslint": "^9.0.0",
|
|
71
|
+
"jsdom": "^29.1.1",
|
|
72
|
+
"lit": "^3.2.0",
|
|
73
|
+
"typescript": "^5.7.0",
|
|
74
|
+
"typescript-eslint": "^8.62.0",
|
|
75
|
+
"vite": "^6.3.0",
|
|
76
|
+
"vitest": "^3.2.0"
|
|
77
|
+
}
|
|
78
|
+
}
|
package/spec/render.sh
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
OUTPUT_DIR="${SCRIPT_DIR}/build"
|
|
6
|
+
|
|
7
|
+
command -v asciidoctor >/dev/null 2>&1 || {
|
|
8
|
+
echo "Error: asciidoctor is required. Install with: gem install asciidoctor"
|
|
9
|
+
exit 1
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
mkdir -p "$OUTPUT_DIR"
|
|
13
|
+
|
|
14
|
+
echo "Rendering spec.adoc -> HTML"
|
|
15
|
+
asciidoctor \
|
|
16
|
+
--attribute sectnums \
|
|
17
|
+
--attribute sectlinks \
|
|
18
|
+
--attribute toclevels=4 \
|
|
19
|
+
--destination-dir "$OUTPUT_DIR" \
|
|
20
|
+
"${SCRIPT_DIR}/spec.adoc"
|
|
21
|
+
|
|
22
|
+
echo "Output: ${OUTPUT_DIR}/spec.html"
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
== Attributes
|
|
2
|
+
|
|
3
|
+
Attributes are named values associated with an object or port. They are rendered inside the symbol's SVG using placeholder substitution.
|
|
4
|
+
|
|
5
|
+
=== Attribute Declaration
|
|
6
|
+
|
|
7
|
+
[literal]
|
|
8
|
+
....
|
|
9
|
+
OBJECT.ATTRIBUTE_NAME = VALUE
|
|
10
|
+
....
|
|
11
|
+
|
|
12
|
+
=== Name and Description Attributes
|
|
13
|
+
|
|
14
|
+
Ports and expression outputs may carry `.Name` and `.Description` metadata:
|
|
15
|
+
|
|
16
|
+
[literal]
|
|
17
|
+
....
|
|
18
|
+
I1.Name = "CBQ 00 Open"
|
|
19
|
+
I1.Description = "(BI 3.1)"
|
|
20
|
+
O1.Name = "Output"
|
|
21
|
+
O1.Description = "(BO 3.2)"
|
|
22
|
+
....
|
|
23
|
+
|
|
24
|
+
* `.Name` -- A human-readable label. Replaces the bare identifier on the diagram when the labels layer is active.
|
|
25
|
+
* `.Description` -- Supplementary annotation text, shown in a smaller font below the name.
|
|
26
|
+
|
|
27
|
+
These attributes are rendered as `ldl-label` class elements within the SVG and are controlled by the labels layer (see <<layout-rules>>).
|
|
28
|
+
|
|
29
|
+
=== TeX in Labels
|
|
30
|
+
|
|
31
|
+
Both `.Name` and `.Description` values support inline TeX math notation enclosed in dollar signs:
|
|
32
|
+
|
|
33
|
+
[literal]
|
|
34
|
+
....
|
|
35
|
+
I1.Name = "$I_a$"
|
|
36
|
+
I1.Description = "Phase A current"
|
|
37
|
+
O1.Name = "$I > I_{set}$"
|
|
38
|
+
O1.Description = "Overcurrent trip"
|
|
39
|
+
....
|
|
40
|
+
|
|
41
|
+
* Inline math is delimited by `$...$` within the attribute value string.
|
|
42
|
+
* A literal `$` outside math delimiters may be produced via `\` `$` in LDL source (the parser passes all `\` sequences through to the segment parser except `\"`).
|
|
43
|
+
* Supported TeX constructs (via MathJax v4 with `base` and `ams` packages):
|
|
44
|
+
** Subscripts: `$I_{set}$`
|
|
45
|
+
** Superscripts: `$I^2$`
|
|
46
|
+
** Greek letters: `$\Delta$`, `$\Theta$`, `$\alpha$`, `$\omega$`, etc.
|
|
47
|
+
** Mathematical operators: `$\cdot$` (·), `$\geq$` (≥), `$\leq$` (≤), `$\neq$` (≠), etc.
|
|
48
|
+
** Overline (negation bar): `$\overline{A}$`
|
|
49
|
+
** Mixed content: `"Phase $I_a$ + $I_b$"` → plain text interspersed with math
|
|
50
|
+
|
|
51
|
+
* Rendering:
|
|
52
|
+
** Input port labels are right-aligned (text-anchor: end).
|
|
53
|
+
** Output port labels are left-aligned (text-anchor: start).
|
|
54
|
+
** Math content is rendered via MathJax as standalone `<svg>` elements, embedded inline within the label `<g>`. Each math SVG is positioned relative to the text baseline using a `transform` attribute, with the baseline offset derived from the MathJax `viewBox`.
|
|
55
|
+
** The math height is computed from the SVG's intrinsic dimensions and contributes to the overall label height, which may cause the containing gate to expand.
|
|
56
|
+
|
|
57
|
+
=== Other Attribute Examples
|
|
58
|
+
|
|
59
|
+
[literal]
|
|
60
|
+
....
|
|
61
|
+
SVT#1.PICKUP = 30 s
|
|
62
|
+
SVT#1.DROPOUT = 10 s
|
|
63
|
+
TIMER#T2.SETPOINT = 1.5 s
|
|
64
|
+
RELAY#R5.NAME = "Main Trip"
|
|
65
|
+
....
|
|
66
|
+
|
|
67
|
+
=== Substitution in Symbols
|
|
68
|
+
|
|
69
|
+
Inside an SVG symbol definition, attribute placeholders use double-brace syntax:
|
|
70
|
+
|
|
71
|
+
[literal]
|
|
72
|
+
....
|
|
73
|
+
<text>{{PICKUP}}</text>
|
|
74
|
+
....
|
|
75
|
+
|
|
76
|
+
When the object is instantiated, `{{PICKUP}}` is replaced with the attribute value `30 s`.
|
|
77
|
+
|
|
78
|
+
If an attribute is declared but no placeholder exists in the symbol SVG, the value is discarded silently.
|
|
79
|
+
|
|
80
|
+
If a placeholder exists in the symbol SVG but no attribute is provided, the placeholder is replaced with an empty string.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
== Connections
|
|
2
|
+
|
|
3
|
+
=== Implicit Connections
|
|
4
|
+
|
|
5
|
+
When an expression assigns an output, the connections are implied:
|
|
6
|
+
|
|
7
|
+
[literal]
|
|
8
|
+
....
|
|
9
|
+
CBFPS = A AND B
|
|
10
|
+
....
|
|
11
|
+
|
|
12
|
+
Creates:
|
|
13
|
+
|
|
14
|
+
* Input port `A` on the diagram boundary.
|
|
15
|
+
* Input port `B` on the diagram boundary.
|
|
16
|
+
* An AND gate with inputs connected to `A` and `B`.
|
|
17
|
+
* Output port `CBFPS` on the diagram boundary, connected to the AND gate output.
|
|
18
|
+
|
|
19
|
+
=== Explicit Connections
|
|
20
|
+
|
|
21
|
+
A `CONNECT` statement creates a wire between two ports:
|
|
22
|
+
|
|
23
|
+
[literal]
|
|
24
|
+
....
|
|
25
|
+
CONNECT AND#G1.out AND#G2.in1
|
|
26
|
+
....
|
|
27
|
+
|
|
28
|
+
Or using simplified dot notation:
|
|
29
|
+
|
|
30
|
+
[literal]
|
|
31
|
+
....
|
|
32
|
+
CONNECT G1.out G2.in1
|
|
33
|
+
....
|
|
34
|
+
|
|
35
|
+
When the object symbol is unambiguous, the symbol type may be omitted.
|
|
36
|
+
|
|
37
|
+
=== Output Fan-Out
|
|
38
|
+
|
|
39
|
+
A single output port may connect to multiple input ports. The renderer handles wire routing automatically.
|