@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,146 @@
1
+ == Options
2
+
3
+ *Options* are document-level directives that control rendering behaviour.
4
+ They MUST appear before any expressions, object declarations, or style blocks.
5
+
6
+ === Syntax
7
+
8
+ [source]
9
+ OPTION NAME = VALUE
10
+
11
+ Options are set once per document. If the same option appears multiple times,
12
+ the last occurrence wins.
13
+
14
+ === Available Options
15
+
16
+ [cols="1m,2m,2m,1m"]
17
+ |===
18
+ | Option | Values | Description | Default
19
+
20
+ | INVERSION
21
+ | GATES
22
+ | NOT operations render as separate NOT gate symbols (current behaviour)
23
+ | GATES
24
+
25
+ | INVERSION
26
+ | BUBBLES
27
+ | NOT operations render as inversion circles (bubbles) on the input port of the downstream gate
28
+ | --
29
+
30
+ | PORT_STYLE
31
+ | CIRCLE
32
+ | Port markers are rendered as circles (r=3)
33
+ | CIRCLE
34
+
35
+ | PORT_STYLE
36
+ | SQUARE
37
+ | Port markers are rendered as squares (5×5)
38
+ | --
39
+
40
+ | GATE_INPUT_STYLE
41
+ | EXPAND
42
+ | Multi-input gates expand vertically to accommodate all input ports with even spacing
43
+ | EXPAND
44
+
45
+ | GATE_INPUT_STYLE
46
+ | BARS
47
+ | Multi-input gates remain at base size (2-input height). Inputs beyond the first two connect via vertical input bars on the left side of the gate body
48
+ | --
49
+
50
+ | LABEL_STYLE
51
+ | SIDE
52
+ | Input labels appear to the left of the input port; output labels to the right (current behaviour)
53
+ | SIDE
54
+
55
+ | LABEL_STYLE
56
+ | ABOVE_BELOW
57
+ | Input labels appear above/below the port, closer to the gate body
58
+ | --
59
+
60
+ | OUTPUT_ORDER
61
+ | AUTO
62
+ | Output ports are reordered vertically by the position of the gate that drives them, so output wires fan out without crossing (the barycentre method already used for inputs)
63
+ | AUTO
64
+
65
+ | OUTPUT_ORDER
66
+ | DECLARATION
67
+ | Output ports are stacked top-to-bottom in the order the outputs are declared (O1, O2, …), each aligned to its source where possible
68
+ | --
69
+
70
+ | INPUT_ORDER
71
+ | AUTO
72
+ | Input ports are reordered vertically using the Sugiyama barycentre method to minimise wire crossings
73
+ | AUTO
74
+
75
+ | INPUT_ORDER
76
+ | DECLARATION
77
+ | Input ports keep their declared (natural-sorted) order; gate rows still follow from that fixed order
78
+ | --
79
+
80
+ | COMPACTNESS
81
+ | NORMAL
82
+ | Standard spacing on both axes
83
+ | NORMAL
84
+
85
+ | COMPACTNESS
86
+ | COMPACT_V
87
+ | Tighter vertical (row) spacing for a denser diagram (still respects minimum gaps, so wires never overlap)
88
+ | --
89
+
90
+ | COMPACTNESS
91
+ | COMPACT_H
92
+ | Tighter horizontal (column) spacing
93
+ | --
94
+
95
+ | COMPACTNESS
96
+ | COMPACT
97
+ | Tighter spacing on both axes (COMPACT_V + COMPACT_H)
98
+ | --
99
+
100
+ | COMPACTNESS
101
+ | SPACIOUS
102
+ | Looser vertical spacing for an airier diagram
103
+ | --
104
+
105
+ | COMPACTNESS
106
+ | _factors_
107
+ | Explicit spacing factors `vertical,horizontal` (e.g. `70,70` or `[60,60]`); a value > 3 is a percentage (70 → 0.70), otherwise a raw factor. A single value applies to both axes.
108
+ | --
109
+
110
+ | COLUMN_SPACING
111
+ | ADAPTIVE
112
+ | Size each inter-column gap to its content (gate widths + the fan-in channels that column needs), so simple columns pack tighter and the diagram is materially narrower. Never wider than UNIFORM, and validated so it introduces no dogleg, entrance violation, overlap or crossing.
113
+ | ADAPTIVE
114
+
115
+ | COLUMN_SPACING
116
+ | UNIFORM
117
+ | Fixed column pitch — every inter-column gap is the same width.
118
+ | --
119
+
120
+ | MARGIN
121
+ | _integer px_
122
+ | Blank border (in pixels) drawn around the diagram content in the rendered/exported SVG. Labels are already inside the content bounds, so this is a pure outer frame.
123
+ | 8
124
+
125
+ | WIRE_LABEL_LEADER
126
+ | TRUE / FALSE
127
+ | Draw a dashed leader line (with an endpoint dot on the wire) from each consumed-intermediate net label to the net it names. The label is seated a readable distance off the wire so the connector is visible; useful where a wide label cannot sit directly on a congested fan-out junction.
128
+ | TRUE
129
+ |===
130
+
131
+ === Examples
132
+
133
+ [source]
134
+ OPTION INVERSION = BUBBLES
135
+ OPTION PORT_STYLE = SQUARE
136
+ OPTION GATE_INPUT_STYLE = BARS
137
+
138
+ An LDL file may combine options freely:
139
+
140
+ [source]
141
+ OPTION INVERSION = BUBBLES
142
+ OPTION GATE_INPUT_STYLE = BARS
143
+ I1.Name = "Start"
144
+ I2.Name = "Stop"
145
+ I3.Name = "Reset"
146
+ O1 = I1 AND I2 AND NOT I3
@@ -0,0 +1,77 @@
1
+ == Ports
2
+
3
+ A *port* is a named connection point on an object. Ports are either inputs, outputs, or bidirectional.
4
+
5
+ === Port Types
6
+
7
+ [cols="1m,3m,3m"]
8
+ |===
9
+ | Keyword | Direction | Semantics
10
+ | `INPUT` | Inward | Receives a signal
11
+ | `OUTPUT` | Outward | Emits a signal
12
+ | `BIDI` | Both | Bidirectional (for Bus or analog)
13
+ |===
14
+
15
+ === Port Declarations
16
+
17
+ Ports are declared within a symbol definition (see <<symbol-definitions>>) or explicitly on an object:
18
+
19
+ [literal]
20
+ ....
21
+ SVT#1.INPUT = A OR B
22
+ SVT#1.OUTPUT = C
23
+ ....
24
+
25
+ The dot notation `OBJECT.PORT` references a specific port.
26
+
27
+ === Port Metadata
28
+
29
+ Ports may carry human-readable metadata using `.Name` and `.Description` attributes:
30
+
31
+ [literal]
32
+ ....
33
+ I1.Name = "CBQ 00 Open"
34
+ I1.Description = "(BI 3.1)"
35
+ O1.Name = "Output"
36
+ O1.Description = "(BO 3.2)"
37
+ ....
38
+
39
+ * `.Name` -- A human-readable label for the port. Displayed as the primary label on the diagram when the labels layer is active.
40
+ * `.Description` -- Supplementary text (e.g. a signal reference). Displayed as a secondary annotation when the labels layer is active.
41
+
42
+ When labels are **not** shown, the bare identifier (e.g. `I1`, `O1`) is used as a compact reference. When labels **are** shown, the `.Name` replaces the identifier on the diagram, and `.Description` is shown in a smaller font below it.
43
+
44
+ === Implicit Ports
45
+
46
+ For built-in gate symbols, ports are implicit based on position:
47
+
48
+ * **AND**, **NAND**, **OR**, **NOR**, **XOR**, **XNOR**: Left-side inputs, right-side output.
49
+ * **NOT**: Left-side input, right-side output.
50
+
51
+ The number of inputs on gate symbols is determined by the expression arity.
52
+
53
+ === Port Naming Convention
54
+
55
+ When the expression `CBFPS = A AND B` is given, the renderer creates an AND gate with two implicit inputs labeled `A` and `B` and one implicit output labeled `CBFPS`.
56
+
57
+ When a port has a `.Name` attribute, that name replaces the identifier as the display label:
58
+
59
+ [literal]
60
+ ....
61
+ I1.Name = "Close Switch"
62
+ O1 = I1 AND I2
63
+ ....
64
+
65
+ In this case the input port is shown as "Close Switch" rather than `I1`.
66
+
67
+ === Port Style Attribute
68
+
69
+ An individual port's marker shape can be overridden from the document default set by `OPTION PORT_STYLE`:
70
+
71
+ [literal]
72
+ ....
73
+ I1.Style = SQUARE
74
+ O1.Style = CIRCLE
75
+ ....
76
+
77
+ When set, this overrides the `OPTION PORT_STYLE` for that specific port. Valid values are `CIRCLE` and `SQUARE`.
@@ -0,0 +1,11 @@
1
+ == Rendering Contract
2
+
3
+ The renderer (TypeScript toolchain) is responsible for:
4
+
5
+ . Parsing LDL source.
6
+ . Instantiating objects and resolving expressions into gate networks.
7
+ . Loading SVG symbols and performing attribute substitution.
8
+ . Routing wires between ports.
9
+ . Emitting SVG and PDF output.
10
+
11
+ The renderer is NOT specified by this document; only the language it consumes.
@@ -0,0 +1,9 @@
1
+ == Revision History
2
+
3
+ [cols="1m,3m,5m"]
4
+ |===
5
+ | Version | Date | Description
6
+ | 0.1.0-draft | 2026-06-13 | Initial specification
7
+ | 0.2.0-draft | 2026-06-13 | Added port metadata (.Name, .Description), layout rules, layer visibility
8
+ | 0.3.0-draft | 2026-06-14 | Added OPTION directives, inversion bubbles, input bars, port styles, minimum dogleg height, wire-wire crossing avoidance
9
+ |===
@@ -0,0 +1,83 @@
1
+ == Styling
2
+
3
+ === ID-Based Selection
4
+
5
+ Every object declared with `#ID` can be styled via CSS:
6
+
7
+ [literal]
8
+ ....
9
+ AND#G1 { stroke: red; }
10
+ SVT#1 { fill: yellow; }
11
+ ....
12
+
13
+ === CSS Injection
14
+
15
+ Style rules are provided in the LDL source:
16
+
17
+ [literal]
18
+ ....
19
+ STYLE
20
+ #G1 { stroke: red; stroke-width: 3; }
21
+ #T2 { fill: #ffffcc; }
22
+ END STYLE
23
+ ....
24
+
25
+ Or loaded from an external stylesheet:
26
+
27
+ [literal]
28
+ ....
29
+ STYLESHEET "path/to/styles.css"
30
+ ....
31
+
32
+ === Default CSS Classes
33
+
34
+ The renderer MUST apply the following CSS classes:
35
+
36
+ [cols="1m,3m"]
37
+ |===
38
+ | Class | Applied to
39
+ | `ldl-symbol` | Every symbol `<g>` element
40
+ | `ldl-port` | Every port marker
41
+ | `ldl-input` | Input port markers
42
+ | `ldl-output` | Output port markers
43
+ | `ldl-attribute` | Attribute text elements
44
+ | `ldl-id` | ID/identifier label text elements (ids layer)
45
+ | `ldl-label` | `.Name` and `.Description` text elements (labels layer)
46
+ | `ldl-name` | Port `.Name` text elements
47
+ | `ldl-description` | Port `.Description` text elements
48
+ | `ldl-wire` | Wire/connection path elements
49
+ | `ldl-gate-and` | AND gate symbols
50
+ | `ldl-gate-or` | OR gate symbols
51
+ | `ldl-gate-not` | NOT gate symbols
52
+ | `ldl-bubble` | Inversion circle on a gate input port (BUBBLES mode)
53
+ | `ldl-input-bar` | Vertical input bar on a multi-input gate (BARS mode)
54
+ | `ldl-bar-stub` | Horizontal stub connecting a bar tap to the gate body (BARS mode)
55
+ | `ldl-port-circle` | Circular port marker (PORT_STYLE = CIRCLE)
56
+ | `ldl-port-square` | Square port marker (PORT_STYLE = SQUARE)
57
+ | `ldl-math` | Inline TeX math `<g>` group within a label
58
+ |===
59
+
60
+ Selectors combining these classes with `#ID` provide fine-grained control:
61
+
62
+ [source,css]
63
+ ----
64
+ .ldl-gate-and#G1 { fill: blue; }
65
+ .ldl-port.ldl-output { fill: green; }
66
+ ----
67
+
68
+ === Layer Visibility
69
+
70
+ Layers are toggled via CSS on the root `<svg>` element:
71
+
72
+ [source,css]
73
+ ----
74
+ /* Show labels, hide bare IDs */
75
+ svg.ldl-layer-labels .ldl-label { display: block; }
76
+ svg.ldl-layer-labels .ldl-id { display: none; }
77
+
78
+ /* Hide labels, show bare IDs */
79
+ svg:not(.ldl-layer-labels) .ldl-label { display: none; }
80
+ svg:not(.ldl-layer-labels) .ldl-id { display: block; }
81
+ ----
82
+
83
+ The renderer provides UI controls to toggle these layers.
@@ -0,0 +1,149 @@
1
+ == SVG Symbol Specification
2
+
3
+ === Structure
4
+
5
+ Each SVG symbol file MUST contain:
6
+
7
+ . A `<g>` element with class `ldl-symbol`.
8
+ . One `<circle>` or `<rect>` per port, with class `ldl-port` and a `data-port` attribute matching the port name.
9
+ . Optional `<text>` elements with `{{PLACEHOLDER}}` for attribute substitution.
10
+ . A `<text>` element with `{{ID}}` to show the object's identifier label.
11
+
12
+ === Port Markers
13
+
14
+ Port markers indicate where connections attach:
15
+
16
+ [literal]
17
+ ....
18
+ <circle class="ldl-port ldl-input" data-port="in" cx="0" cy="20" r="3"/>
19
+ <circle class="ldl-port ldl-output" data-port="out" cx="80" cy="20" r="3"/>
20
+ ....
21
+
22
+ === Attribute Placeholders
23
+
24
+ [literal]
25
+ ....
26
+ <text class="ldl-attribute" x="40" y="30">{{PICKUP}}</text>
27
+ <text class="ldl-attribute" x="40" y="45">{{DROPOUT}}</text>
28
+ ....
29
+
30
+ === ID Label Placeholder
31
+
32
+ [literal]
33
+ ....
34
+ <text class="ldl-id" x="40" y="10">{{ID}}</text>
35
+ ....
36
+
37
+ === Name and Description Labels
38
+
39
+ For input and output ports, the renderer generates labels from `.Name` and `.Description` attributes:
40
+
41
+ [literal]
42
+ ....
43
+ <text class="ldl-label ldl-name" x="0" y="-5">{{NAME}}</text>
44
+ <text class="ldl-label ldl-description" x="0" y="12" font-size="9">{{DESCRIPTION}}</text>
45
+ ....
46
+
47
+ Elements with class `ldl-label` are part of the *labels layer* and controlled by the `ldl-layer-labels` CSS class.
48
+ Elements with class `ldl-id` are part of the *ids layer* and controlled by the `ldl-layer-ids` CSS class.
49
+
50
+ === SVG File Example
51
+
52
+ A minimal AND gate symbol (`symbols/and.svg`):
53
+
54
+ [source,xml]
55
+ ----
56
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 60">
57
+ <g class="ldl-symbol">
58
+ <path d="M0,0 L40,0 A30,30 0 0,1 40,60 L0,60 Z"
59
+ fill="white" stroke="black" stroke-width="2"/>
60
+ <text class="ldl-id" x="20" y="-5">{{ID}}</text>
61
+ <circle class="ldl-port ldl-input" data-port="in1" cx="0" cy="15" r="3"/>
62
+ <circle class="ldl-port ldl-input" data-port="in2" cx="0" cy="45" r="3"/>
63
+ <circle class="ldl-port ldl-output" data-port="out" cx="100" cy="30" r="3"/>
64
+ </g>
65
+ </svg>
66
+ ----
67
+
68
+ === Inversion Bubble Rendering
69
+
70
+ When `OPTION INVERSION = BUBBLES` is set, each bubbled port renders as an open circle with radius `BUBBLE_R` (4px).
71
+
72
+ ==== Input-Side Bubble
73
+
74
+ An input-side bubble (on a gate input port or output node's input port) renders as:
75
+
76
+ . An open circle (stroke, no fill) with radius `BUBBLE_R`.
77
+ . Position: centered on the port Y, offset left of the gate body edge. The bubbled input port shifts left by `BUBBLE_R * 2` so that the bubble sits between the wire endpoint and the gate body.
78
+ . The wire connects to the left edge of the bubble.
79
+ . The bubble's right edge touches the gate body edge.
80
+
81
+ SVG example (input-side bubble on a gate):
82
+
83
+ [source,xml]
84
+ ----
85
+ <circle class="ldl-bubble ldl-input" data-port="in_1"
86
+ cx="-8" cy="30" r="4"
87
+ fill="none" stroke="#2c3e50" stroke-width="1.5"/>
88
+ ----
89
+
90
+ ==== Output-Side Bubble
91
+
92
+ An output-side bubble (on a gate output port) renders as:
93
+
94
+ . An open circle (stroke, no fill) with radius `BUBBLE_R`.
95
+ . Position: centered on the port Y, offset right of the gate body edge. The bubbled output port shifts right by `BUBBLE_R * 2` so that the bubble sits between the gate body and the wire start point.
96
+ . The gate body's right edge connects to the bubble's left edge (conceptually; the wire starts from the bubble's right edge).
97
+ . The wire starts from the right edge of the bubble.
98
+
99
+ SVG example (output-side bubble on a gate):
100
+
101
+ [source,xml]
102
+ ----
103
+ <circle class="ldl-bubble ldl-output" data-port="out"
104
+ cx="108" cy="30" r="4"
105
+ fill="none" stroke="#2c3e50" stroke-width="1.5"/>
106
+ ----
107
+
108
+ The wire from a bubbled output port starts at the bubble's right edge (`cx + BUBBLE_R`), not at the gate body edge.
109
+
110
+ === Input Bar Rendering
111
+
112
+ When `OPTION GATE_INPUT_STYLE = BARS` is set, multi-input gates (3+ inputs) render a vertical input bar:
113
+
114
+ . A vertical line from `(barX, gateTop)` to `(barX, gateBottom)`, where `barX = gateX - 12`.
115
+ . Stroke-width: 2.5, stroke: `#2c3e50`.
116
+ . Horizontal stubs at each bar tap Y position, from `barX` to `gateX - 6`.
117
+ . Input wires connect at the left end of each stub (`barX`, tapY).
118
+
119
+ SVG example:
120
+
121
+ [source,xml]
122
+ ----
123
+ <!-- Vertical input bar -->
124
+ <line class="ldl-input-bar" x1="-12" y1="0" x2="-12" y2="60"
125
+ stroke="#2c3e50" stroke-width="2.5"/>
126
+ <!-- Horizontal stub for 3rd input -->
127
+ <line class="ldl-bar-stub" x1="-12" y1="30" x2="-6" y2="30"
128
+ stroke="#34495e" stroke-width="2.5"/>
129
+ ----
130
+
131
+ === Port Style Rendering
132
+
133
+ When `OPTION PORT_STYLE = CIRCLE` (default), ports render as circles with `r=3`:
134
+
135
+ [source,xml]
136
+ ----
137
+ <circle class="ldl-port ldl-input ldl-port-circle" cx="0" cy="30" r="3"
138
+ fill="#2c3e50"/>
139
+ ----
140
+
141
+ When `OPTION PORT_STYLE = SQUARE`, ports render as 5×5 squares:
142
+
143
+ [source,xml]
144
+ ----
145
+ <rect class="ldl-port ldl-input ldl-port-square" x="-2.5" y="27.5" width="5" height="5"
146
+ fill="#2c3e50"/>
147
+ ----
148
+
149
+ Individual ports can override the document default via `.Style = SQUARE` or `.Style = CIRCLE`.
@@ -0,0 +1,143 @@
1
+ [[symbol-definitions]]
2
+ == Symbol Definitions
3
+
4
+ === Overview
5
+
6
+ A symbol definition describes a reusable SVG component. It specifies the SVG asset, its ports, its default attributes, and any logic it encapsulates.
7
+
8
+ === Symbol Definition Syntax
9
+
10
+ [literal]
11
+ ....
12
+ SYMBOL SYMBOL_NAME
13
+ SVG = "path/to/symbol.svg"
14
+ PORT INPUT name1
15
+ PORT INPUT name2
16
+ PORT OUTPUT name3
17
+ ATTRIBUTE name1 = default_value1
18
+ ATTRIBUTE name2 = default_value2
19
+ LOGIC = boolean_expression
20
+ END SYMBOL
21
+ ....
22
+
23
+ === Fields
24
+
25
+ [cols="1m,4m,3m"]
26
+ |===
27
+ | Field | Description | Required
28
+ | `SVG` | Path to the SVG file for this symbol | Yes
29
+ | `PORT` | A port declaration with direction and name | Yes (at least one)
30
+ | `ATTRIBUTE` | A default attribute with name and value | No
31
+ | `LOGIC` | An embedded boolean expression defining the symbol's internal logic | No
32
+ |===
33
+
34
+ === Port Declaration in Symbol
35
+
36
+ [literal]
37
+ ....
38
+ PORT DIRECTION NAME [: POSITION]
39
+ ....
40
+
41
+ Where:
42
+
43
+ * `DIRECTION` is `INPUT`, `OUTPUT`, or `BIDI`.
44
+ * `NAME` is the port identifier.
45
+ * `POSITION` is an optional side descriptor: `LEFT`, `RIGHT`, `TOP`, `BOTTOM`. Default is `LEFT` for inputs, `RIGHT` for outputs.
46
+
47
+ === Built-in Symbols
48
+
49
+ The following symbols do not require explicit definition; they are always available.
50
+
51
+ ==== AND Gate
52
+
53
+ [literal]
54
+ ....
55
+ SYMBOL AND
56
+ SVG = "builtin:and"
57
+ PORT INPUT in
58
+ PORT OUTPUT out
59
+ LOGIC = out = in_0 AND in_1 AND ... AND in_n
60
+ END SYMBOL
61
+ ....
62
+
63
+ The AND symbol supports variable fan-in. The SVG is scaled accordingly.
64
+
65
+ ==== OR Gate
66
+
67
+ [literal]
68
+ ....
69
+ SYMBOL OR
70
+ SVG = "builtin:or"
71
+ PORT INPUT in
72
+ PORT OUTPUT out
73
+ LOGIC = out = in_0 OR in_1 OR ... OR in_n
74
+ END SYMBOL
75
+ ....
76
+
77
+ ==== NOT Gate
78
+
79
+ [literal]
80
+ ....
81
+ SYMBOL NOT
82
+ SVG = "builtin:not"
83
+ PORT INPUT in
84
+ PORT OUTPUT out
85
+ LOGIC = out = NOT in
86
+ END SYMBOL
87
+ ....
88
+
89
+ ==== NAND, NOR, XOR, XNOR Gates
90
+
91
+ Each has the same port convention as AND/OR, with the corresponding logic.
92
+
93
+ === Inversion Bubbles
94
+
95
+ When `OPTION INVERSION = BUBBLES` is set, `NOT` operations are rendered as inversion bubbles instead of separate NOT gate symbols. Bubbles may appear on **input ports** or **output ports** depending on where the inversion occurs in the expression.
96
+
97
+ ==== Input-Side Bubbles
98
+
99
+ Any input port that receives the output of a `NOT` operation is marked with an inversion circle (bubble). The bubble is drawn at the input port position with a radius of `BUBBLE_R` (4px). The NOT gate symbol is not rendered; instead, the wire from the NOT's source connects directly to the bubbled input.
100
+
101
+ For AND and OR gates with bubbled inputs, the gate body remains unchanged. The bubble is rendered as an open circle (stroke only, no fill) overlapping the input port position on the left side of the gate.
102
+
103
+ ==== Output-Side Bubbles
104
+
105
+ When a `NOT` operation wraps a gate's output (e.g., `O1 = NOT (I1 AND I2)`), the bubble appears on the gate's output port. The bubble is drawn at the output port position with radius `BUBBLE_R` (4px), offset to the right of the gate body edge. The wire from the bubbled output connects directly to the downstream port.
106
+
107
+ Similarly, when a `NOT` operation feeds directly into an output (e.g., `O1 = NOT I1`), the bubble appears on the output node's input port.
108
+
109
+ The bubble on an output port is rendered as an open circle (stroke only, no fill) to the right of the gate body.
110
+
111
+ ==== Double-Inversion Cancellation
112
+
113
+ Consecutive `NOT` operations are simplified before rendering:
114
+
115
+ * An even number of `NOT` operations cancels out entirely (no bubble rendered).
116
+ * An odd number of `NOT` operations reduces to a single inversion (one bubble rendered).
117
+
118
+ === Input Bars
119
+
120
+ When `OPTION GATE_INPUT_STYLE = BARS` is set, gates with more than two inputs render as follows:
121
+
122
+ . The gate body stays at the 2-input base height (44px for AND/OR).
123
+ . The first two inputs render as normal input ports on the left side of the gate.
124
+ . Additional inputs connect via a *vertical input bar* drawn along the left edge of the gate body.
125
+ . The vertical bar extends from the top of the gate body to the bottom.
126
+ . Additional input wires tap into the bar at evenly-spaced Y positions along the bar.
127
+ . The bar is drawn as a vertical line segment (stroke-width 2.5) with short horizontal stubs connecting it to the gate body edge.
128
+
129
+ The vertical bar is a visual convenience: it allows multi-input logic without expanding the gate symbol. The logical wiring is unchanged -- each input still connects to its source signal.
130
+
131
+ === Custom Symbol Example
132
+
133
+ [literal]
134
+ ....
135
+ SYMBOL SVT
136
+ SVG = "symbols/svt.svg"
137
+ PORT INPUT in
138
+ PORT OUTPUT out
139
+ ATTRIBUTE PICKUP = "0 s"
140
+ ATTRIBUTE DROPOUT = "0 s"
141
+ LOGIC = out = TIMER(in, PICKUP, DROPOUT)
142
+ END SYMBOL
143
+ ....
@@ -0,0 +1,31 @@
1
+ == Templates
2
+
3
+ A *template* is a pre-packaged symbol definition bundled with an SVG asset. Templates allow common patterns to be reused across projects.
4
+
5
+ === Template Declaration
6
+
7
+ [literal]
8
+ ....
9
+ IMPORT TEMPLATE "path/to/template.ldlt" AS name
10
+ ....
11
+
12
+ This loads a template file which contains one or more symbol definitions.
13
+
14
+ === Template File Format
15
+
16
+ A template file (`.ldlt`) contains symbol definitions:
17
+
18
+ [literal]
19
+ ....
20
+ SYMBOL TIMER
21
+ SVG = "timer.svg"
22
+ PORT INPUT in
23
+ PORT OUTPUT out
24
+ ATTRIBUTE PICKUP = "0 s"
25
+ ATTRIBUTE DROPOUT = "0 s"
26
+ END SYMBOL
27
+ ....
28
+
29
+ === Template Resolution
30
+
31
+ The renderer resolves SVG paths relative to the template's directory. Built-in symbols (`builtin:*`) are resolved by the renderer itself.
package/spec/spec.adoc ADDED
@@ -0,0 +1,49 @@
1
+ = Logic Diagram Language (LDL) Specification
2
+ :toc: macro
3
+ :toclevels: 4
4
+ :sectnums:
5
+ :sectlinks
6
+ :version: 0.3.0-draft
7
+ :revdate: 2026-06-14
8
+
9
+ toc::[]
10
+
11
+ include::sections/introduction.adoc[]
12
+
13
+ include::sections/lexical-conventions.adoc[]
14
+
15
+ include::sections/options.adoc[]
16
+
17
+ include::sections/expressions.adoc[]
18
+
19
+ include::sections/function-blocks.adoc[]
20
+
21
+ include::sections/objects.adoc[]
22
+
23
+ include::sections/ports.adoc[]
24
+
25
+ include::sections/attributes.adoc[]
26
+
27
+ include::sections/symbol-definitions.adoc[]
28
+
29
+ include::sections/templates.adoc[]
30
+
31
+ include::sections/svg-symbol-specification.adoc[]
32
+
33
+ include::sections/styling.adoc[]
34
+
35
+ include::sections/layout-rules.adoc[]
36
+
37
+ include::sections/hyperlinks.adoc[]
38
+
39
+ include::sections/connections.adoc[]
40
+
41
+ include::sections/examples.adoc[]
42
+
43
+ include::sections/grammar.adoc[]
44
+
45
+ include::sections/file-extension.adoc[]
46
+
47
+ include::sections/rendering-contract.adoc[]
48
+
49
+ include::sections/revision-history.adoc[]