@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,58 @@
1
+ export interface DiagramTheme {
2
+ background: string;
3
+ stroke: string;
4
+ fill: string;
5
+ wire: string;
6
+ wireHover: string;
7
+ portFill: string;
8
+ nameFill: string;
9
+ nameOutFill: string;
10
+ descFill: string;
11
+ idFill: string;
12
+ junctionFill: string;
13
+ }
14
+ export interface UITheme {
15
+ bg: string;
16
+ surface: string;
17
+ border: string;
18
+ text: string;
19
+ textDim: string;
20
+ textMuted: string;
21
+ toolbarBg: string;
22
+ toolbarDark: string;
23
+ toolbarActive: string;
24
+ accent: string;
25
+ errorBg: string;
26
+ errorBorder: string;
27
+ errorText: string;
28
+ editorBg: string;
29
+ editorGutter: string;
30
+ editorGutterText: string;
31
+ editorActiveLine: string;
32
+ editorActiveGutter: string;
33
+ editorCursor: string;
34
+ editorSelection: string;
35
+ syntaxComment: string;
36
+ syntaxKeyword: string;
37
+ syntaxType: string;
38
+ syntaxVariable: string;
39
+ syntaxString: string;
40
+ syntaxNumber: string;
41
+ syntaxPunct: string;
42
+ toolbarText: string;
43
+ toolbarBorderAlpha20: string;
44
+ toolbarBorderAlpha40: string;
45
+ toolbarSeparator: string;
46
+ }
47
+ export interface AppTheme {
48
+ name: string;
49
+ ui: UITheme;
50
+ diagram: DiagramTheme;
51
+ print: DiagramTheme;
52
+ }
53
+ export declare const LIGHT_DIAGRAM: DiagramTheme;
54
+ export declare const DARK_DIAGRAM: DiagramTheme;
55
+ export declare const LIGHT_UI: UITheme;
56
+ export declare const DARK_UI: UITheme;
57
+ export declare const LIGHT_THEME: AppTheme;
58
+ export declare const DARK_THEME: AppTheme;
@@ -0,0 +1,104 @@
1
+ export const LIGHT_DIAGRAM = {
2
+ background: '#ffffff',
3
+ stroke: '#2c3e50',
4
+ fill: '#ffffff',
5
+ wire: '#34495e',
6
+ wireHover: '#1b5e20',
7
+ portFill: '#2c3e50',
8
+ nameFill: '#1a237e',
9
+ nameOutFill: '#1a237e',
10
+ descFill: '#607d8b',
11
+ idFill: '#90a4ae',
12
+ junctionFill: '#34495e',
13
+ };
14
+ export const DARK_DIAGRAM = {
15
+ background: '#1a1b2e',
16
+ stroke: '#c8cdd5',
17
+ fill: '#2a2b3d',
18
+ wire: '#9aa5b4',
19
+ wireHover: '#4caf50',
20
+ portFill: '#c8cdd5',
21
+ nameFill: '#82b1ff',
22
+ nameOutFill: '#82b1ff',
23
+ descFill: '#90a4ae',
24
+ idFill: '#607080',
25
+ junctionFill: '#9aa5b4',
26
+ };
27
+ export const LIGHT_UI = {
28
+ bg: '#f5f6fa',
29
+ surface: '#e8eaf0',
30
+ border: '#dce1e8',
31
+ text: '#2c3e50',
32
+ textDim: '#78909c',
33
+ textMuted: '#90a4ae',
34
+ toolbarBg: '#2c3e50',
35
+ toolbarDark: '#1a252f',
36
+ toolbarActive: '#0f3460',
37
+ accent: '#3498db',
38
+ errorBg: '#fdecea',
39
+ errorBorder: '#e57373',
40
+ errorText: '#c62828',
41
+ editorBg: '#ffffff',
42
+ editorGutter: '#f5f6fa',
43
+ editorGutterText: '#90a4ae',
44
+ editorActiveLine: '#f0f2f8',
45
+ editorActiveGutter: '#e8eaf0',
46
+ editorCursor: '#2c3e50',
47
+ editorSelection: 'rgba(52, 152, 219, 0.25)',
48
+ syntaxComment: '#6a9955',
49
+ syntaxKeyword: '#9c27b0',
50
+ syntaxType: '#1565c0',
51
+ syntaxVariable: '#2c3e50',
52
+ syntaxString: '#e65100',
53
+ syntaxNumber: '#00695c',
54
+ syntaxPunct: '#78909c',
55
+ toolbarText: '#e0e0e0',
56
+ toolbarBorderAlpha20: 'rgba(255,255,255,0.2)',
57
+ toolbarBorderAlpha40: 'rgba(255,255,255,0.4)',
58
+ toolbarSeparator: 'rgba(255,255,255,0.25)',
59
+ };
60
+ export const DARK_UI = {
61
+ bg: '#12131e',
62
+ surface: '#1a1b2e',
63
+ border: '#2e3045',
64
+ text: '#e2e8f0',
65
+ textDim: '#9aa5b4',
66
+ textMuted: '#607080',
67
+ toolbarBg: '#1e1f32',
68
+ toolbarDark: '#141525',
69
+ toolbarActive: '#283593',
70
+ accent: '#42a5f5',
71
+ errorBg: '#2d1215',
72
+ errorBorder: '#c62828',
73
+ errorText: '#ef9a9a',
74
+ editorBg: '#1a1b2e',
75
+ editorGutter: '#12131e',
76
+ editorGutterText: '#607080',
77
+ editorActiveLine: '#252640',
78
+ editorActiveGutter: '#2a2b3d',
79
+ editorCursor: '#e2e8f0',
80
+ editorSelection: 'rgba(66, 165, 245, 0.25)',
81
+ syntaxComment: '#6a9955',
82
+ syntaxKeyword: '#ce93d8',
83
+ syntaxType: '#64b5f6',
84
+ syntaxVariable: '#e2e8f0',
85
+ syntaxString: '#ffab91',
86
+ syntaxNumber: '#4db6ac',
87
+ syntaxPunct: '#9aa5b4',
88
+ toolbarText: '#e0e0e0',
89
+ toolbarBorderAlpha20: 'rgba(255,255,255,0.2)',
90
+ toolbarBorderAlpha40: 'rgba(255,255,255,0.4)',
91
+ toolbarSeparator: 'rgba(255,255,255,0.25)',
92
+ };
93
+ export const LIGHT_THEME = {
94
+ name: 'light',
95
+ ui: LIGHT_UI,
96
+ diagram: LIGHT_DIAGRAM,
97
+ print: LIGHT_DIAGRAM,
98
+ };
99
+ export const DARK_THEME = {
100
+ name: 'dark',
101
+ ui: DARK_UI,
102
+ diagram: DARK_DIAGRAM,
103
+ print: LIGHT_DIAGRAM,
104
+ };
package/docs/api.adoc ADDED
@@ -0,0 +1,196 @@
1
+ = LDL API Reference
2
+ :toc: left
3
+ :toclevels: 2
4
+ :source-highlighter: highlight.js
5
+
6
+ The `@openpowershift/logic-diagram-language` package turns *LDL* source (see the
7
+ link:user-guide.adoc[User Guide] for the language) into an SVG, and — in the browser — into a PNG or
8
+ PDF. This page documents the programmatic API.
9
+
10
+ == Installation
11
+
12
+ [source,sh]
13
+ ----
14
+ npm install @openpowershift/logic-diagram-language
15
+ ----
16
+
17
+ The package is ESM-only (`"type": "module"`) and ships type declarations. SVG rendering is
18
+ isomorphic (Node + browser); the PNG/PDF helpers are browser-only.
19
+
20
+ == Pipeline
21
+
22
+ [source]
23
+ ----
24
+ LDL source ──parse──▶ Diagram ──layoutDiagram──▶ LayoutResult (geometry)
25
+ └────── renderDiagram ──▶ SVG string
26
+ ----
27
+
28
+ `renderDiagram` runs the layout internally, so the common path is just `parse` → `renderDiagram`.
29
+
30
+ == Quick start (SVG)
31
+
32
+ [source,ts]
33
+ ----
34
+ import { parse, renderDiagram, resolveOptions } from '@openpowershift/logic-diagram-language';
35
+
36
+ const source = `
37
+ O1.Name = "Trip"
38
+ I1.Name = "Overcurrent"
39
+ I2.Name = "Enable"
40
+ O1 = I1 AND I2
41
+ `;
42
+
43
+ const { diagram, errors } = parse(source);
44
+ if (errors.length) throw new Error(errors[0].message);
45
+
46
+ const svg = renderDiagram(
47
+ diagram,
48
+ diagram.portMeta,
49
+ true, // showLabels
50
+ false, // showIds
51
+ resolveOptions(diagram.options), // options (merges OPTION lines over the defaults)
52
+ );
53
+ // `svg` is a complete <svg>…</svg> string — write it to a file or inject into the DOM.
54
+ ----
55
+
56
+ == Rendering to SVG
57
+
58
+ [source,ts]
59
+ ----
60
+ function renderDiagram(
61
+ diagram: Diagram,
62
+ portMeta?: PortMeta[],
63
+ showLabels?: boolean, // default true — draw .Name / .Description labels
64
+ showIds?: boolean, // default false — draw bare identifiers (I1, AND#G1, …)
65
+ options?: RenderOptions, // default DEFAULT_OPTIONS
66
+ diagramTheme?: DiagramTheme // default LIGHT_DIAGRAM
67
+ ): string
68
+ ----
69
+
70
+ - `diagram` / `portMeta` come from `parse()` (`result.diagram`, `result.diagram.portMeta`).
71
+ - `options` is a fully-resolved `RenderOptions`; build it with `resolveOptions(diagram.options)` so a
72
+ diagram's own `OPTION` lines are honoured, then override fields as needed.
73
+ - Pass `DARK_DIAGRAM` (or a custom `DiagramTheme`) for a dark render.
74
+
75
+ Node example writing a file:
76
+
77
+ [source,ts]
78
+ ----
79
+ import { writeFileSync } from 'node:fs';
80
+ import { parse, renderDiagram, resolveOptions } from '@openpowershift/logic-diagram-language';
81
+
82
+ const { diagram } = parse(source);
83
+ writeFileSync('diagram.svg', renderDiagram(diagram, diagram.portMeta, true, false,
84
+ resolveOptions(diagram.options)));
85
+ ----
86
+
87
+ == Rendering to PNG (browser)
88
+
89
+ [source,ts]
90
+ ----
91
+ import { parse, renderDiagram, resolveOptions, svgToPngBlob } from '@openpowershift/logic-diagram-language';
92
+
93
+ const { diagram } = parse(source);
94
+ const svg = renderDiagram(diagram, diagram.portMeta, true, false, resolveOptions(diagram.options));
95
+
96
+ const pngBlob = await svgToPngBlob(svg, 2); // 2× the diagram's intrinsic pixel size
97
+ // e.g. download it:
98
+ const url = URL.createObjectURL(pngBlob);
99
+ Object.assign(document.createElement('a'), { href: url, download: 'diagram.png' }).click();
100
+ URL.revokeObjectURL(url);
101
+ ----
102
+
103
+ `svgToPngBlob(svg, scale = 2)` rasterises the SVG onto a white `<canvas>` at `scale`× and returns a
104
+ PNG `Blob`. Browser only (needs `DOMParser`, `Image`, `<canvas>`).
105
+
106
+ == Rendering to PDF (browser)
107
+
108
+ [source,ts]
109
+ ----
110
+ import { svgToPdfBlob } from '@openpowershift/logic-diagram-language';
111
+
112
+ const pdfBlob = await svgToPdfBlob(svg); // single page sized to the diagram
113
+ ----
114
+
115
+ `svgToPdfBlob(svg, scale = 2)` returns a one-page PDF `Blob` sized to the diagram (orientation follows
116
+ the aspect ratio). It loads `jspdf` on demand, so it is not pulled into your bundle unless used.
117
+ Browser only.
118
+
119
+ TIP: In Node, render the SVG with `renderDiagram` and convert it with a server-side rasteriser such as
120
+ `@resvg/resvg-js`, `sharp`, or a headless browser.
121
+
122
+ == Options
123
+
124
+ Resolve a diagram's `OPTION` lines into a `RenderOptions`, then override individually:
125
+
126
+ [source,ts]
127
+ ----
128
+ import { resolveOptions, DEFAULT_OPTIONS } from '@openpowershift/logic-diagram-language';
129
+
130
+ const options = resolveOptions(diagram.options);
131
+ options.inversion = 'BUBBLES'; // override in code
132
+ options.margin = 16;
133
+ ----
134
+
135
+ `resolveOptions(optionDecls)` starts from `DEFAULT_OPTIONS` and applies each `OPTION` declaration.
136
+ Passing `diagram.options` (from `parse`) reproduces exactly what the diagram asked for.
137
+
138
+ [cols="1m,1,1m",options="header"]
139
+ |===
140
+ | Field | Values | Default
141
+
142
+ | inversion | `'GATES'` \| `'BUBBLES'` | `'GATES'`
143
+ | portStyle | `'CIRCLE'` \| `'SQUARE'` | `'CIRCLE'`
144
+ | gateInputStyle | `'EXPAND'` \| `'BARS'` | `'EXPAND'`
145
+ | inputOrder | `'AUTO'` \| `'DECLARATION'` | `'AUTO'`
146
+ | outputOrder | `'AUTO'` \| `'DECLARATION'` | `'AUTO'`
147
+ | columnSpacing | `'ADAPTIVE'` \| `'UNIFORM'` | `'ADAPTIVE'`
148
+ | compactness | `'NORMAL'` \| `'COMPACT'` \| `'COMPACT_V'` \| `'COMPACT_H'` \| `'SPACIOUS'` | `'NORMAL'`
149
+ | compactnessFactors | `[number, number]` (vertical, horizontal) | —
150
+ | margin | `number` (px) | `8`
151
+ | wireLabelLeader | `boolean` | `true`
152
+ | strokeWidth | `number` (px) | `2.5`
153
+ | hideJunctions | `boolean` | `false`
154
+ |===
155
+
156
+ Each field also has an `OPTION` form (e.g. `OPTION INVERSION = BUBBLES`, `OPTION MARGIN = 16`) — see
157
+ the link:user-guide.adoc[User Guide] and link:../spec/spec.adoc[spec] for the source syntax.
158
+
159
+ == Working with geometry directly
160
+
161
+ To post-process the layout (custom renderer, measurements, hit-testing) call `layoutDiagram`:
162
+
163
+ [source,ts]
164
+ ----
165
+ import { parse, layoutDiagram, resolveOptions, findWireCrossings } from '@openpowershift/logic-diagram-language';
166
+
167
+ const { diagram } = parse(source);
168
+ const layout = layoutDiagram(diagram, diagram.portMeta, resolveOptions(diagram.options));
169
+
170
+ layout.nodes; // LayoutNode[] — gates/blocks/inputs/outputs with absX/absY/width/height + ports
171
+ layout.wires; // LayoutWire[] — orthogonal point lists, fromId/toId
172
+ layout.junctions; // LayoutJunction[]
173
+ layout.labels; // LayoutLabel[] — consumed-intermediate net labels (+ optional leaderX/leaderY)
174
+ layout.width; layout.height;
175
+
176
+ const crossings = findWireCrossings(layout.wires, layout.junctions); // WireCrossing[]
177
+ ----
178
+
179
+ == Exports
180
+
181
+ [cols="1m,3",options="header"]
182
+ |===
183
+ | Export | Description
184
+
185
+ | parse | LDL source → `{ diagram, errors }`
186
+ | renderDiagram | `Diagram` → SVG string (runs layout internally)
187
+ | layoutDiagram | `Diagram` → `LayoutResult` (geometry only)
188
+ | findWireCrossings | count/enumerate wire crossings in a layout
189
+ | resolveOptions | `OptionDecl[]` → fully-resolved `RenderOptions`
190
+ | DEFAULT_OPTIONS | the default `RenderOptions`
191
+ | svgToPngBlob | SVG string → PNG `Blob` (browser)
192
+ | svgToPdfBlob | SVG string → PDF `Blob` (browser)
193
+ | LIGHT_DIAGRAM, DARK_DIAGRAM | built-in `DiagramTheme` values
194
+ | EXAMPLES, EXAMPLE_NAMES | map of example name → LDL source, and its keys
195
+ | _types_ | `RenderOptions`, `Diagram`, `LayoutResult`, `DiagramTheme`, … (see `index.d.ts`)
196
+ |===
@@ -0,0 +1,163 @@
1
+ # Authoring LDL — a guide for AI agents
2
+
3
+ This is a **self-contained, implemented-only** reference for generating LDL (Logic Diagram Language)
4
+ source. Prefer it over the full [spec](../spec/spec.adoc): the spec is a formal grammar that also
5
+ describes *reserved but not-yet-implemented* features. Everything below renders today.
6
+
7
+ Give an end user a diagram by emitting an LDL file and rendering it with
8
+ `@openpowershift/logic-diagram-language` (`parse` → `renderDiagram`, see [api.adoc](api.adoc)).
9
+
10
+ ## Mental model
11
+
12
+ - An LDL file is a list of **assignments** `NAME = <expression>`. The renderer lays everything out
13
+ automatically — you never specify coordinates.
14
+ - A name **consumed** in another expression becomes a shared internal signal (it fans out). A name
15
+ **not** consumed anywhere becomes a diagram **output** (drawn on the right). Boundary names that are
16
+ only ever *read* become **inputs** (drawn on the left).
17
+ - So: to make `X` an output, don't reference it elsewhere. To force an intermediate to also render as
18
+ an output, add `X.OUT = TRUE`.
19
+
20
+ ## Core syntax (all implemented)
21
+
22
+ ```ldl
23
+ // comment to end of line
24
+
25
+ O1 = A AND B OR NOT C // operators: AND, OR, NOT (precedence: NOT > AND > OR)
26
+ O2 = A AND (B OR C) // parentheses group
27
+ INT = A AND B // INT is consumed below -> internal signal (not an output)
28
+ O3 = INT OR D
29
+
30
+ // Labels & descriptions (identifiers stay the machine name; labels are display text)
31
+ A.Name = "Overcurrent"
32
+ A.Description = "50P1 pickup"
33
+ O1.Name = "Trip"
34
+
35
+ // Inline TeX math in any label with $...$
36
+ IA.Name = "$I_a > I_{pickup}$"
37
+
38
+ // Force an internal signal to ALSO be drawn as an output
39
+ INT.OUT = TRUE
40
+ ```
41
+
42
+ ### Operators
43
+
44
+ Only **`AND`**, **`OR`**, **`NOT`** exist. `NAND`, `NOR`, `XOR`, `XNOR` are **not implemented** —
45
+ express them the long way (e.g. NAND → `NOT (A AND B)`).
46
+
47
+ ### Named gates
48
+
49
+ Give a specific gate an id (so it can be labelled or CSS-styled) with `GATE#ID(...)`:
50
+
51
+ ```ldl
52
+ TRIP = AND#G1(A, B, C)
53
+ G1.Name = "Trip AND"
54
+ ```
55
+
56
+ ### Function blocks (SEL-style)
57
+
58
+ ```ldl
59
+ T = TIMER(IN, 2cyc, 5cyc) // TIMER(input, pickup, dropout); durations: 2cyc, 30cyc, 3s, 0
60
+ L = SR(SET, RESET) // SR latch (default output Q). For the inverted output: SR(SET, RESET).NQ
61
+ E = RISING(A) // rising-edge (FALLING(A) for falling)
62
+ C = COMPARE(IA, IPICKUP) // comparator: + over -, outputs true when IA > IPICKUP
63
+ // Generic block with named ports and a selected output port:
64
+ P50 = FB#E50P1(I = IPH, TC = NOT BLK).T
65
+ E50P1.Name = "50P1"
66
+ E50P1.Description = "DT 4.8 x In, 0.02 s"
67
+ ```
68
+
69
+ - Reference a block's output port with `.PORT` (e.g. `.Q`, `.NQ`, `.T`). Default port if omitted.
70
+ - Reuse a block instance by id: `SR#L1(...)` used twice refers to the same latch.
71
+
72
+ ### Feedback / seal-in
73
+
74
+ If a name appears **inside its own definition**, that reference is drawn as a loop-back wire (a latch
75
+ holding itself in) rather than a new input:
76
+
77
+ ```ldl
78
+ SEAL = (PICKUP OR SEAL) AND NOT RESET // SEAL feeds back into itself
79
+ ```
80
+
81
+ ### Options
82
+
83
+ Put `OPTION` lines at the top. Implemented options and defaults:
84
+
85
+ | Option | Values (default first) |
86
+ | --- | --- |
87
+ | `INVERSION` | `GATES`, `BUBBLES` |
88
+ | `PORT_STYLE` | `CIRCLE`, `SQUARE` |
89
+ | `GATE_INPUT_STYLE` | `EXPAND`, `BARS` |
90
+ | `INPUT_ORDER` | `AUTO`, `DECLARATION` |
91
+ | `OUTPUT_ORDER` | `AUTO`, `DECLARATION` |
92
+ | `COLUMN_SPACING` | `ADAPTIVE`, `UNIFORM` |
93
+ | `COMPACTNESS` | `NORMAL`, `COMPACT_V`, `COMPACT_H`, `COMPACT`, `SPACIOUS`, or `70,70` |
94
+ | `MARGIN` | integer px (default `8`) |
95
+ | `WIRE_LABEL_LEADER` | `TRUE` (default), `FALSE` |
96
+ | `STROKE_WIDTH` | number px (default `2.5`) |
97
+ | `HIDE_JUNCTIONS` | `TRUE`, `FALSE` |
98
+
99
+ ```ldl
100
+ OPTION INVERSION = BUBBLES
101
+ OPTION MARGIN = 16
102
+ ```
103
+
104
+ ### Styling (optional)
105
+
106
+ ```ldl
107
+ STYLE
108
+ #G1 path { fill: #ffecec; } // a gate body by its id (target the shape, e.g. path/rect)
109
+ .ldl-wire[data-to="TRIP"] { stroke: #c62828; stroke-width: 4; }
110
+ END STYLE
111
+ ```
112
+
113
+ ## Do NOT use (reserved but not implemented — they will not render)
114
+
115
+ `NAND` / `NOR` / `XOR` / `XNOR` operators · `CONNECT` · custom `SYMBOL` definitions ·
116
+ `IMPORT` / `STYLESHEET` · hyperlinks / `.LINK` · `.Style` attribute · `BIDI`.
117
+
118
+ ## Complete worked examples
119
+
120
+ Simple trip logic:
121
+
122
+ ```ldl
123
+ I1.Name = "Overcurrent"
124
+ I2.Name = "Block"
125
+ O1.Name = "Trip"
126
+ O1 = I1 AND NOT I2
127
+ ```
128
+
129
+ Overcurrent with seal-in latch and a trip timer (SEL-style):
130
+
131
+ ```ldl
132
+ OPTION INVERSION = BUBBLES
133
+
134
+ A = SR#L1(COMPARE(IA, IPICKUP), RESET) // comparator pickup into a seal-in latch (Q output)
135
+ TRIP = TIMER(A, 0, 30cyc) // 30-cycle trip delay; TRIP is unconsumed -> an output
136
+
137
+ IA.Name = "$I_a$"
138
+ IPICKUP.Name = "$I_{pickup}$"
139
+ RESET.Name = "Reset"
140
+ TRIP.Name = "Trip"
141
+ ```
142
+
143
+ Two protection elements ORed to a common trip, one shared blocking signal:
144
+
145
+ ```ldl
146
+ BLK = HBL2T AND WIN // shared internal signal (fans out to both)
147
+ P50 = FB#E50P1(I = IPH, TC = NOT BLK).T
148
+ Q50 = FB#E50Q1(I = IQ, TC = NOT BLK).T
149
+ TRIP = P50 OR Q50
150
+
151
+ BLK.Name = "Inrush block"
152
+ E50P1.Name = "50P1"
153
+ E50Q1.Name = "50Q1"
154
+ TRIP.Name = "Trip"
155
+ ```
156
+
157
+ ## Checklist before returning a file
158
+
159
+ 1. Only `AND` / `OR` / `NOT` operators (rewrite any NAND/NOR/XOR/XNOR).
160
+ 2. `OPTION` lines first, then assignments.
161
+ 3. Every signal that should be an **output** is not referenced elsewhere (or has `.OUT = TRUE`).
162
+ 4. Labels via `.Name` / `.Description`; wrap math in `$...$`.
163
+ 5. Validate by rendering: `parse(src).errors` must be empty.