grammar-inference-engine/bex/template.py
tobjend dc559a4aee
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
fix badge position; purge remaining German user-reference comments
2026-07-01 13:28:55 +02:00

154 lines
4.5 KiB
Python

"""
template — One-Shot YAML Template Generator.
Converts an inferred k-ORE/SORE/CHARE expression back into
a human-readable YAML skeleton.
Generates:
- A YAML scaffold with placeholders
- Cardinality annotations:
* # REQUIRED: Exactly 1
* # REPEATED: 1 or more
* # OPTIONAL: 0 or 1
* # VARIABLE: 0 or more
* # CHOOSE: alternative module
"""
def parse_expression(expr):
"""Split a regular expression into its components."""
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):
"""Return the cardinality description for a quantifier."""
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):
"""
Generate a YAML one-shot template from a regular expression.
Args:
expr: Inferred expression (string)
context_key: YAML container key (e.g. 'tasks')
include_header: Whether to include header section (name, hosts)
Returns:
YAML skeleton with placeholders and cardinality comments
"""
if not expr or expr in ('', 'ε'):
return "# No structure inferred (empty sequences or no examples)"
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}# CHOOSE (pick one):")
for alt in alts:
alt_clean = alt.strip()
lines.append(f"{indent}# - {alt_clean}: <params>")
if card:
lines[-1] = f"{lines[-1]} {card}"
else:
lines.append(f"{indent}- {inner_expr}: <params> {card}")
task_index += 1
elif token[0] == 'name':
name = token[1]
quantifier = token[2]
card = format_prompt_cardinality(quantifier)
lines.append(f"{indent}- {name}: <params> {card}")
task_index += 1
elif token[0] == 'pipe':
pass
i += 1
return '\n'.join(lines) + '\n'