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