diff --git a/README.md b/README.md index 4d7e081..1de9333 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,8 @@ console.log(svgString); | `+` | Adder / Add | | `-` | Subtractor / Sub | | `*` | Multiplier / Mul | +| `?` | MUX (multiplexer) | +| `$dff` | D flip-flop | ### JavaScript Expression Rendering diff --git a/lib/draw_body.js b/lib/draw_body.js index 5093ec8..f4826f1 100644 --- a/lib/draw_body.js +++ b/lib/draw_body.js @@ -66,7 +66,94 @@ const gater2 = { } }; -function drawBody (type, ymin, ymax) { +function drawMux (ymin, ymax, dataInputYs) { + if (ymin === ymax) { ymin = -4; ymax = 4; } + + const g = ['g']; + + // Trapezoid with fixed inset=6 for consistent angle, pad=11 for label clearance + g.push(['path', { + class: 'gate', + d: 'M -16,' + (ymin - 11) + + ' L 0,' + (ymin - 5) + + ' L 0,' + (ymax + 5) + + ' L -16,' + (ymax + 11) + + ' Z' + }]); + + // Data input index labels inside the body + if (dataInputYs) { + for (let i = 0; i < dataInputYs.length; i++) { + g.push(['text', {x: -15, y: dataInputYs[i] + 4, class: 'wirename'}] + .concat(tspan.parse(String(i)))); + } + } + + return g; +} + +function drawFlipFlop (ymin, ymax, portInfo) { + if (ymin === ymax) { ymin = -4; ymax = 4; } + + const g = ['g']; + + // Rectangle body — 32px wide for dual-side labels (x: -32 to 0) + g.push(['path', { + class: 'gate', + d: 'm -32,' + (ymin - 8) + ' 32,0 0,' + (ymax - ymin + 16) + ' -32,0 z' + }]); + + // Port labels and markers — portInfo is {activePorts, portRelYs, hasQn, qPortRelY, qnPortRelY} + if (portInfo) { + const activePorts = portInfo.activePorts || {}; + const portRelYs = portInfo.portRelYs || {}; + + // Left-side input labels + if (activePorts.d && portRelYs.d != null) { + g.push(['text', {x: -30, y: portRelYs.d + 4, class: 'wirename'}, 'D']); + } + if (activePorts.s && portRelYs.s != null) { + g.push(['text', {x: -30, y: portRelYs.s + 4, class: 'wirename'}, 'S']); + } + if (activePorts.r && portRelYs.r != null) { + g.push(['text', {x: -30, y: portRelYs.r + 4, class: 'wirename'}, 'R']); + } + + // Clock input — triangle marker instead of text label + if (activePorts.clk && portRelYs.clk != null) { + const cy = portRelYs.clk; + g.push(['path', { + class: 'wire', + d: 'M -32,' + (cy - 4) + ' -26,' + cy + ' -32,' + (cy + 4) + }]); + } + + // Right-side output labels + if (portInfo.qPortRelY != null) { + g.push(['text', {x: -2, y: portInfo.qPortRelY + 4, class: 'pinname'}, 'Q']); + } + + // Qn — label + inversion bubble on right edge + if (portInfo.hasQn && portInfo.qnPortRelY != null) { + g.push(['text', {x: -2, y: portInfo.qnPortRelY + 4, class: 'pinname'}] + .concat(tspan.parse('Q\u0305'))); + // Inversion bubble at right edge + g.push(['path', { + class: 'gate', + d: 'M 4,' + portInfo.qnPortRelY + ' C 4,' + (portInfo.qnPortRelY + 1.1) + ' 3.1,' + (portInfo.qnPortRelY + 2) + ' 2,' + (portInfo.qnPortRelY + 2) + + ' 0.9,' + (portInfo.qnPortRelY + 2) + ' 0,' + (portInfo.qnPortRelY + 1.1) + ' 0,' + portInfo.qnPortRelY + + ' 0,' + (portInfo.qnPortRelY - 1.1) + ' 0.9,' + (portInfo.qnPortRelY - 2) + ' 2,' + (portInfo.qnPortRelY - 2) + + ' 3.1,' + (portInfo.qnPortRelY - 2) + ' 4,' + (portInfo.qnPortRelY - 1.1) + ' 4,' + portInfo.qnPortRelY + ' z' + }]); + } + } + + return g; +} + +function drawBody (type, ymin, ymax, dataInputYs) { + if (type === '$dff') { return drawFlipFlop(ymin, ymax, dataInputYs); } + if (type === '?') { return drawMux(ymin, ymax, dataInputYs); } if (gater1.is(type)) { return gater1.render(type); } if (gater2.is(type)) { return gater2.render(type, ymin, ymax); } return ['text', {x:-14, y:4, class: 'wirename'}].concat(tspan.parse(type)); diff --git a/lib/draw_boxes.js b/lib/draw_boxes.js index 614c0a1..d2ec720 100644 --- a/lib/draw_boxes.js +++ b/lib/draw_boxes.js @@ -19,6 +19,22 @@ function drawBoxes (tree, xmax) { spec.push([32 * (xmax - branch.x), 8 * branch.y]); } } + // Pass MUX port positions (in SVG coords) through the spec + if (tree[0].ports) { + spec.ports = tree[0].ports.map(py => 8 * py); + } + // Pass flip-flop metadata through the spec + if (tree[0].name === '$dff') { + spec.portMap = tree[0].portMap; + spec.portPositions = {}; + for (const pn of Object.keys(tree[0].portPositions)) { + spec.portPositions[pn] = 8 * tree[0].portPositions[pn]; + } + spec.hasQn = tree[0].hasQn; + spec.qnName = tree[0].qnName; + spec.qPortY = tree[0].qPortY != null ? 8 * tree[0].qPortY : null; + spec.qnPortY = tree[0].qnPortY != null ? 8 * tree[0].qnPortY : null; + } ret.push(drawGate(spec)); for (let i = 1; i < tree.length; i++) { const branch = tree[i]; diff --git a/lib/draw_gate.js b/lib/draw_gate.js index 68e7a9c..f09f09b 100644 --- a/lib/draw_gate.js +++ b/lib/draw_gate.js @@ -3,9 +3,239 @@ const tspan = require('tspan'); const drawBody = require('./draw_body.js'); +function drawMuxGate (spec) { + const ilen = spec.length; + const ret = ['g']; + + const gateX = spec[1][0]; + const gateY = spec[1][1]; + const ports = spec.ports; // SVG-coord port Y positions from layout + + // Collect actual data input positions + const dataYs = []; + const dataXs = []; + for (let i = 3; i < ilen; i++) { + dataYs.push(spec[i][1]); + dataXs.push(spec[i][0]); + } + + const numData = dataYs.length; + const portYmin = ports[0]; + const portYmax = ports[numData - 1]; + const bodyLeftX = gateX - 16; + + // Identify wires that need vertical jogs (inputY != portY) + const jogIndices = []; + for (let i = 0; i < numData; i++) { + if (dataYs[i] !== ports[i]) { + jogIndices.push(i); + } + } + const numJogs = jogIndices.length; + + // Assign staggered jog columns to avoid wire overlaps. + // Topmost port gets rightmost column (closest to body), + // bottommost gets leftmost — prevents horizontal-to-body segments + // from crossing other wires' verticals. + const inputMaxX = Math.max.apply(null, dataXs); + const colSpacing = numJogs > 0 ? (bodyLeftX - inputMaxX) / (numJogs + 1) : 0; + const jogColumnMap = {}; + for (let k = 0; k < numJogs; k++) { + jogColumnMap[jogIndices[k]] = Math.round(bodyLeftX - (k + 1) * colSpacing); + } + + // Wire each data input to its corresponding MUX port + for (let i = 0; i < numData; i++) { + const inputX = dataXs[i]; + const inputY = dataYs[i]; + const portY = ports[i]; + + if (inputY === portY) { + // Straight horizontal wire from input to body + ret.push(['g', + ['path', { + d: 'M' + inputX + ',' + inputY + ' L' + bodyLeftX + ',' + portY, + class: 'wire' + }] + ]); + } else { + // L-shaped route via assigned jog column + const jogX = jogColumnMap[i]; + ret.push(['g', + ['path', { + d: 'M' + inputX + ',' + inputY + + ' L' + jogX + ',' + inputY + + ' L' + jogX + ',' + portY + + ' L' + bodyLeftX + ',' + portY, + class: 'wire' + }] + ]); + } + } + + // Selector wire: horizontal to below gate center, then vertical up to body bottom + const selX = spec[2][0]; + const selY = spec[2][1]; + const bodyBottomAbs = gateY + (portYmax - gateY) + 8; + const selWireX = gateX - 8; + + ret.push(['g', + ['path', { + d: 'M' + selX + ',' + selY + ' L' + selWireX + ',' + selY + ' L' + selWireX + ',' + bodyBottomAbs, + class: 'wire' + }] + ]); + + // Gate body (trapezoid) sized by port positions, with port-relative index labels + const portRelYs = ports.map(py => py - gateY); + + ret.push(['g', + {transform: 'translate(' + gateX + ',' + gateY + ')'}, + ['title'].concat(tspan.parse(spec[0])), + drawBody(spec[0], portYmin - gateY, portYmax - gateY, portRelYs) + ]); + + return ret; +} + +function drawFlipFlopGate (spec) { + const ret = ['g']; + + const gateX = spec[1][0]; + const gateY = spec[1][1]; + const portMap = spec.portMap; // {d: treeIdx, clk: treeIdx, ...} + const portPositions = spec.portPositions; // {d: svgY, clk: svgY, ...} + const bodyLeftX = gateX - 32; + + // Collect input positions and their target port Y positions + const inputEntries = []; + const portNames = Object.keys(portMap); + for (let k = 0; k < portNames.length; k++) { + const pn = portNames[k]; + const treeIdx = portMap[pn]; + const inputX = spec[treeIdx + 1][0]; // spec[2], spec[3], ... (offset by +1 for gate pos) + const inputY = spec[treeIdx + 1][1]; + const portY = portPositions[pn]; + inputEntries.push({port: pn, inputX: inputX, inputY: inputY, portY: portY}); + } + + // Identify wires that need vertical jogs (inputY != portY) + const jogEntries = []; + for (let k = 0; k < inputEntries.length; k++) { + if (inputEntries[k].inputY !== inputEntries[k].portY) { + jogEntries.push(k); + } + } + const numJogs = jogEntries.length; + + // Assign staggered jog columns + const inputMaxX = inputEntries.length > 0 ? Math.max.apply(null, inputEntries.map(function (e) { return e.inputX; })) : bodyLeftX; + const colSpacing = numJogs > 0 ? (bodyLeftX - inputMaxX) / (numJogs + 1) : 0; + const jogColumnMap = {}; + for (let k = 0; k < numJogs; k++) { + jogColumnMap[jogEntries[k]] = Math.round(bodyLeftX - (k + 1) * colSpacing); + } + + // Wire each input to its port position on the body + for (let k = 0; k < inputEntries.length; k++) { + const e = inputEntries[k]; + if (e.inputY === e.portY) { + // Straight horizontal wire + ret.push(['g', + ['path', { + d: 'M' + e.inputX + ',' + e.inputY + ' L' + bodyLeftX + ',' + e.portY, + class: 'wire' + }] + ]); + } else { + // L-shaped route via jog column + const jogX = jogColumnMap[k]; + ret.push(['g', + ['path', { + d: 'M' + e.inputX + ',' + e.inputY + + ' L' + jogX + ',' + e.inputY + + ' L' + jogX + ',' + e.portY + + ' L' + bodyLeftX + ',' + e.portY, + class: 'wire' + }] + ]); + } + } + + // Q output wire — short stub from right edge of body going right + // (parent gate handles wiring from gate position to output label) + // The Q port is inside the body; the parent wire connects at gateX,gateY + + // Qn output — wire + inversion bubble + label on right side + if (spec.hasQn && spec.qnPortY != null) { + const qnY = spec.qnPortY; + // Wire from after the inversion bubble (4px) to label area + ret.push(['g', + ['path', { + d: 'M' + (gateX + 4) + ',' + qnY + ' L' + (gateX + 16) + ',' + qnY, + class: 'wire' + }] + ]); + // Qn signal name label — placed past the wire endpoint (gateX + 16) + if (spec.qnName) { + ret.push(['g', + ['text', {x: gateX + 20, y: qnY + 4, class: 'wirename'}] + .concat(tspan.parse(spec.qnName)) + ]); + } + } + + // Compute port Y range for body sizing + const allPortYs = []; + for (let k = 0; k < portNames.length; k++) { + allPortYs.push(portPositions[portNames[k]]); + } + // Include Q and Qn output positions in body range + if (spec.qPortY != null) { allPortYs.push(spec.qPortY); } + if (spec.qnPortY != null) { allPortYs.push(spec.qnPortY); } + + const portYmin = Math.min.apply(null, allPortYs); + const portYmax = Math.max.apply(null, allPortYs); + + // Build portInfo for drawBody + const portRelYs = {}; + for (let k = 0; k < portNames.length; k++) { + portRelYs[portNames[k]] = portPositions[portNames[k]] - gateY; + } + const activePorts = {}; + for (let k = 0; k < portNames.length; k++) { + activePorts[portNames[k]] = true; + } + + const portInfo = { + activePorts: activePorts, + portRelYs: portRelYs, + hasQn: spec.hasQn, + qPortRelY: spec.qPortY != null ? spec.qPortY - gateY : null, + qnPortRelY: spec.qnPortY != null ? spec.qnPortY - gateY : null + }; + + // Gate body + ret.push(['g', + {transform: 'translate(' + gateX + ',' + gateY + ')'}, + ['title'].concat(tspan.parse(spec[0])), + drawBody(spec[0], portYmin - gateY, portYmax - gateY, portInfo) + ]); + + return ret; +} + // ['type', [x,y], [x,y] ... ] function drawGate (spec) { // ['type', [x,y], [x,y] ... ] + if (spec[0] === '$dff') { + return drawFlipFlopGate(spec); + } + + if (spec[0] === '?') { + return drawMuxGate(spec); + } + const ilen = spec.length; const ys = []; diff --git a/lib/render.js b/lib/render.js index f4f2815..2b69c87 100644 --- a/lib/render.js +++ b/lib/render.js @@ -1,37 +1,184 @@ 'use strict'; -function render(tree, state) { - // var y, i, ilen; +function processBranch (tree, i, state) { + const branch = tree[i]; + if (Array.isArray(branch)) { + state = render(branch, { + x: (state.x + 1), + y: state.y, + xmax: state.xmax + }); + } else { + tree[i] = { + name: branch, + x: (state.x + 1), + y: state.y + }; + state.y += 2; + } + return state; +} - state.xmax = Math.max(state.xmax, state.x); +function renderMux (tree, ilen, state) { + const numDataInputs = ilen - 2; + const portSpacing = 2; // fixed grid units between MUX ports - const y = state.y; - const ilen = tree.length; + // Render data inputs with natural spacing (each gets only the space it needs) + for (let i = 2; i < ilen; i++) { + state = processBranch(tree, i, state); + } - for (let i = 1; i < ilen; i++) { + // Compute fixed port positions independent of actual input positions + // Ensure port range is at least as tall as data input range + const dataInputYs = []; + for (let i = 2; i < ilen; i++) { const branch = tree[i]; - if (Array.isArray(branch)) { - state = render(branch, { - x: (state.x + 1), - y: state.y, - xmax: state.xmax - }); + dataInputYs.push(Array.isArray(branch) ? branch[0].y : branch.y); + } + + // Align last port with last data input so tightly-packed simple inputs + // match their ports exactly; complex inputs (with room) absorb the jog + const lastInputY = dataInputYs[dataInputYs.length - 1]; + const portStart = lastInputY - (numDataInputs - 1) * portSpacing; + const ports = []; + for (let i = 0; i < numDataInputs; i++) { + ports.push(portStart + i * portSpacing); + } + + // Center gate on port range + const portCenter = (ports[0] + ports[numDataInputs - 1]) / 2; + tree[0] = { + name: tree[0], + x: state.x, + y: Math.round(portCenter), + ports: ports + }; + + // Ensure state.y accounts for port range extending below data inputs + const portBottom = ports[ports.length - 1] + 2; + if (portBottom > state.y) { + state.y = portBottom; + } + + // Selector (position 1) placed below data inputs and ports + state = processBranch(tree, 1, state); + + return state; +} + +function renderFlipFlop (tree, state) { + const config = tree[1]; // {d, clk, s, r, qn} + + // Rebuild tree array: expand config object into positioned branches + tree.length = 1; + const portMap = {}; // port name -> tree index + let idx = 1; + + // Order: S (top), D, clk, R (bottom) — visual top-to-bottom + const inputOrder = []; + if (config.s != null) { inputOrder.push({port: 's', signal: config.s}); } + if (config.d != null) { inputOrder.push({port: 'd', signal: config.d}); } + if (config.clk != null) { inputOrder.push({port: 'clk', signal: config.clk}); } + if (config.r != null) { inputOrder.push({port: 'r', signal: config.r}); } + + // Pin gate x-position: compound inputs decrement state.x when they return + // from render(), which would drift subsequent inputs. Use a fixed gateX + // so all inputs are consistently placed at gateX + 2 (extra unit for the + // wider flip-flop body which is 32px = 1 full grid unit wide). + const gateX = state.x; + + for (let k = 0; k < inputOrder.length; k++) { + const signal = inputOrder[k].signal; + tree[idx] = signal; + portMap[inputOrder[k].port] = idx; + + if (Array.isArray(signal)) { + state = render(signal, {x: gateX + 2, y: state.y, xmax: state.xmax}); } else { - tree[i] = { - name: branch, - x: (state.x + 1), - y: state.y - }; + tree[idx] = {name: signal, x: gateX + 2, y: state.y}; + state.xmax = Math.max(state.xmax, gateX + 2); state.y += 2; } + idx++; + } + + // Restore state.x to gateX so render() decrements correctly for the parent + state.x = gateX; + + // Calculate port positions from actual rendered positions + const portPositions = {}; + for (const portName of Object.keys(portMap)) { + const treeIdx = portMap[portName]; + const branch = tree[treeIdx]; + portPositions[portName] = Array.isArray(branch) ? branch[0].y : branch.y; + } + + // Compute port Y range for body sizing + const allYs = Object.values(portPositions); + if (allYs.length === 0) { + throw new Error('$dff requires at least one input port (d, clk, s, or r)'); + } + const portYmin = Math.min.apply(null, allYs); + const portYmax = Math.max.apply(null, allYs); + + // Q and Qn output ports at fixed offsets relative to body + // Q aligns with D (or top input), Qn aligns with clk (or bottom input) + const qPortY = portPositions.d != null ? portPositions.d : portYmin; + let qnPortY = null; + if (config.qn != null) { + qnPortY = portPositions.clk != null ? portPositions.clk : portYmax; } + // Position gate at Q output port so the parent output wire aligns with Q tree[0] = { - name: tree[0], - x: state.x, - y: Math.round((y + (state.y - 2)) / 2) + name: '$dff', + x: gateX, + y: qPortY, + portMap: portMap, + portPositions: portPositions, + hasQn: config.qn != null, + qnName: config.qn || null, + qPortY: qPortY, + qnPortY: qnPortY }; + // Note: do NOT state.x-- here; render() does it after we return + return state; +} + +function render(tree, state) { + state.xmax = Math.max(state.xmax, state.x); + + const y = state.y; + const ilen = tree.length; + const isMux = (tree[0] === '?'); + const isFlop = (tree[0] === '$dff' && ilen === 2 && tree[1] !== null && typeof tree[1] === 'object' && !Array.isArray(tree[1])); + + if (isFlop) { + state = renderFlipFlop(tree, state); + } else if (isMux) { + state = renderMux(tree, ilen, state); + } else { + for (let i = 1; i < ilen; i++) { + state = processBranch(tree, i, state); + } + + if (ilen === 2 && Array.isArray(tree[1])) { + // Single compound input: align to its output position + tree[0] = { + name: tree[0], + x: state.x, + y: tree[1][0].y + }; + } else { + tree[0] = { + name: tree[0], + x: state.x, + y: Math.round((y + (state.y - 2)) / 2) + }; + } + } + state.x--; return state; } diff --git a/ref/functions.md b/ref/functions.md index 4f2d0c0..a0a693a 100644 --- a/ref/functions.md +++ b/ref/functions.md @@ -13,9 +13,32 @@ | xnor | `~^` | | add | `+` | | mul | `*` | +| mux | `?` | +| dff | `$dff` | ## structure ```js [and, a, b, [or, c, [not, d]]] ``` + +## flip-flop + +Flip-flops use a config object instead of positional inputs. Ports set to `null` or omitted are not drawn. + +```js +['q', ['$dff', { d: 'din', clk: 'clock', r: 'reset' }]] +['q', ['$dff', { d: ['&', 'a', 'b'], clk: 'clk', s: 'set', r: 'rst', qn: 'q_bar' }]] +``` + +Supported ports: + +| port | description | +|-------|------------------------------------------| +| `d` | data input | +| `clk` | clock input (drawn with triangle) | +| `s` | set input | +| `r` | reset input | +| `qn` | inverted output (drawn with bubble) | + +Any port value can be a sub-expression (e.g. `['&', 'a', 'b']`). diff --git a/report.html b/report.html index f2d7a33..8e5800f 100644 --- a/report.html +++ b/report.html @@ -1,15 +1,720 @@ -
[["x",["&","a","b"]]]- -
[["x",["&","a","b","c","d","e"]]]- -
[["x",["|","a","b"]]]- -
[["x",["^","a","b"]]]- -
[["x",["~&","a","b"]]]- -
[["x",["~|","a","b"]]]- -
[["x",["~|","a0","a1","a2",["~&","a3","a4","a5"]]]]- - \ No newline at end of file +
[ + [ + "x", + [ + "&", + "a", + "b" + ] + ] +]+ + +
[ + [ + "x", + [ + "&", + "a", + "b", + "c", + "d", + "e" + ] + ] +]+ + +
[ + [ + "x", + [ + "|", + "a", + "b" + ] + ] +]+ + +
[ + [ + "x", + [ + "^", + "a", + "b" + ] + ] +]+ + +
[ + [ + "x", + [ + "~&", + "a", + "b" + ] + ] +]+ + +
[ + [ + "x", + [ + "~|", + "a", + "b" + ] + ] +]+ + +
[ + [ + "x", + [ + "~|", + "a0", + "a1", + "a2", + [ + "~&", + "a3", + "a4", + "a5" + ] + ] + ] +]+ + +
[ + [ + "foo", + [ + 42, + 5 + ] + ] +]+ + +
[
+ [
+ "q",
+ [
+ "$dff",
+ {
+ "d": "din",
+ "clk": "clock"
+ }
+ ]
+ ]
+]
+
+
+[
+ [
+ "q",
+ [
+ "$dff",
+ {
+ "d": "din",
+ "clk": "clock",
+ "r": "reset"
+ }
+ ]
+ ]
+]
+
+
+[
+ [
+ "q",
+ [
+ "$dff",
+ {
+ "d": "din",
+ "clk": "clock",
+ "s": "set",
+ "r": "reset"
+ }
+ ]
+ ]
+]
+
+
+[
+ [
+ "q",
+ [
+ "$dff",
+ {
+ "d": "din",
+ "clk": "clock",
+ "s": "set",
+ "r": "reset",
+ "qn": "q_bar"
+ }
+ ]
+ ]
+]
+
+
+[
+ [
+ "q",
+ [
+ "$dff",
+ {
+ "d": "din",
+ "clk": "clock",
+ "qn": "q_n"
+ }
+ ]
+ ]
+]
+
+
+[
+ [
+ "q",
+ [
+ "$dff",
+ {
+ "d": [
+ "&",
+ "a",
+ "b"
+ ],
+ "clk": "clock"
+ }
+ ]
+ ]
+]
+
+
+[
+ [
+ "q",
+ [
+ "$dff",
+ {
+ "d": [
+ "|",
+ "x",
+ "y"
+ ],
+ "clk": "clk",
+ "s": "set",
+ "r": [
+ "&",
+ "rst0",
+ "rst1"
+ ],
+ "qn": "q_bar"
+ }
+ ]
+ ]
+]
+
+
+[
+ [
+ "q",
+ [
+ "$dff",
+ {
+ "d": [
+ "?",
+ "s1",
+ [
+ "?",
+ "s0",
+ "a",
+ "b",
+ "c",
+ "d"
+ ],
+ [
+ "?",
+ "s0",
+ "e",
+ "f",
+ "g",
+ "h"
+ ],
+ [
+ "?",
+ "s0",
+ "i",
+ "j",
+ "k",
+ "l"
+ ],
+ [
+ "?",
+ "s0",
+ "m",
+ "n",
+ "o",
+ "p"
+ ]
+ ],
+ "clk": "clk",
+ "r": "reset"
+ }
+ ]
+ ]
+]
+
+
+[ + [ + "x", + [ + "?", + "sel", + "a", + "b" + ] + ] +]+ + +
[ + [ + "x", + [ + "?", + "sel", + "a", + "b", + "c", + "d" + ] + ] +]+ + +
[ + [ + "x", + [ + "?", + "sel", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h" + ] + ] +]+ + +
[ + [ + "x", + [ + "?", + "s", + [ + "&", + "a", + "b" + ], + "c", + "d", + "e" + ] + ] +]+ + +
[ + [ + "x", + [ + "?", + "s", + [ + "&", + "a", + "b", + "c", + "d", + "e" + ], + "f", + "g", + "h" + ] + ] +]+ + +
[ + [ + "x", + [ + "?", + "s", + [ + "&", + "a0", + "a1", + "a2", + "a3", + "a4" + ], + [ + "|", + "b0", + "b1", + "b2", + "b3" + ], + [ + "&", + "c0", + "c1", + "c2" + ], + "d" + ] + ] +]+ + +
[ + [ + "x", + [ + "?", + "s1", + [ + "?", + "s0", + "a", + "b", + "c", + "d" + ], + [ + "?", + "s0", + "e", + "f", + "g", + "h" + ], + [ + "?", + "s0", + "i", + "j", + "k", + "l" + ], + [ + "?", + "s0", + "m", + "n", + "o", + "p" + ] + ] + ] +]+ + +
' + JSON.stringify(src, null, 4) + '\n'; + var svg = lib.renderAssign(index, {assign: src }); + res += onml.stringify(svg, 2) + '\n'; + res += '
' + JSON.stringify(src, null, 4) + '\n'; + var svg = lib.renderAssign(index, {assign: src }); + res += onml.stringify(svg, 2) + '\n'; + res += '