@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,47 @@
|
|
|
1
|
+
// Browser-only helpers that rasterise a rendered SVG string (from `renderDiagram`) to a PNG or PDF.
|
|
2
|
+
// They use the DOM (`DOMParser`, `Image`, `<canvas>`), so they only run in a browser; in Node use
|
|
3
|
+
// `renderDiagram` for SVG and rasterise with your own toolchain (e.g. resvg, sharp, puppeteer).
|
|
4
|
+
function svgViewBox(svg) {
|
|
5
|
+
const doc = new DOMParser().parseFromString(svg, 'image/svg+xml');
|
|
6
|
+
const el = doc.documentElement;
|
|
7
|
+
const vb = el.getAttribute('viewBox')?.split(/\s+/).map(Number) ?? [0, 0, 800, 400];
|
|
8
|
+
return { el, w: vb[2], h: vb[3] };
|
|
9
|
+
}
|
|
10
|
+
// Draw the SVG onto a white canvas at `scale`× its intrinsic size. Resolves the canvas once painted.
|
|
11
|
+
function svgToCanvas(svg, scale) {
|
|
12
|
+
const { el, w, h } = svgViewBox(svg);
|
|
13
|
+
const canvas = document.createElement('canvas');
|
|
14
|
+
canvas.width = Math.max(1, Math.round(w * scale));
|
|
15
|
+
canvas.height = Math.max(1, Math.round(h * scale));
|
|
16
|
+
const ctx = canvas.getContext('2d');
|
|
17
|
+
ctx.fillStyle = '#ffffff';
|
|
18
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
19
|
+
const data = new XMLSerializer().serializeToString(el);
|
|
20
|
+
const src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(data)));
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
const img = new Image();
|
|
23
|
+
img.onload = () => { ctx.drawImage(img, 0, 0, canvas.width, canvas.height); resolve(canvas); };
|
|
24
|
+
img.onerror = () => reject(new Error('Failed to rasterise SVG'));
|
|
25
|
+
img.src = src;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Rasterise an SVG string to a PNG Blob at `scale`× the diagram's intrinsic pixel size (default 2×).
|
|
30
|
+
* Browser only.
|
|
31
|
+
*/
|
|
32
|
+
export async function svgToPngBlob(svg, scale = 2) {
|
|
33
|
+
const canvas = await svgToCanvas(svg, scale);
|
|
34
|
+
return new Promise((resolve, reject) => canvas.toBlob(b => (b ? resolve(b) : reject(new Error('Canvas toBlob failed'))), 'image/png'));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Rasterise an SVG string into a single-page PDF Blob sized to the diagram (orientation follows the
|
|
38
|
+
* aspect ratio). Loads `jspdf` on demand. Browser only.
|
|
39
|
+
*/
|
|
40
|
+
export async function svgToPdfBlob(svg, scale = 2) {
|
|
41
|
+
const { w, h } = svgViewBox(svg);
|
|
42
|
+
const canvas = await svgToCanvas(svg, scale);
|
|
43
|
+
const { jsPDF } = await import('jspdf');
|
|
44
|
+
const pdf = new jsPDF({ orientation: w > h ? 'landscape' : 'portrait', unit: 'px', format: [w, h] });
|
|
45
|
+
pdf.addImage(canvas.toDataURL('image/png'), 'PNG', 0, 0, w, h);
|
|
46
|
+
return pdf.output('blob');
|
|
47
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { parse } from './parser/index.js';
|
|
2
|
+
export { renderDiagram } from './renderer/svg-renderer.js';
|
|
3
|
+
export { layoutDiagram, findWireCrossings } from './renderer/layout.js';
|
|
4
|
+
export { resolveOptions, DEFAULT_OPTIONS } from './parser/ast.js';
|
|
5
|
+
export type { RenderOptions, InversionStyle, PortStyle, GateInputStyle, OutputOrder, InputOrder, Compactness, ColumnSpacing, Diagram, DiagramOutput, GateType, LogicNode, PortNode, GateNode, SymbolRefNode, BlockNode, BlockType, PortMeta, ObjectDecl, AttributeDecl, ConnectDecl, StyleDecl, OptionDecl, ParseResult, ParseError, Position, } from './parser/ast.js';
|
|
6
|
+
export type { LayoutResult, LayoutNode, LayoutPort, LayoutWire, LayoutJunction, LayoutLabel, WireCrossing, } from './renderer/layout.js';
|
|
7
|
+
export { LIGHT_DIAGRAM, DARK_DIAGRAM } from './theme/themes.js';
|
|
8
|
+
export type { DiagramTheme } from './theme/themes.js';
|
|
9
|
+
export { EXAMPLES, EXAMPLE_NAMES } from './examples.js';
|
|
10
|
+
export { svgToPngBlob, svgToPdfBlob } from './export-image.js';
|
package/dist/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>LDL - Logic Diagram Language</title>
|
|
7
|
+
<script type="module" crossorigin src="./assets/index-CseDKK8b.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="./assets/index-h3L11pUv.css">
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<ldl-app></ldl-app>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Public library API for @openpowershift/logic-diagram-language.
|
|
2
|
+
//
|
|
3
|
+
// Pipeline: LDL source ── parse ──▶ Diagram ── layoutDiagram ──▶ LayoutResult
|
|
4
|
+
// └──── renderDiagram ──▶ SVG string
|
|
5
|
+
//
|
|
6
|
+
// renderDiagram runs the layout internally, so the common path is just parse → renderDiagram.
|
|
7
|
+
// SVG rendering is isomorphic (works in Node and the browser). The optional PNG/PDF helpers in
|
|
8
|
+
// ./export-image are browser-only (they rasterise the SVG through a <canvas>).
|
|
9
|
+
export { parse } from './parser/index.js';
|
|
10
|
+
export { renderDiagram } from './renderer/svg-renderer.js';
|
|
11
|
+
export { layoutDiagram, findWireCrossings } from './renderer/layout.js';
|
|
12
|
+
export { resolveOptions, DEFAULT_OPTIONS } from './parser/ast.js';
|
|
13
|
+
// Themes.
|
|
14
|
+
export { LIGHT_DIAGRAM, DARK_DIAGRAM } from './theme/themes.js';
|
|
15
|
+
// Ready-made example sources (a map of name → LDL source) used by the playground and docs.
|
|
16
|
+
export { EXAMPLES, EXAMPLE_NAMES } from './examples.js';
|
|
17
|
+
// Browser-only raster/PDF export helpers (rasterise an SVG string via <canvas>).
|
|
18
|
+
export { svgToPngBlob, svgToPdfBlob } from './export-image.js';
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
export type GateType = 'AND' | 'OR' | 'NOT' | 'NAND' | 'NOR' | 'XOR' | 'XNOR';
|
|
2
|
+
export type InversionStyle = 'GATES' | 'BUBBLES';
|
|
3
|
+
export type PortStyle = 'CIRCLE' | 'SQUARE';
|
|
4
|
+
export type GateInputStyle = 'EXPAND' | 'BARS';
|
|
5
|
+
export type OutputOrder = 'DECLARATION' | 'AUTO';
|
|
6
|
+
export type InputOrder = 'DECLARATION' | 'AUTO';
|
|
7
|
+
export type Compactness = 'COMPACT' | 'COMPACT_H' | 'COMPACT_V' | 'NORMAL' | 'SPACIOUS';
|
|
8
|
+
export type ColumnSpacing = 'UNIFORM' | 'ADAPTIVE';
|
|
9
|
+
export interface RenderOptions {
|
|
10
|
+
inversion: InversionStyle;
|
|
11
|
+
portStyle: PortStyle;
|
|
12
|
+
gateInputStyle: GateInputStyle;
|
|
13
|
+
outputOrder: OutputOrder;
|
|
14
|
+
inputOrder: InputOrder;
|
|
15
|
+
compactness: Compactness;
|
|
16
|
+
compactnessFactors?: [number, number];
|
|
17
|
+
columnSpacing: ColumnSpacing;
|
|
18
|
+
margin: number;
|
|
19
|
+
wireLabelLeader: boolean;
|
|
20
|
+
strokeWidth?: number;
|
|
21
|
+
hideJunctions: boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare const DEFAULT_OPTIONS: RenderOptions;
|
|
24
|
+
export interface Position {
|
|
25
|
+
line: number;
|
|
26
|
+
column: number;
|
|
27
|
+
offset: number;
|
|
28
|
+
}
|
|
29
|
+
export interface PortNode {
|
|
30
|
+
kind: 'port';
|
|
31
|
+
name: string;
|
|
32
|
+
pos?: Position;
|
|
33
|
+
}
|
|
34
|
+
export interface GateNode {
|
|
35
|
+
kind: 'gate';
|
|
36
|
+
gateType: GateType;
|
|
37
|
+
id?: string;
|
|
38
|
+
inputs: LogicNode[];
|
|
39
|
+
pos?: Position;
|
|
40
|
+
}
|
|
41
|
+
export interface SymbolRefNode {
|
|
42
|
+
kind: 'symbolRef';
|
|
43
|
+
symbolName: string;
|
|
44
|
+
id?: string;
|
|
45
|
+
portName?: string;
|
|
46
|
+
pos?: Position;
|
|
47
|
+
}
|
|
48
|
+
export type BlockType = 'TIMER' | 'SR' | 'RISING' | 'FALLING' | 'COMPARE' | 'FB';
|
|
49
|
+
export interface BlockNode {
|
|
50
|
+
kind: 'block';
|
|
51
|
+
blockType: BlockType;
|
|
52
|
+
id?: string;
|
|
53
|
+
inputs: LogicNode[];
|
|
54
|
+
inputLabels?: (string | undefined)[];
|
|
55
|
+
params: Record<string, string>;
|
|
56
|
+
port?: string;
|
|
57
|
+
pos?: Position;
|
|
58
|
+
}
|
|
59
|
+
export type LogicNode = PortNode | GateNode | SymbolRefNode | BlockNode;
|
|
60
|
+
export interface DiagramOutput {
|
|
61
|
+
name: string;
|
|
62
|
+
expression: LogicNode;
|
|
63
|
+
pos?: Position;
|
|
64
|
+
}
|
|
65
|
+
export interface ObjectDecl {
|
|
66
|
+
symbolName: string;
|
|
67
|
+
id?: string;
|
|
68
|
+
pos?: Position;
|
|
69
|
+
}
|
|
70
|
+
export interface PortMeta {
|
|
71
|
+
identifier: string;
|
|
72
|
+
property: 'Name' | 'Description' | 'Style' | 'Out';
|
|
73
|
+
value: string;
|
|
74
|
+
pos?: Position;
|
|
75
|
+
}
|
|
76
|
+
export interface AttributeDecl {
|
|
77
|
+
objectRef: string;
|
|
78
|
+
id?: string;
|
|
79
|
+
attributeName: string;
|
|
80
|
+
value: string;
|
|
81
|
+
pos?: Position;
|
|
82
|
+
}
|
|
83
|
+
export interface ConnectDecl {
|
|
84
|
+
fromObject: string;
|
|
85
|
+
fromId?: string;
|
|
86
|
+
fromPort: string;
|
|
87
|
+
toObject: string;
|
|
88
|
+
toId?: string;
|
|
89
|
+
toPort: string;
|
|
90
|
+
}
|
|
91
|
+
export interface StyleDecl {
|
|
92
|
+
css: string;
|
|
93
|
+
}
|
|
94
|
+
export interface OptionDecl {
|
|
95
|
+
name: string;
|
|
96
|
+
value: string;
|
|
97
|
+
pos?: Position;
|
|
98
|
+
}
|
|
99
|
+
export interface Diagram {
|
|
100
|
+
outputs: DiagramOutput[];
|
|
101
|
+
objects: ObjectDecl[];
|
|
102
|
+
portMeta: PortMeta[];
|
|
103
|
+
attributes: AttributeDecl[];
|
|
104
|
+
connections: ConnectDecl[];
|
|
105
|
+
styles: StyleDecl[];
|
|
106
|
+
options: OptionDecl[];
|
|
107
|
+
}
|
|
108
|
+
export interface ParseError {
|
|
109
|
+
message: string;
|
|
110
|
+
line: number;
|
|
111
|
+
column: number;
|
|
112
|
+
offset: number;
|
|
113
|
+
}
|
|
114
|
+
export interface ParseResult {
|
|
115
|
+
diagram: Diagram;
|
|
116
|
+
errors: ParseError[];
|
|
117
|
+
}
|
|
118
|
+
export declare function resolveOptions(optionDecls: OptionDecl[]): RenderOptions;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export const DEFAULT_OPTIONS = {
|
|
2
|
+
inversion: 'GATES',
|
|
3
|
+
portStyle: 'CIRCLE',
|
|
4
|
+
gateInputStyle: 'EXPAND',
|
|
5
|
+
// AUTO input/output ordering and ADAPTIVE column spacing are the defaults: they minimise wire
|
|
6
|
+
// crossings and pack columns to their content (narrower diagrams) with no downside on the corpus.
|
|
7
|
+
// Set the DECLARATION / UNIFORM values explicitly per diagram to opt back out.
|
|
8
|
+
outputOrder: 'AUTO',
|
|
9
|
+
inputOrder: 'AUTO',
|
|
10
|
+
compactness: 'NORMAL',
|
|
11
|
+
columnSpacing: 'ADAPTIVE',
|
|
12
|
+
margin: 8,
|
|
13
|
+
wireLabelLeader: true,
|
|
14
|
+
hideJunctions: false,
|
|
15
|
+
};
|
|
16
|
+
export function resolveOptions(optionDecls) {
|
|
17
|
+
const opts = { ...DEFAULT_OPTIONS };
|
|
18
|
+
for (const decl of optionDecls) {
|
|
19
|
+
const name = decl.name.toUpperCase();
|
|
20
|
+
const value = decl.value.toUpperCase();
|
|
21
|
+
if (name === 'INVERSION' && (value === 'GATES' || value === 'BUBBLES')) {
|
|
22
|
+
opts.inversion = value;
|
|
23
|
+
}
|
|
24
|
+
else if (name === 'PORT_STYLE' && (value === 'CIRCLE' || value === 'SQUARE')) {
|
|
25
|
+
opts.portStyle = value;
|
|
26
|
+
}
|
|
27
|
+
else if (name === 'GATE_INPUT_STYLE' && (value === 'EXPAND' || value === 'BARS')) {
|
|
28
|
+
opts.gateInputStyle = value;
|
|
29
|
+
}
|
|
30
|
+
else if (name === 'OUTPUT_ORDER' && (value === 'DECLARATION' || value === 'AUTO')) {
|
|
31
|
+
opts.outputOrder = value;
|
|
32
|
+
}
|
|
33
|
+
else if (name === 'INPUT_ORDER' && (value === 'DECLARATION' || value === 'AUTO')) {
|
|
34
|
+
opts.inputOrder = value;
|
|
35
|
+
}
|
|
36
|
+
else if (name === 'MARGIN') {
|
|
37
|
+
const m = parseFloat(decl.value);
|
|
38
|
+
if (!isNaN(m) && m >= 0)
|
|
39
|
+
opts.margin = m;
|
|
40
|
+
}
|
|
41
|
+
else if (name === 'WIRE_LABEL_LEADER') {
|
|
42
|
+
if (/^(true|1|yes|on)$/i.test(value))
|
|
43
|
+
opts.wireLabelLeader = true;
|
|
44
|
+
else if (/^(false|0|no|off)$/i.test(value))
|
|
45
|
+
opts.wireLabelLeader = false;
|
|
46
|
+
}
|
|
47
|
+
else if (name === 'STROKE_WIDTH') {
|
|
48
|
+
const w = parseFloat(decl.value);
|
|
49
|
+
if (!isNaN(w) && w > 0)
|
|
50
|
+
opts.strokeWidth = w;
|
|
51
|
+
}
|
|
52
|
+
else if (name === 'HIDE_JUNCTIONS') {
|
|
53
|
+
if (/^(true|1|yes|on)$/i.test(value))
|
|
54
|
+
opts.hideJunctions = true;
|
|
55
|
+
else if (/^(false|0|no|off)$/i.test(value))
|
|
56
|
+
opts.hideJunctions = false;
|
|
57
|
+
}
|
|
58
|
+
else if (name === 'COLUMN_SPACING' && (value === 'UNIFORM' || value === 'ADAPTIVE')) {
|
|
59
|
+
opts.columnSpacing = value;
|
|
60
|
+
}
|
|
61
|
+
else if (name === 'SIZE' || name === 'COMPACTNESS') {
|
|
62
|
+
if (value === 'COMPACT' || value === 'COMPACT_H' || value === 'COMPACT_V' ||
|
|
63
|
+
value === 'NORMAL' || value === 'SPACIOUS') {
|
|
64
|
+
opts.compactness = value;
|
|
65
|
+
opts.compactnessFactors = undefined;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// Explicit factors, e.g. `COMPACTNESS = 70,70` (vertical, horizontal). A value > 3 is
|
|
69
|
+
// read as a percentage (70 -> 0.70); a value <= 3 is taken as a raw factor (0.7).
|
|
70
|
+
const nums = value.replace(/[[\]\s]/g, '').split(',').map(s => parseFloat(s)).filter(n => !isNaN(n) && n > 0);
|
|
71
|
+
if (nums.length >= 1) {
|
|
72
|
+
const f = (n) => (n > 3 ? n / 100 : n);
|
|
73
|
+
opts.compactnessFactors = [f(nums[0]), f(nums.length >= 2 ? nums[1] : nums[0])];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return opts;
|
|
79
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { parse } from './parser.js';
|
|
2
|
+
export type { GateType, LogicNode, PortNode, GateNode, SymbolRefNode, Diagram, DiagramOutput, ParseResult, ParseError, PortMeta, ObjectDecl, AttributeDecl, OptionDecl, RenderOptions, InversionStyle, PortStyle, GateInputStyle } from './ast.js';
|
|
3
|
+
export { DEFAULT_OPTIONS } from './ast.js';
|