feat: XOR-Gruppen (=) — genau eine Alternative (SPEC §3, D35)
Parser erkennt `=` als disjunktives Gate (nur mit folgendem Leerraum — `=SUMME(…)` bleibt Label); Mischungen melden mixedGate wie gehabt. Die XOR-Regel warnt je WEITERER realisierter Alternative (xorConflict mit Zeilennummer; realisiert = [~]/[/]/[x]/[^], siehe D35). Darstellung erbt die komplette any-of-Geometrie (ul class="or xor"), ergänzt um eine „1"-Plakette am Austritt der Sammelleiste — auch im Grafikexport. Legende (+ hint_root) und Warntext in allen 9 Sprachen; SPEC §11-Eintrag in §1/§3/§9 überführt; 13 neue Tests (tests/xor.test.js). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
2754fa40f6
commit
d276c94840
+31
-6
@@ -19,14 +19,19 @@ export const STATUS_BY_CODE = {
|
||||
'!': {key:'highrisk', name:'High Risk – Aufwand unklar'}
|
||||
};
|
||||
|
||||
/* Status, die als „realisiert" zählen (XOR-Regel, SPEC §3/D35): Kosten sind
|
||||
investiert oder mehr. Absicht (`[?]`, `[ ]`, `[!]`), Ablehnung (`[-]`) und
|
||||
neutrale Knoten zählen nicht. */
|
||||
const REALIZED = new Set(['arbeit', 'durchstich', 'fertig', 'prod']);
|
||||
|
||||
/* Parst den Notationstext zu { roots, warnings }.
|
||||
Jeder Knoten: {label, type:'and'|'or', optional, status, url, size, tags,
|
||||
focus, children, line}.
|
||||
Jeder Knoten: {label, type:'and'|'or'|'xor', optional, status, url, size,
|
||||
tags, focus, children, line}.
|
||||
`type` ist das Gate der Geschwistergruppe, `optional` (Zeichen `+`, SPEC §3)
|
||||
eine Eigenschaft des einzelnen Knotens: er hängt an derselben Und-Zerlegung
|
||||
(`type:'and'`), ist darin aber entbehrlich. Dadurch bleibt die
|
||||
Gemischt-Warnung unverändert richtig — sie schlägt nur an, wenn `|` mit
|
||||
`-`/`+` gemischt wird.
|
||||
Gemischt-Warnung unverändert richtig — sie schlägt an, wenn `|` oder `=`
|
||||
mit `-`/`+` (oder untereinander) gemischt wird.
|
||||
Extraktionsreihenfolge (SPEC §1): Kommentar -> Zeichen/Status -> URL -> Größe
|
||||
-> Tags -> Fokusmarke -> Label. Hierarchie über Einrückungsbreite (Tab = 2 Leerzeichen);
|
||||
Elternknoten ist die nächste vorangehende Zeile mit kleinerer Breite. */
|
||||
@@ -41,9 +46,11 @@ export function parse(text){
|
||||
/* Statusbox tolerant erfassen: irgendein einzelnes Zeichen in [ ] an der
|
||||
Statusposition. Gültige Codes -> Status; unbekannte -> Warnung + neutral
|
||||
(fehlertolerant: die Zeile geht nicht verloren). */
|
||||
const m = raw.match(/^([ \t]*)([-|+])?\s*(?:\[([^\]])\]\s*)?(.*)$/);
|
||||
/* `=` (XOR, SPEC §3) nur mit folgendem Leerraum — die Leerraum-Regel hält
|
||||
Labels wie `=SUMME(A1:B2)` heraus; `-`/`+`/`|` bleiben wie bisher. */
|
||||
const m = raw.match(/^([ \t]*)([-|+]|=(?=[ \t]))?\s*(?:\[([^\]])\]\s*)?(.*)$/);
|
||||
const width = m[1].replace(/\t/g,' ').length;
|
||||
const type = m[2] === '|' ? 'or' : 'and';
|
||||
const type = m[2] === '|' ? 'or' : m[2] === '=' ? 'xor' : 'and';
|
||||
const optional = m[2] === '+';
|
||||
const boxChar = m[3]; // undefined, wenn keine Statusbox
|
||||
|
||||
@@ -74,5 +81,23 @@ export function parse(text){
|
||||
stack.push({node, width});
|
||||
});
|
||||
|
||||
/* XOR-Regel (SPEC §3): In einer `=`-Gruppe darf genau EINE Alternative
|
||||
realisiert sein. Jede weitere wird einzeln gemeldet — die Warnung zeigt so
|
||||
auf die Zeile, die man ansehen muss, statt pauschal auf die Gruppe (D35).
|
||||
Gruppen-Gate nach dem ersten Kind, wie in der Darstellung (§3). */
|
||||
(function checkXor(node){
|
||||
const kids = node.children;
|
||||
if(kids.length && kids[0].type === 'xor'){
|
||||
let realized = 0;
|
||||
for(const k of kids){
|
||||
if(k.status && REALIZED.has(k.status.key)){
|
||||
realized++;
|
||||
if(realized > 1) warnings.push({type:'xorConflict', line:k.line, label:k.label});
|
||||
}
|
||||
}
|
||||
}
|
||||
kids.forEach(checkXor);
|
||||
})(virtualRoot);
|
||||
|
||||
return {roots: virtualRoot.children, warnings};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user