refactor: VF-Linienzug-Journal-Grammatik in lib/vf_linienzug_grammatik.py gebuendelt
lib/vf_journal_export.py (Journal.dekodiere) und lib/vf_spec_export.py (SpecLeser) implementierten denselben Tokenwalk (Praeambel, Sektions- schleife, Glied-Dispatch, VF-Einheit-Unterschleife) sowie dieselben Code-Tabellen (SEITE/VARIANTE/VERTEILUNG/IM_VF/ENDPUNKT/ENDE2/ENDE3/ GEFAELLE/GF_LAENGE_MODUS, MENU_START/MENU_START_AKTUELL/MENU_WEITER, Grammatik) unabhaengig voneinander - Aenderungen an der Grammatik (wie im vorigen Commit) mussten von Hand synchron gehalten werden und sind bereits einmal auseinandergelaufen. Neuer gemeinsamer Parser-Kern (Kern/lies_journal) plus Sink-Schnittstelle (SpecSink in vf_spec_export.py, ProtokollSink in vf_journal_export.py) ersetzt die doppelte Implementierung; Code-Tabellen/Grammatik-Objekte leben jetzt nur noch in lib/vf_linienzug_grammatik.py. Journal behaelt nur noch die reine XDATA/DXF-Zerlegung, SpecSchreiber (Spec -> Journal, Gegenstueck zu Lisp/vf_spec.lsp) bleibt unveraendert in vf_spec_export.py. Zusaetzlich: pruefe_flachen_freitext schlaegt jetzt hart fehl, wenn generierter Freitext (quelle/beschreibung) ein rohes Anfuehrungszeichen enthaelt - der Lisp-seitige Flach-JSON-Leser (ssg-cfg-parse-value) kennt keine Escapes und wuerde daran die Zeichenkette (und damit die gesamte restliche Datei) falsch zerlegen. Byte-identischer Output gegen die Baseline geprueft (beide CLIs, echte Eingaben); pytest tests/test_vf_spec.py + test_vf_journal_grammatik.py unveraendert: 2 bekannte, vorbestehende Fehlschlaege, 38 bestanden. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
1bdecf0678
commit
6d15c6be13
+237
-294
@@ -14,6 +14,12 @@ ist die vollstaendige, wiederabspielbare Bau-Anleitung der Kette
|
||||
vfl-entry->string wieder zurueck und annotiert jeden Wert mit der Frage, die
|
||||
er beantwortet - abgeleitet aus dem Frageablauf von vf-linienzug-modus.
|
||||
|
||||
Den Frageablauf selbst laeuft der GEMEINSAME Parser-Kern
|
||||
(lib/vf_linienzug_grammatik.py), den sich dieses Skript mit
|
||||
lib/vf_spec_export.py teilt: dort steht die Grammatik EINMAL, hier nur noch
|
||||
die ProtokollSink mit den Annotationstexten. Frueher war der Ablauf in beiden
|
||||
Dateien von Hand nachgebaut und lief regelmaessig auseinander.
|
||||
|
||||
Bewusst OHNE ezdxf: gebraucht werden nur INSERT-Kopf (Blockname, Einfuegepunkt,
|
||||
Drehung) und die 1000er-Gruppen der XDATA - das ist reine Gruppencode-Paar-
|
||||
Lesung und funktioniert auch bei ASCII-DXF im 100-MB-Bereich.
|
||||
@@ -47,6 +53,14 @@ import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
# Grammatik (Code-Tabellen, Menue-Zuordnung) UND Frageablauf kommen aus dem
|
||||
# gemeinsamen Modul - hier steht nur noch die Annotation dazu. SEITE/VARIANTE
|
||||
# braucht zusaetzlich die Kurzbeschreibung (kurzbeschreibung): sie liest die
|
||||
# fertigen Protokolleintraege nach und hat dort nur den rohen Menue-Code.
|
||||
from vf_linienzug_grammatik import ( # noqa: E402
|
||||
GRAMMATIK_AUFZEICHNUNG, SEITE, Sink, VARIANTE, lies_journal)
|
||||
|
||||
PREFIX = "[vf_journal_export]"
|
||||
|
||||
XDATA_APP = "SSG_VF_EDIT"
|
||||
@@ -60,25 +74,6 @@ RUND = 6
|
||||
# Anlagenkuerzel in den test_ids (VF_<kuerzel>_LZ_01 ...).
|
||||
TEST_ID_ANLAGE = "hundm05"
|
||||
|
||||
# Antwort-Klartexte der Menues aus vf_linienzug.lsp (nur zur Annotation).
|
||||
JA_NEIN = {"1": "Ja", "2": "Nein"}
|
||||
SEITE = {"1": "links", "2": "rechts"}
|
||||
VARIANTE = {"1": "aussen", "2": "innen"}
|
||||
GF_MODUS = {"1": "ueber Zielhoehe", "2": "ueber Neigungswinkel"}
|
||||
ENDPUNKT3 = {"1": "Ja, nur Motorstation", "2": "Nein, weiterbauen",
|
||||
"3": "Ja, Motorstation + Kettenende am Zielpunkt"}
|
||||
KETTENENDE3 = {"1": "Ja, mit ES-Element", "2": "Ja, ohne ES-Element",
|
||||
"3": "Nein, weiterbauen"}
|
||||
KETTENENDE2 = {"1": "Ja, Separator + ES-Element", "2": "Nein, weiterbauen"}
|
||||
VERTEILUNG = {"1": "halbe Staustrecke am Ausgang", "2": "alles am Einlauf"}
|
||||
IM_VF = {"1": "Horizontaler Foerderer", "2": "Vario-Kurve",
|
||||
"3": "Auf/Ab-Foerderer"}
|
||||
MENU_START = {"1": "Neue Linie: GF", "2": "Neue Linie: Ab/Auf VF",
|
||||
"3": "Horizontal-VF", "4": "Neue Linie bis Kettenende"}
|
||||
MENU_WEITER = {"1": "GF-Bogen", "2": "Neue Linie: GF",
|
||||
"3": "Neue Linie: Ab/Auf VF", "4": "Horizontal-VF",
|
||||
"5": "Neue Linie bis Kettenende"}
|
||||
|
||||
STEP_KOMMENTAR = ("Glied-Marker (vfl-journal-mark) - beim Replay "
|
||||
"uebersprungen, Label = gewaehlter Segmenttyp")
|
||||
|
||||
@@ -148,50 +143,155 @@ def lese_vf_inserts(dxf_pfad):
|
||||
# ============================================================
|
||||
# Journal dekodieren
|
||||
# ============================================================
|
||||
# Den Frageablauf laeuft der gemeinsame Kern in lib/vf_linienzug_grammatik.py
|
||||
# (dieselbe Routine, die lib/vf_spec_export.py fuer die Spec benutzt). Hier
|
||||
# steht nur noch die ANNOTATION: zu jeder Frage die Konsolen-Vorlage aus
|
||||
# vf_linienzug.lsp und - wo die Domaenen-Kurzform nicht schon der
|
||||
# Antworttext ist - der Klartext der Antwort.
|
||||
|
||||
class Journal(object):
|
||||
"""Ein Journal (kind:payload je Zeile) in das Frage-Antwort-Protokoll
|
||||
uebersetzen. Der Ablauf folgt vf-linienzug-modus (Modus 1):
|
||||
# Prompt-Vorlagen je Frage-Schluessel (siehe Sink-Doku im Grammatik-Modul).
|
||||
# Ein Schluessel, nicht der Feldname: dasselbe Feld ("hoehe") beantwortet an
|
||||
# verschiedenen Stellen verschiedene Fragen.
|
||||
|
||||
Praeambel: Startpunkt, Starthoehe, AS-Element (Ja/Nein, Winkel, Seite)
|
||||
Hauptschleife je Glied: STEP-Marker, Menuewahl, dann die Fragen des
|
||||
gewaehlten Segmenttyps
|
||||
Innerhalb einer VF-Einheit (Horizontal-VF / Linie): Separator vor/nach,
|
||||
"Ist der Endpunkt der Foerderer?", danach Horizontal / Vario-Kurve /
|
||||
Auf-Ab bis die Einheit schliesst
|
||||
Kettenende: ES-Glied (Winkel, Seite)
|
||||
_REAL_FRAGEN = {
|
||||
"start_hoehe": "getreal Hoehe (Z) Startpunkt -> %.1f mm",
|
||||
"gf_winkel": "getreal Neigungswinkel -> %.1f Grad",
|
||||
"gf_hoehe": "getreal Zielhoehe (Z) Linienendpunkt -> %.1f mm",
|
||||
"vf_zielhoehe": "getreal Zielhoehe (Z) Linienendpunkt -> %.1f mm",
|
||||
"kettenende_hoehe": "getreal Zielhoehe (Z) am Kettenende -> %.1f mm",
|
||||
"aufab_hoehe": "getreal Zielhoehe (Z) am Segmentende -> %.1f mm",
|
||||
"eingang_hoehe": "getreal Ziel-Hoehe fuer den GF1-Einlauf der "
|
||||
"VF-Einheit (nur bei komplett neu gebautem Eingang) "
|
||||
"-> %.1f mm",
|
||||
}
|
||||
|
||||
_INT_FRAGEN = {
|
||||
"gf_bogen_winkel": "getint GF-Bogen Winkel (30/60/90 Grad) -> %d",
|
||||
"vario_winkel": "getint Vario-Kurve Winkel (30/60/90 Grad, Wert "
|
||||
"direkt) -> %d Grad",
|
||||
"winkel_idx": "getint Winkelwahl aus den gueltigen Kandidaten "
|
||||
"(vfl-waehle-winkel) -> Option %d",
|
||||
}
|
||||
|
||||
# STR-Antworten, deren Inhalt schon der Wert ist (kein Code).
|
||||
_ROH_FRAGEN = {
|
||||
"as_winkel": "getstring AS-Element Winkel (Wert direkt, 90 oder 30 "
|
||||
"Grad) -> %s Grad",
|
||||
"es_winkel": "getstring ES-Element Winkel (Wert direkt, 90 oder 30 "
|
||||
"Grad) -> %s Grad",
|
||||
}
|
||||
|
||||
_JA_NEIN_FRAGEN = {
|
||||
"as": "getstring AS-Element setzen? (1=Ja, 2=Nein) -> %s",
|
||||
"sep_vor": "getstring Separator VOR dem horizontalen Stueck? (1=Ja, "
|
||||
"2=Nein) -> %s",
|
||||
"sep_nach": "getstring Separator NACH dem horizontalen Stueck? (1=Ja, "
|
||||
"2=Nein) -> %s",
|
||||
"es": "getstring ES-Element setzen? (1=Ja, 2=Nein - Kette endet direkt "
|
||||
"am Zielpunkt) -> %s",
|
||||
"separator": "getstring Separator an dieser Stelle einfuegen? (1=Ja, "
|
||||
"2=Nein) -> %s",
|
||||
}
|
||||
|
||||
# Die Menue-Antwort auf "Naechstes Element" ist der Glied-Name; sein
|
||||
# Klartext ist derselbe am Kettenanfang wie spaeter, nur die aufgezaehlten
|
||||
# Optionen im Fragetext unterscheiden sich. Darum EINE Antworttabelle fuer
|
||||
# beide Fragen (frueher zwei code-indizierte Tabellen nebeneinander).
|
||||
_GLIED_TEXT = {"GF-Bogen": "GF-Bogen", "Linie-GF": "Neue Linie: GF",
|
||||
"Linie-VF": "Neue Linie: Ab/Auf VF",
|
||||
"Horizontal-VF": "Horizontal-VF",
|
||||
"Linie": "Neue Linie bis Kettenende"}
|
||||
|
||||
# STR-Antworten ueber eine Code-Tabelle: (Vorlage, Klartext je Domaenenwert).
|
||||
# Steht statt der Klartexte None, ist der Domaenenwert selbst schon der
|
||||
# Antworttext ("links"/"rechts", "aussen"/"innen").
|
||||
#
|
||||
# ACHTUNG: die Fragetexte zaehlen die Menue-Optionen der AUFZEICHNUNG auf
|
||||
# (4 Optionen am Kettenanfang). Wird das Protokoll je gegen die aktuelle
|
||||
# Grammatik erzeugt (GRAMMATIK_AKTUELL, 6 Optionen), muss "menu_start" hier
|
||||
# mitgezogen werden - die Antworten selbst bleiben richtig.
|
||||
_CODE_FRAGEN = {
|
||||
"as_seite": ("getstring AS-Seite (1=links, 2=rechts) -> %s", None),
|
||||
"es_seite": ("getstring ES-Seite (1=links, 2=rechts) -> %s", None),
|
||||
"gf_bogen_seite": ("getstring GF-Bogen Seite (1=links, 2=rechts) -> %s",
|
||||
None),
|
||||
"vario_seite": ("getstring Vario-Kurve Seite (1=links, 2=rechts) -> %s",
|
||||
None),
|
||||
"vario_variante": ("getstring Vario-Kurve Variante (1=aussen, 2=innen) "
|
||||
"-> %s", None),
|
||||
"gefaelle": ("getstring Gefaelle festlegen (1=ueber Zielhoehe, 2=ueber "
|
||||
"Neigungswinkel) -> %s",
|
||||
{"hoehe": "ueber Zielhoehe",
|
||||
"winkel": "ueber Neigungswinkel"}),
|
||||
"ende3": ("getstring Ist das Kettenende? (1=Ja mit ES, 2=Ja ohne ES, "
|
||||
"3=Nein weiterbauen) -> %s",
|
||||
{"ja-mit-es": "Ja, mit ES-Element",
|
||||
"ja-ohne-es": "Ja, ohne ES-Element",
|
||||
"nein": "Nein, weiterbauen"}),
|
||||
"ende2": ("getstring Ist das Kettenende? (1=Ja, Separator + ES-Element, "
|
||||
"2=Nein, weiterbauen) -> %s",
|
||||
{"ja-mit-es": "Ja, Separator + ES-Element",
|
||||
"nein": "Nein, weiterbauen"}),
|
||||
"gf_verteilung": ("getstring GF-Verteilung (1=halbe Staustrecke am "
|
||||
"Ausgang, 2=alles am Einlauf) -> %s",
|
||||
{"haelfte": "halbe Staustrecke am Ausgang",
|
||||
"einlauf": "alles am Einlauf"}),
|
||||
"endpunkt": ("getstring Ist der Endpunkt der Foerderer? (1=Ja nur "
|
||||
"Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + "
|
||||
"Kettenende) -> %s",
|
||||
{"motorstation": "Ja, nur Motorstation",
|
||||
"weiter": "Nein, weiterbauen",
|
||||
"kettenende": "Ja, Motorstation + Kettenende am "
|
||||
"Zielpunkt"}),
|
||||
"im_vf": ("getstring Naechstes in der VF-Einheit (1=Horizontaler "
|
||||
"Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> %s",
|
||||
{"horizontal": "Horizontaler Foerderer",
|
||||
"vario-kurve": "Vario-Kurve",
|
||||
"auf-ab": "Auf/Ab-Foerderer"}),
|
||||
"menu_start": ("getstring Naechstes Element am Kettenanfang (1=Neue "
|
||||
"Linie GF, 2=Neue Linie Ab/Auf VF, 3=Horizontal-VF, "
|
||||
"4=Neue Linie bis Kettenende) -> %s", _GLIED_TEXT),
|
||||
"menu_weiter": ("getstring Naechstes Element (1=GF-Bogen, 2=Neue Linie "
|
||||
"GF, 3=Neue Linie Ab/Auf VF, 4=Horizontal-VF, 5=Neue "
|
||||
"Linie bis Kettenende) -> %s", _GLIED_TEXT),
|
||||
}
|
||||
|
||||
# Kontext der Laengeneingabe je Frage-Schluessel (die "%s"-Stelle im
|
||||
# getpoint-Kommentar).
|
||||
_LINIE_TEXT = {
|
||||
"gf_linie": "Endpunkt der GF-Linie",
|
||||
"vf_linie": "Endpunkt des Ab/Auf-VF-Segments",
|
||||
"horizontal_vf": "Endpunkt des horizontalen VF-Stuecks",
|
||||
"horizontal_sub": "Endpunkt des horizontalen VF-Stuecks",
|
||||
"aufab_sub": "Endpunkt des Auf/Ab-VF-Segments",
|
||||
"linie_kettenende": "Endpunkt der Linie bis Kettenende (Ziel-XY der "
|
||||
"Kette)",
|
||||
"kettenende_ziel": "Zielpunkt des Kettenendes",
|
||||
}
|
||||
|
||||
|
||||
def _vorlage(tabelle, frage):
|
||||
if frage not in tabelle:
|
||||
raise ValueError("keine Annotation fuer die Frage %r hinterlegt "
|
||||
"(lib/vf_journal_export.py)" % frage)
|
||||
return tabelle[frage]
|
||||
|
||||
|
||||
class ProtokollSink(Sink):
|
||||
"""Schreibt aus den Meldungen des Parser-Kerns das annotierte
|
||||
Frage-Antwort-Protokoll (Schema tests/testdata/hm_recformat.json):
|
||||
je verbrauchtem Wert ein Eintrag {"typ", Wertfeld(er), "kommentar"}.
|
||||
|
||||
Die Reihenfolge der Schluessel im Eintrag ist fest (typ, Wert,
|
||||
kommentar) - das erzeugte JSON wird von Hand gelesen und mit frueheren
|
||||
Laeufen verglichen.
|
||||
"""
|
||||
|
||||
def __init__(self, roh):
|
||||
self.tok = []
|
||||
for zeile in roh.split(TRENNER):
|
||||
if zeile:
|
||||
art, _, wert = zeile.partition(":")
|
||||
self.tok.append((art, wert))
|
||||
self.pos = 0
|
||||
def __init__(self):
|
||||
self.out = []
|
||||
self.erster_step = True
|
||||
self.erster_dl = True
|
||||
|
||||
# --- Tokenzugriff ---
|
||||
|
||||
def art(self):
|
||||
return self.tok[self.pos][0] if self.pos < len(self.tok) else None
|
||||
|
||||
def label(self):
|
||||
return self.tok[self.pos][1] if self.pos < len(self.tok) else None
|
||||
|
||||
def nimm(self, erwartet):
|
||||
if self.pos >= len(self.tok):
|
||||
raise ValueError("Journal endet vor %s" % erwartet)
|
||||
art, wert = self.tok[self.pos]
|
||||
if art != erwartet:
|
||||
raise ValueError("Token %d: %s erwartet, %s:%s gefunden"
|
||||
% (self.pos, erwartet, art, wert))
|
||||
self.pos += 1
|
||||
return wert
|
||||
|
||||
# --- Ausgabe ---
|
||||
def ergebnis(self):
|
||||
return self.out
|
||||
|
||||
def add(self, typ, kommentar, **felder):
|
||||
eintrag = {"typ": typ}
|
||||
@@ -199,253 +299,96 @@ class Journal(object):
|
||||
eintrag["kommentar"] = kommentar
|
||||
self.out.append(eintrag)
|
||||
|
||||
def frage_str(self, kommentar_fmt, tabelle=None):
|
||||
w = self.nimm("STR")
|
||||
self.add("string", kommentar_fmt % (tabelle.get(w, "?") if tabelle
|
||||
else w), wert=w)
|
||||
return w
|
||||
# --- Struktur: nur die Glied-Marker stehen im Protokoll ---
|
||||
|
||||
def frage_real(self, kommentar_fmt):
|
||||
w = float(self.nimm("REAL"))
|
||||
self.add("real", kommentar_fmt % w, wert=w)
|
||||
return w
|
||||
|
||||
def frage_int(self, kommentar_fmt):
|
||||
w = int(self.nimm("INT"))
|
||||
self.add("int", kommentar_fmt % w, wert=w)
|
||||
return w
|
||||
|
||||
def marker(self, label):
|
||||
def sektion_start(self, label):
|
||||
self.add("step", STEP_KOMMENTAR, wert=label)
|
||||
|
||||
def frage_linie(self, was):
|
||||
"""Segmentlaenge; die gesnappte Fahrtrichtung steht nur beim ersten
|
||||
Segment der Kette im Journal (vfl-in-abstand), danach erbt jedes
|
||||
Segment die Richtung des Vorgaengers."""
|
||||
dl = float(self.nimm("DL"))
|
||||
if self.erster_dl and self.art() == "REAL":
|
||||
hz = float(self.nimm("REAL"))
|
||||
self.erster_dl = False
|
||||
def marke(self, label):
|
||||
self.add("step", STEP_KOMMENTAR, wert=label)
|
||||
|
||||
def glied_gewaehlt(self, label, code, frage):
|
||||
vorlage, texte = _vorlage(_CODE_FRAGEN, frage)
|
||||
self.add("string", vorlage % texte[label], wert=code)
|
||||
|
||||
# --- Werte ---
|
||||
|
||||
def punkt_abs(self, punkt):
|
||||
self.add("point_abs", "getpoint Startpunkt der Kette (absoluter "
|
||||
"Weltpunkt, einziger Anker der ganzen Kette)", wert=punkt)
|
||||
|
||||
def linie(self, dl, hz, frage):
|
||||
was = _vorlage(_LINIE_TEXT, frage)
|
||||
if hz is None:
|
||||
self.add("point_rel",
|
||||
"getpoint %s: dL=%.1f mm (Fahrtrichtung vom "
|
||||
"Vorgaenger geerbt)" % (was, dl), dL=dl)
|
||||
else:
|
||||
self.add("point_rel",
|
||||
"getpoint %s: dL=%.1f mm, gesnappte Fahrtrichtung "
|
||||
"hz=%.0f Grad (nur beim ersten Segment der Kette "
|
||||
"journalisiert)" % (was, dl, hz), dL=dl, hz=hz)
|
||||
else:
|
||||
self.erster_dl = False
|
||||
self.add("point_rel",
|
||||
"getpoint %s: dL=%.1f mm (Fahrtrichtung vom "
|
||||
"Vorgaenger geerbt)" % (was, dl), dL=dl)
|
||||
return dl
|
||||
|
||||
# --- Ablauf ---
|
||||
def real_feld(self, name, wert, frage):
|
||||
self.add("real", _vorlage(_REAL_FRAGEN, frage) % wert, wert=wert)
|
||||
|
||||
def int_feld(self, name, wert, frage):
|
||||
self.add("int", _vorlage(_INT_FRAGEN, frage) % wert, wert=wert)
|
||||
|
||||
def roh_feld(self, name, wert, frage):
|
||||
self.add("string", _vorlage(_ROH_FRAGEN, frage) % wert, wert=wert)
|
||||
|
||||
def ja_nein(self, name, code, wert, frage):
|
||||
self.add("string",
|
||||
_vorlage(_JA_NEIN_FRAGEN, frage) % ("Ja" if wert else "Nein"),
|
||||
wert=code)
|
||||
|
||||
def code_feld(self, name, code, wert, frage):
|
||||
self._code(code, wert, frage)
|
||||
|
||||
def wahl(self, name, code, wert, frage):
|
||||
self._code(code, wert, frage)
|
||||
|
||||
def _code(self, code, wert, frage):
|
||||
"""Eine ueber eine Code-Tabelle beantwortete Frage. Der Domaenenwert
|
||||
ist entweder selbst schon der Antworttext (links/rechts,
|
||||
aussen/innen) oder wird ueber die Klartexttabelle der Frage
|
||||
uebersetzt; im Protokoll steht als "wert" der rohe Journal-Code, so
|
||||
wie er in der XDATA stand."""
|
||||
vorlage, texte = _vorlage(_CODE_FRAGEN, frage)
|
||||
self.add("string", vorlage % (texte[wert] if texte else wert),
|
||||
wert=code)
|
||||
|
||||
# implizit() schreibt nichts: segment_typ und das automatische VF-Ende
|
||||
# ergeben sich aus dem Ablauf, es wurde dafuer kein Token gelesen.
|
||||
|
||||
|
||||
def tokens_aus_journal(roh):
|
||||
"""Die Rohform der XDATA ("ART:wert", getrennt durch TRENNER) in
|
||||
Tokenpaare zerlegen."""
|
||||
tok = []
|
||||
for zeile in roh.split(TRENNER):
|
||||
if zeile:
|
||||
art, _, wert = zeile.partition(":")
|
||||
tok.append((art, wert))
|
||||
return tok
|
||||
|
||||
|
||||
class Journal(object):
|
||||
"""Ein Journal (kind:payload je Zeile) in das Frage-Antwort-Protokoll
|
||||
uebersetzen.
|
||||
|
||||
Der Ablauf selbst steckt im gemeinsamen Parser-Kern
|
||||
(lib/vf_linienzug_grammatik.py) - diese Klasse haelt nur noch die
|
||||
Rohform-Zerlegung und die Sink zusammen.
|
||||
"""
|
||||
|
||||
def __init__(self, roh, grammatik=GRAMMATIK_AUFZEICHNUNG):
|
||||
self.tok = tokens_aus_journal(roh)
|
||||
self.grammatik = grammatik
|
||||
|
||||
def dekodiere(self):
|
||||
self.praeambel()
|
||||
while self.pos < len(self.tok):
|
||||
label = self.nimm("STEP")
|
||||
self.marker(label)
|
||||
if label == "ES":
|
||||
self.glied_es(marker_gelesen=True)
|
||||
continue
|
||||
if self.erster_step:
|
||||
tabelle, kopf = MENU_START, (
|
||||
"Naechstes Element am Kettenanfang (1=Neue Linie GF, "
|
||||
"2=Neue Linie Ab/Auf VF, 3=Horizontal-VF, 4=Neue Linie "
|
||||
"bis Kettenende) -> %s")
|
||||
else:
|
||||
tabelle, kopf = MENU_WEITER, (
|
||||
"Naechstes Element (1=GF-Bogen, 2=Neue Linie GF, "
|
||||
"3=Neue Linie Ab/Auf VF, 4=Horizontal-VF, 5=Neue Linie "
|
||||
"bis Kettenende) -> %s")
|
||||
self.frage_str("getstring " + kopf, tabelle)
|
||||
self.erster_step = False
|
||||
if label == "Linie-GF":
|
||||
self.glied_linie_gf()
|
||||
elif label == "Linie-VF":
|
||||
self.glied_linie_vf()
|
||||
elif label == "Horizontal-VF":
|
||||
self.glied_horizontal_vf()
|
||||
elif label == "Linie":
|
||||
self.glied_linie_kettenende()
|
||||
elif label == "GF-Bogen":
|
||||
self.frage_int("getint GF-Bogen Winkel (30/60/90 Grad) "
|
||||
"-> %d")
|
||||
self.frage_str("getstring GF-Bogen Seite (1=links, "
|
||||
"2=rechts) -> %s", SEITE)
|
||||
else:
|
||||
raise ValueError("unbekanntes Glied: %s" % label)
|
||||
if self.pos != len(self.tok):
|
||||
raise ValueError("nicht alle Tokens verbraucht (%d von %d)"
|
||||
% (self.pos, len(self.tok)))
|
||||
return self.out
|
||||
|
||||
def praeambel(self):
|
||||
punkt = [float(v) for v in self.nimm("PT").split(",")]
|
||||
self.add("point_abs", "getpoint Startpunkt der Kette (absoluter "
|
||||
"Weltpunkt, einziger Anker der ganzen Kette)", wert=punkt)
|
||||
self.frage_real("getreal Hoehe (Z) Startpunkt -> %.1f mm")
|
||||
if self.frage_str("getstring AS-Element setzen? (1=Ja, 2=Nein) "
|
||||
"-> %s", JA_NEIN) == "1":
|
||||
w = self.nimm("STR")
|
||||
self.add("string", "getstring AS-Element Winkel (Wert direkt, "
|
||||
"90 oder 30 Grad) -> %s Grad" % w, wert=w)
|
||||
self.frage_str("getstring AS-Seite (1=links, 2=rechts) -> %s",
|
||||
SEITE)
|
||||
|
||||
def glied_linie_gf(self):
|
||||
self.frage_linie("Endpunkt der GF-Linie")
|
||||
if self.frage_str("getstring Gefaelle festlegen (1=ueber Zielhoehe, "
|
||||
"2=ueber Neigungswinkel) -> %s", GF_MODUS) == "2":
|
||||
self.frage_real("getreal Neigungswinkel -> %.1f Grad")
|
||||
else:
|
||||
self.frage_real("getreal Zielhoehe (Z) Linienendpunkt "
|
||||
"-> %.1f mm")
|
||||
if self.frage_str("getstring Ist das Kettenende? (1=Ja mit ES, "
|
||||
"2=Ja ohne ES, 3=Nein weiterbauen) -> %s",
|
||||
KETTENENDE3) == "1":
|
||||
self.glied_es()
|
||||
|
||||
def glied_horizontal_vf(self):
|
||||
"""VF-Einheit mit HORIZONTALEM Anfangskoerper (winkel1=0 in
|
||||
vfl-vf-einheit): dort stellt vfl-baue-horizontal-koerper im
|
||||
Ziel-Modus die Separator-/Endpunkt-Fragen VOR der
|
||||
Laengenberechnung."""
|
||||
self.frage_linie("Endpunkt des horizontalen VF-Stuecks")
|
||||
self.vf_einheit(auto_ende=False, erster_horizontal=True)
|
||||
|
||||
def glied_linie_vf(self):
|
||||
""""Neue Linie: Ab/Auf VF" - Laenge, Zielhoehe, ggf. Winkelwahl
|
||||
(vfl-vf-entscheidung), dann eine VF-Einheit mit GEWINKELTEM
|
||||
Anfangskoerper. Zu dem Koerper wird nichts gefragt (vfl-vf-einheit
|
||||
baut ihn direkt), die erste Frage der Einheit ist also die
|
||||
Endpunkt-Frage der Fortsetzungsschleife."""
|
||||
self.frage_linie("Endpunkt des Ab/Auf-VF-Segments")
|
||||
self.frage_real("getreal Zielhoehe (Z) Linienendpunkt -> %.1f mm")
|
||||
self.frage_winkelwahl()
|
||||
self.vf_einheit(auto_ende=False, erster_horizontal=False)
|
||||
|
||||
def glied_linie_kettenende(self):
|
||||
""""Neue Linie bis Kettenende": Laenge + Zielhoehe, danach entscheidet
|
||||
vfl-segment-entscheidung selbst. Reine Gefaellestrecke -> ES-Element
|
||||
ohne weitere Frage; VF-Einheit -> vf_einheit mit auto-ende."""
|
||||
self.frage_linie("Endpunkt der Linie bis Kettenende (Ziel-XY der "
|
||||
"Kette)")
|
||||
self.frage_real("getreal Zielhoehe (Z) am Kettenende -> %.1f mm")
|
||||
self.frage_winkelwahl()
|
||||
if self.art() == "STEP" and self.label() == "ES":
|
||||
self.glied_es()
|
||||
elif self.art() == "STR":
|
||||
# vfl-segment-entscheidung liefert fuer typ="VF" immer einen
|
||||
# Winkel > 0 (die VF-Winkelstufen beginnen bei 3 Grad), der erste
|
||||
# Koerper ist hier also nie horizontal.
|
||||
self.vf_einheit(auto_ende=True, erster_horizontal=False)
|
||||
|
||||
def glied_es(self, marker_gelesen=False):
|
||||
if not marker_gelesen:
|
||||
label = self.nimm("STEP")
|
||||
if label != "ES":
|
||||
raise ValueError("ES-Marker erwartet, %s gefunden" % label)
|
||||
self.marker(label)
|
||||
w = self.nimm("STR")
|
||||
self.add("string", "getstring ES-Element Winkel (Wert direkt, 90 "
|
||||
"oder 30 Grad) -> %s Grad" % w, wert=w)
|
||||
self.frage_str("getstring ES-Seite (1=links, 2=rechts) -> %s", SEITE)
|
||||
|
||||
def frage_winkelwahl(self):
|
||||
"""Winkelwahl (vfl-waehle-winkel) steht nur im Journal, wenn mehrere
|
||||
Kandidatenwinkel gueltig waren - bei genau einem faellt die Frage
|
||||
weg."""
|
||||
if self.art() == "INT":
|
||||
self.frage_int("getint Winkelwahl aus den gueltigen "
|
||||
"Kandidaten (vfl-waehle-winkel) -> Option %d")
|
||||
|
||||
def horizontal_koerper(self):
|
||||
"""vfl-baue-horizontal-koerper im Ziel-Modus: Separator vor/nach und
|
||||
die Endpunkt-Frage stehen VOR der Laengenberechnung. Rueckgabe: die
|
||||
Endpunkt-Antwort, die die Fortsetzungsschleife weiterverwendet."""
|
||||
self.frage_str("getstring Separator VOR dem horizontalen Stueck? "
|
||||
"(1=Ja, 2=Nein) -> %s", JA_NEIN)
|
||||
self.frage_str("getstring Separator NACH dem horizontalen Stueck? "
|
||||
"(1=Ja, 2=Nein) -> %s", JA_NEIN)
|
||||
return self.frage_str("getstring Ist der Endpunkt der Foerderer? "
|
||||
"(1=Ja nur Motorstation, 2=Nein weiterbauen, "
|
||||
"3=Ja Motorstation + Kettenende) -> %s",
|
||||
ENDPUNKT3)
|
||||
|
||||
def vario_kurve(self):
|
||||
label = self.nimm("STEP")
|
||||
if label != "Vario-Kurve":
|
||||
raise ValueError("Vario-Kurve-Marker erwartet, %s" % label)
|
||||
self.marker(label)
|
||||
self.frage_int("getint Vario-Kurve Winkel (30/60/90 Grad, Wert "
|
||||
"direkt) -> %d Grad")
|
||||
self.frage_str("getstring Vario-Kurve Seite (1=links, 2=rechts) "
|
||||
"-> %s", SEITE)
|
||||
self.frage_str("getstring Vario-Kurve Variante (1=aussen, 2=innen) "
|
||||
"-> %s", VARIANTE)
|
||||
|
||||
def vf_einheit(self, auto_ende, erster_horizontal):
|
||||
"""Eine VF-Einheit: GF-Verteilung, erster Koerper, dann die
|
||||
Fortsetzungsschleife (vfl-vf-einheit) bis Motorstation/Kettenende,
|
||||
danach der Abschluss (vfl-vf-einheit-abschluss).
|
||||
|
||||
erster_horizontal spiegelt die Verzweigung ueber winkel1 in
|
||||
vfl-vf-einheit: nur der horizontale Erstkoerper laeuft durch
|
||||
vfl-baue-horizontal-koerper und fragt darum Separator vor/nach und
|
||||
die Endpunkt-Frage schon vorab (deren Antwort die Schleife dann
|
||||
uebernimmt, statt erneut zu fragen). Ein gewinkelter Erstkoerper
|
||||
wird ohne jede Frage gebaut."""
|
||||
self.frage_str("getstring GF-Verteilung (1=halbe Staustrecke am "
|
||||
"Ausgang, 2=alles am Einlauf) -> %s", VERTEILUNG)
|
||||
vorantwort = self.horizontal_koerper() if erster_horizontal else None
|
||||
ziel_ende = False
|
||||
es_gewuenscht = True
|
||||
while True:
|
||||
if vorantwort is None:
|
||||
antwort = self.frage_str(
|
||||
"getstring Ist der Endpunkt der Foerderer? (1=Ja nur "
|
||||
"Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + "
|
||||
"Kettenende) -> %s", ENDPUNKT3)
|
||||
else:
|
||||
antwort, vorantwort = vorantwort, None
|
||||
if antwort == "1":
|
||||
break
|
||||
if antwort == "3":
|
||||
es_gewuenscht = self.frage_str(
|
||||
"getstring ES-Element setzen? (1=Ja, 2=Nein - Kette "
|
||||
"endet direkt am Zielpunkt) -> %s", JA_NEIN) != "2"
|
||||
self.frage_linie("Zielpunkt des Kettenendes")
|
||||
self.frage_real("getreal Zielhoehe (Z) am Kettenende "
|
||||
"-> %.1f mm")
|
||||
self.frage_winkelwahl()
|
||||
ziel_ende = True
|
||||
break
|
||||
wahl = self.frage_str("getstring Naechstes in der VF-Einheit "
|
||||
"(1=Horizontaler Foerderer, "
|
||||
"2=Vario-Kurve, 3=Auf/Ab-Foerderer) "
|
||||
"-> %s", IM_VF)
|
||||
if wahl == "1":
|
||||
self.frage_linie("Endpunkt des horizontalen VF-Stuecks")
|
||||
vorantwort = self.horizontal_koerper()
|
||||
elif wahl == "2":
|
||||
self.vario_kurve()
|
||||
else:
|
||||
self.frage_linie("Endpunkt des Auf/Ab-VF-Segments")
|
||||
self.frage_real("getreal Zielhoehe (Z) am Segmentende "
|
||||
"-> %.1f mm")
|
||||
self.frage_winkelwahl()
|
||||
if ziel_ende and not es_gewuenscht:
|
||||
return
|
||||
if auto_ende or ziel_ende:
|
||||
self.glied_es()
|
||||
return
|
||||
if self.frage_str("getstring Ist das Kettenende? (1=Ja, Separator + "
|
||||
"ES-Element, 2=Nein, weiterbauen) -> %s",
|
||||
KETTENENDE2) == "1":
|
||||
self.glied_es()
|
||||
else:
|
||||
self.frage_str("getstring Separator an dieser Stelle einfuegen? "
|
||||
"(1=Ja, 2=Nein) -> %s", JA_NEIN)
|
||||
return lies_journal(self.tok, ProtokollSink(), self.grammatik)
|
||||
|
||||
|
||||
# ============================================================
|
||||
|
||||
Reference in New Issue
Block a user