Fehlerbehandlung
Was ein Template tut, wenn ein Tag fehlschlägt: onerror=, default=, woher die Strategie kommt, und der eine Fehler, der niemals auffangbar ist.
Ein Prompt wird meist im unpassendsten Moment zusammengesetzt — während eine Anfrage wartet. Die interessante Frage ist deshalb nicht, ob ein Konstrukt fehlschlagen kann, sondern was der Renderer ausgibt, wenn es passiert.
Fehlerbehandlung greift zur Ausführungszeit. onerror= bestimmt, was anstelle eines
fehlschlagenden Konstrukts erscheint, statt den Render abzubrechen. Lexer- und Parser-Fehler liegen
vollständig außerhalb: Ein Konstrukt, das nicht parst, erreicht die Ausführung nie, es gibt also
nichts zu ersetzen. Diese Grenze ist das Erste, was man verinnerlichen sollte — Fehlerbehandlung
rettet keinen Syntax-Tippfehler, nur ein Fehlschlagen der Arbeit selbst.
Das Vokabular seit v0.24.0
onerror= | Gibt aus | Anmerkungen |
|---|---|---|
throw | nichts — der Fehler propagiert, der Render schlägt fehl | Der Standard, und der Rückfall für jeden unbekannten Wert. |
default | das default=-Attribut des Konstrukts | Leer, wenn kein default= vorhanden ist, also remove mit Notausgang. |
remove | leerer String | |
keepraw | den Originalquelltext des Konstrukts, wortgetreu | Leer, wenn kein Quelltext erfasst wurde — es schlägt geschlossen fehl, statt einen falschen Ausschnitt auszugeben. |
log | leerer String | Zusätzlich eine WARN-Zeile mit Tag und Fehler. |
{~exons.var name="tone" onerror="default" default="neutral" /~}Woher die Strategie kommt
Ein Dokument gibt selten für jedes Tag seine eigene Fehlerbehandlung an. Das muss es auch nicht — die Auflösung geht drei Schritte und hält beim ersten an, der antwortet:
- Das eigene
onerror=des fehlschlagenden Konstrukts (bei einem Zweig: das des Zweigs — siehe unten). - Der Kontext-Standard: die Strategie, die der Ausführungskontext meldet, gesetzt aus
WithErrorStrategyder Engine und vom Kind-Kontext eines Schleifenkörpers geerbt. throw.
Der Host bestimmt also die Haltung, und das Dokument überschreibt sie lokal. Ein Renderer für Entwürfe kann überall nachsichtig sein, ohne dass ein einziges Dokument mitmacht; ein Validator kann über denselben Dokumenten strikt sein.
Ein Schreibfehler fällt nicht durch
throw auf. onerror="remov" schlägt also hart fehl, unter einem Renderer, der nie hart fehlschlagen
soll — und der Schreibfehler ist der einzige Hinweis darauf. Engine.Validate meldet ihn als Fehler
auf jeder Form, die das Attribut beachtet; genau darum lohnt sich diese Prüfung, bevor du ein Template
auslieferst.Welche Fehler auffangbar sind seit v0.24.0
| Konstrukt | Fehlschlag |
|---|---|
| jedes Tag | für den Tag-Namen ist kein Resolver registriert |
| jedes Tag | das Validate des Resolvers weist die Attribute ab |
| jedes Tag | das Resolve des Resolvers gibt einen Fehler zurück |
exons.if / exons.elseif | die Bedingung des Zweigs lässt sich nicht auswerten |
exons.for | der in=-Pfad ist im Kontext nicht vorhanden |
exons.for | der gefundene Wert ist nicht iterierbar |
exons.for | der Host-Kontext kann keinen Kind-Kontext erzeugen |
exons.switch | der eval=-Ausdruck der Verteilung lässt sich nicht auswerten |
exons.case | der eval=-Ausdruck des Falls lässt sich nicht auswerten |
Alles andere ist ein harter Fehlschlag. Parse-Zeit-Abweisungen — ein fehlendes eval=, item= oder
in=, ein nicht-numerisches oder negatives limit=, ein ungeschlossenes oder falsch geschlossenes
Konstrukt — passieren vor der Ausführung. Und ein Fehler innerhalb eines gewählten Zweigs oder
Schleifenkörpers gehört zur Stelle dieses Knotens: Das onerror= des umgebenden Konstrukts fängt ihn
nicht ab.
Die Versionsmarkierung dieses Abschnitts ist tragend. Vor v0.24.0 lasen exons.if, exons.for und
exons.switch nur die Schlüssel, die sie brauchten, und ließen den Rest der Attribut-Map verfallen —
onerror= und default= waren an einem Block-Konstrukt also stillschweigend wirkungslos, und
jeder der obigen Fehlschläge war ein unbedingter Render-Fehler. Das Attribut zu schreiben war nicht
falsch; es tat nur nichts.
Fehlerbehandlung pro Zweig seit v0.24.0
Ein exons.elseif und ein exons.case tragen jeweils ihr eigenes onerror=, beachtet an der
Position des Zweigs statt an der des öffnenden Tags:
{~exons.if eval="input.verbose"~}
Langform
{~exons.elseif eval="input.terse" onerror="remove"~}
Kurzform
{~/exons.if~}Das ist wichtig, weil eine Bedingung das ist, was man am häufigsten falsch schreibt — und eine kaputte Bedingung im dritten Zweig einer Kette sollte nicht dem ersten zugeschrieben werden.
default= hat zwei Lesarten, die nie kollidieren
Bei exons.var und exons.input trägt derselbe Attributname zusätzlich eine Resolver-Bedeutung —
ein Lookup-Fehlschlag oder ein leerer deklarierter Wert. Dieser Pfad gibt einen Wert zurück statt
eines Fehlers, löst also auf, bevor die Fehlerbehandlung überhaupt befragt wird. default= als
Fehlerbehandlung liest nur onerror="default".
Vererbung ist niemals auffangbar seit v0.25.0
{~exons.extends~} trägt kein onerror=, und keine Strategie — auch remove nicht — macht es
überlebbar.
Vererbung wird aufgelöst, bevor irgendein Tag ausgeführt wird, denn die Elternkette entscheidet,
welches Dokument überhaupt ausgeführt wird. Es gibt keinen Knoten, dessen Fehlschlag eine Strategie
abfangen könnte. Ein nicht auflösbares extends lässt den Render fehlschlagen, unter jeder
Konfiguration.
Das ist bewusst so und nicht unfertig. Es abzuschwächen würde die nackten Block-Körper des Kindes rendern — ein anderes Dokument als das, das der Autor geschrieben hat, zurückgegeben als Erfolg. Ein Aufrufer kann das nicht erkennen, was es strikt schlechter macht als einen Fehler.
Die beiden in einer Registry unterscheiden
cuserr, und seine tag-Metadaten sind extends. Das gilt für alle drei
Arten, auf die die Auflösung fehlschlagen kann — die Deklaration ist nicht lesbar, es gibt keine
Engine zum Auflösen, oder die Kette lässt sich nicht gehen (fehlendes Eltern-Template, zirkulär, zu
tief). Verzweige nicht über den Code des Fehlers: Er wird aus dem Wortlaut der zugrunde liegenden
Meldung abgeleitet, nicht von der Bibliothek gesetzt.