grammar-inference-engine/bex/template.py
tobjend 7c00c6713d Initial commit: BEX-based grammar inference engine
- CRX: direct CHARE inference (Algorithm 7, TODS 2010)
- iDRegEx: k-ORE inference (Algorithm 4, arXiv 2010)
- RWR₀: SORE repair (Algorithm 6, TODS 2010)
- rwr²: k-ORE extraction (Algorithm 3, arXiv 2010)
- SOA, k-OA, iKoa, 2T-INF, Baum-Welch
- Ansible role grammar adapter
- Generic YAML key-path converter
- 28 tests, all passing
2026-07-01 08:01:16 +02:00

154 lines
4.8 KiB
Python

"""
template — One-Shot YAML Template Generator.
Wandelt den inferierten k-ORE/SORE/CHARE regulären Ausdruck zurück
in ein menschenlesbares YAML-Skelett für LLM-Prompts.
Der Generator erzeugt:
- Ein YAML-Grundgerüst mit Platzhaltern
- Kommentare mit Kardinalitätshinweisen:
* # PFLICHT: Genau 1 mal erforderlich
* # PFLICHT: 1 oder mehrmals erforderlich
* # OPTIONAL: 0 oder 1 mal (darf weggelassen werden)
* # OPTIONAL: 0 oder mehrmals
* # WAHLWEISE: alternatives Modul
"""
def parse_expression(expr):
"""Zerlegt einen regulären Ausdruck in seine Bestandteile."""
if not expr or expr in ('', 'ε', ''):
return [('empty', 'ε')]
tokens = []
i = 0
while i < len(expr):
if expr[i] == '(':
depth = 1
j = i + 1
while j < len(expr) and depth > 0:
if expr[j] == '(':
depth += 1
elif expr[j] == ')':
depth -= 1
j += 1
group = expr[i:j]
quantifier = ''
if j < len(expr) and expr[j] in '*+?':
quantifier = expr[j]
j += 1
tokens.append(('group', group, quantifier))
i = j
elif expr[i] == '|':
tokens.append(('pipe', '|'))
i += 1
elif expr[i] == '.':
if i + 1 < len(expr) and expr[i + 1] == '.':
tokens.append(('concat', '..'))
i += 2
else:
tokens.append(('concat', '.'))
i += 1
elif expr[i] in '*+?':
if tokens and tokens[-1][0] == 'name':
name, val, _ = tokens[-1]
tokens[-1] = (name, val, expr[i])
i += 1
elif expr[i].isalnum() or expr[i] in '/_-':
j = i
while j < len(expr) and (expr[j].isalnum() or expr[j] in '/_-'):
j += 1
name = expr[i:j]
tokens.append(('name', name, ''))
i = j
else:
i += 1
return tokens
def format_prompt_cardinality(quantifier):
"""Gibt die deutsche Kardinalitätsbeschreibung für einen Quantifier zurück."""
mapping = {
'': '# PFLICHT: Genau 1 mal erforderlich',
'+': '# PFLICHT: 1 oder mehrmals erforderlich',
'*': '# OPTIONAL: 0 oder mehrmals',
'?': '# OPTIONAL: 0 oder 1 mal (darf weggelassen werden)',
}
return mapping.get(quantifier, '')
def generate_template(expr, context_key=None, include_header=True):
"""
Generiert ein YAML-One-Shot-Template aus einem regulären Ausdruck.
Args:
expr: Der inferierte Ausdruck (String)
context_key: Name des YAML-Container-Keys (z.B. 'tasks')
include_header: Ob der Header-Teil (name, hosts) eingefügt wird
Returns:
String: YAML-Skelett mit Platzhaltern und Kardinalitätskommentaren
"""
if not expr or expr in ('', 'ε'):
return "# Keine Struktur inferiert (leere Sequenzen oder keine Beispiele)"
if include_header:
lines = [
"- name: <Name des Plays>",
" hosts: <Ziel-Server> # PFLICHT: Genau 1 mal erforderlich",
]
if context_key:
lines.append(f" {context_key}:")
else:
lines.append(" tasks:")
indent = " "
else:
lines = []
if context_key:
lines.append(f" {context_key}: # Container-Kontext: {context_key}")
else:
lines.append(" tasks:")
indent = " "
tokens = parse_expression(expr)
task_index = 0
skip_until_pipe = False
alternatives = []
in_alternatives = False
i = 0
while i < len(tokens):
token = tokens[i]
if token[0] == 'group':
group_str = token[1]
quantifier = token[2]
card = format_prompt_cardinality(quantifier)
inner_expr = group_str[1:-1]
if '|' in inner_expr:
alts = inner_expr.split('|')
lines.append(f"{indent}# WAHLWEISE (eines auswählen):")
for alt in alts:
alt_clean = alt.strip()
lines.append(f"{indent}# - {alt_clean}: <Parameter für {alt_clean}>")
if card:
lines[-1] = f"{lines[-1]} {card}"
else:
lines.append(f"{indent}- {inner_expr}: <Parameter für {inner_expr}> {card}")
task_index += 1
elif token[0] == 'name':
name = token[1]
quantifier = token[2]
card = format_prompt_cardinality(quantifier)
lines.append(f"{indent}- {name}: <Parameter für {name}> {card}")
task_index += 1
elif token[0] == 'pipe':
pass
i += 1
return '\n'.join(lines) + '\n'