@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,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.