add proxy for the first part and try to develop the second part

This commit is contained in:
Pierre Geeraert
2025-12-03 12:08:56 +00:00
parent 6c34d510e5
commit 460f18d85a
1095 changed files with 358799 additions and 5 deletions
@@ -0,0 +1,594 @@
"""
Python code generation for models defined with tatsu.model
"""
from __future__ import annotations
import re
import textwrap
from .. import grammars
from ..collections import OrderedSet as oset
from ..exceptions import CodegenError
from ..objectmodel import BASE_CLASS_TOKEN, Node
from ..util import compress_seq, indent, safe_name, timestamp, trim
from .cgbase import CodeGenerator, ModelRenderer
class PythonCodeGenerator(CodeGenerator):
def _find_renderer_class(self, node):
if not isinstance(node, Node):
return None
name = node.__class__.__name__
renderer = globals().get(name)
if not renderer or not issubclass(renderer, Base):
raise CodegenError(f'Renderer for {name} not found')
return renderer
def codegen(model):
return PythonCodeGenerator().render(model)
class Base(ModelRenderer):
def defines(self):
return self.node.defines()
def make_defines_declaration(self):
defines = compress_seq(self.defines())
ldefs = oset(safe_name(d) for d, value in defines if value)
sdefs = oset(
safe_name(d)
for d, value in defines
if not value and d not in ldefs
)
if not (sdefs or ldefs):
return ''
else:
sdefs = '[{}]'.format(', '.join(sorted(repr(d) for d in sdefs)))
ldefs = '[{}]'.format(', '.join(sorted(repr(d) for d in ldefs)))
if not ldefs:
return f'\n\n self._define({sdefs}, {ldefs})'
else:
return '\n' + trim(self.define_template % (sdefs, ldefs))
define_template = """\
self._define(
%s,
%s,
)\
"""
class Void(Base):
template = 'self._void()'
class Any(Base):
template = 'self._any()'
class Fail(Base):
template = 'self._fail()'
class Comment(Base):
def render_fields(self, fields):
lines = '\n'.join(
f'# {c!s}' for c in self.node.comment.splitlines()
)
fields.update(lines=lines)
template = '\n{lines}\n'
class EOLComment(Comment):
pass
class EOF(Base):
template = 'self._check_eof()'
class _Decorator(Base):
template = '{exp}'
class Group(_Decorator):
template = """\
with self._group():
{exp:1::}\
"""
class Token(Base):
def render_fields(self, fields):
fields.update(token=repr(self.node.token))
template = 'self._token({token})'
class Constant(Base):
def render_fields(self, fields):
fields.update(constant=repr(self.node.literal))
template = 'self._constant({constant})'
class Alert(Base):
def render_fields(self, fields):
fields.update(literal=repr(self.node.literal), level=self.node.level)
template = 'self._alert({literal}, {level})'
class Pattern(Base):
def render_fields(self, fields):
raw_repr = repr(self.node.pattern)
fields.update(pattern=raw_repr)
template = 'self._pattern({pattern})'
class Lookahead(_Decorator):
template = """\
with self._if():
{exp:1::}\
"""
class NegativeLookahead(_Decorator):
template = """\
with self._ifnot():
{exp:1::}\
"""
class Sequence(Base):
def render_fields(self, fields):
fields.update(seq='\n'.join(self.rend(s) for s in self.node.sequence))
defines = self.make_defines_declaration()
fields.update(defines=defines)
template = """{seq}
{defines}\
"""
class Choice(Base):
def render_fields(self, fields):
firstset = self.node.lookahead_str()
if firstset:
msglines = textwrap.wrap(firstset, width=40)
error = ['expecting one of: ', *msglines]
else:
error = ['no available options']
error = [repr(e) for e in error]
fields.update(n=self.counter(), error=error)
fields.update(
options='\n'.join(self.rend(o) for o in self.node.options),
)
def render(self, **fields):
if len(self.node.options) == 1:
return self.rend(self.options[0], **fields)
else:
return super().render(**fields)
template = """\
with self._choice():
{options:1::}
self._error(
{error:2:\\n:} # noqa: COM812
)\
"""
class Option(_Decorator):
def render_fields(self, fields):
defines = self.make_defines_declaration()
fields.update(defines=defines)
template = """\
with self._option():
{exp:1::}\
"""
class Closure(_Decorator):
def render_fields(self, fields):
fields.update(n=self.counter())
def render(self, **fields):
if () in self.node.exp.lookahead():
raise CodegenError(f'{self.node} may repeat empty sequence')
return '\n' + super().render(**fields)
template = """\
def block{n}():
{exp:1::}
self._closure(block{n})\
"""
class PositiveClosure(Closure):
template = """\
def block{n}():
{exp:1::}
self._positive_closure(block{n})\
"""
class Join(_Decorator):
def render_fields(self, fields):
fields.update(n=self.counter())
def render(self, **fields):
if () in self.node.exp.lookahead():
raise CodegenError('may repeat empty sequence')
return '\n' + super().render(**fields)
template = """\
def sep{n}():
{sep:1::}
def block{n}():
{exp:1::}
self._join(block{n}, sep{n})\
"""
class PositiveJoin(Join):
template = """\
def sep{n}():
{sep:1::}
def block{n}():
{exp:1::}
self._positive_join(block{n}, sep{n})\
"""
class Gather(Join):
template = """\
def sep{n}():
{sep:1::}
def block{n}():
{exp:1::}
self._gather(block{n}, sep{n})\
"""
class PositiveGather(Join):
template = """\
def sep{n}():
{sep:1::}
def block{n}():
{exp:1::}
self._positive_gather(block{n}, sep{n})\
"""
class LeftJoin(PositiveJoin):
template = """\
def sep{n}():
{sep:1::}
def block{n}():
{exp:1::}
self._left_join(block{n}, sep{n})\
"""
class RightJoin(PositiveJoin):
template = """\
def sep{n}():
{sep:1::}
def block{n}():
{exp:1::}
self._right_join(block{n}, sep{n})\
"""
class EmptyClosure(Base):
template = 'self._empty_closure()'
class SkipTo(Closure):
template = """\
def block{n}():
{exp:1::}
self._skip_to(block{n})\
"""
class Optional(_Decorator):
template = """\
with self._optional():
{exp:1::}\
"""
class Cut(Base):
template = 'self._cut()'
class Named(_Decorator):
def __str__(self):
return f'{self.name}:{self.rend(self.exp)}'
def render_fields(self, fields):
fields.update(n=self.counter(), name=safe_name(self.node.name))
template = """
{exp}
self.name_last_node('{name}')\
"""
class NamedList(Named):
template = """
{exp}
self.add_last_node_to_name('{name}')\
"""
class Override(Named):
pass
class OverrideList(NamedList):
pass
class Special(Base):
pass
class RuleRef(Base):
template = 'self._{name}_()'
class RuleInclude(_Decorator):
def render_fields(self, fields):
super().render_fields(fields)
fields.update(exp=self.rend(self.node.rule.exp))
template = """
{exp}
"""
class Rule(_Decorator):
@staticmethod
def param_repr(p):
if isinstance(p, int | float):
return str(p)
else:
return repr(p.split(BASE_CLASS_TOKEN)[0])
def render(self, **fields):
try:
return super().render(**fields)
except CodegenError as e:
raise CodegenError(f'{self.node.name}={e}') from e
def render_fields(self, fields):
self.reset_counter()
params = kwparams = ''
if self.node.params:
params = ', '.join(
self.param_repr(self.rend(p)) for p in self.node.params
)
if self.node.kwparams:
kwparams = ', '.join(
f'{k}={self.param_repr(self.rend(v))}'
for k, v in self.kwparams.items()
)
if params and kwparams:
params = params + ', ' + kwparams
elif kwparams:
params = kwparams
fields.update(params=params)
sdefines = ''
if not isinstance(self.node, grammars.Choice):
sdefines = self.make_defines_declaration()
fields.update(defines=sdefines)
leftrec = self.node.is_leftrec
fields.update(leftrec='\n@leftrec' if leftrec else '')
fields.update(
nomemo='\n@nomemo'
if not self.node.is_memoizable and not leftrec
else '',
)
fields.update(isname='\n@isname' if self.node.is_name else '')
template = """
@tatsumasu({params})\
{leftrec}\
{nomemo}\
{isname}
def _{name}_(self):
{exp:1::}
"""
define_template = """\
self._define(
%s,
%s
)\
"""
class BasedRule(Rule):
def defines(self):
return self.rhs.defines()
def render_fields(self, fields):
super().render_fields(fields)
fields.update(exp=self.rhs)
class Grammar(Base):
def render_fields(self, fields):
abstract_template = trim(self.abstract_rule_template)
abstract_rules = [
abstract_template.format(name=safe_name(rule.name))
for rule in self.node.rules
]
abstract_rules = indent('\n'.join(abstract_rules))
whitespace = self.node.config.whitespace
if not whitespace:
whitespace = 'None'
elif isinstance(whitespace, re.Pattern):
whitespace = repr(whitespace)
else:
whitespace = f're.compile(r"{whitespace}")'
if self.node.config.nameguard is not None:
nameguard = repr(self.node.config.nameguard)
else:
nameguard = 'None'
ignorecase = self.node.config.ignorecase
left_recursion = self.node.config.left_recursion
parseinfo = self.node.config.parseinfo
namechars = repr(self.node.config.namechars or '')
comments = repr(self.node.config.comments)
eol_comments = repr(self.node.config.eol_comments)
rules = '\n'.join(
[self.get_renderer(rule).render() for rule in self.node.rules],
)
version = str(tuple(int(n) for n in str(timestamp()).split('.')))
keywords = [str(k) for k in self.keywords if k is not None]
keywords = '\n'.join(f' {k!r},' for k in keywords)
if keywords:
keywords = f'(\n{keywords}\n)'
fields.update(
rules=indent(rules),
start=self.node.rules[0].name,
abstract_rules=abstract_rules,
version=version,
whitespace=whitespace,
nameguard=nameguard,
ignorecase=ignorecase,
left_recursion=left_recursion,
parseinfo=parseinfo,
keywords=keywords,
namechars=namechars,
comments=comments,
eol_comments=eol_comments,
)
abstract_rule_template = """
def {name}(self, ast):
return ast
"""
template = """\
#!/usr/bin/env python3
# WARNING: CAVEAT UTILITOR
#
# This file was automatically generated by TatSu.
#
# https://pypi.python.org/pypi/tatsu/
#
# Any changes you make to it will be overwritten the next time
# the file is generated.
# ruff: noqa: C405, I001, F401, SIM117
import sys
from pathlib import Path
from tatsu.buffering import Buffer
from tatsu.parsing import Parser
from tatsu.parsing import tatsumasu
from tatsu.parsing import leftrec, nomemo, isname
from tatsu.infos import ParserConfig
from tatsu.util import re, generic_main
KEYWORDS: set[str] = set({keywords})
class {name}Buffer(Buffer):
def __init__(self, text, /, config: ParserConfig | None = None, **settings):
config = ParserConfig.new(
config,
owner=self,
whitespace={whitespace},
nameguard={nameguard},
ignorecase={ignorecase},
namechars={namechars},
parseinfo={parseinfo},
comments={comments},
eol_comments={eol_comments},
keywords=KEYWORDS,
start={start!r},
)
config = config.replace(**settings)
super().__init__(text, config=config)
class {name}Parser(Parser):
def __init__(self, /, config: ParserConfig | None = None, **settings):
config = ParserConfig.new(
config,
owner=self,
whitespace={whitespace},
nameguard={nameguard},
ignorecase={ignorecase},
namechars={namechars},
parseinfo={parseinfo},
comments={comments},
eol_comments={eol_comments},
left_recursion={left_recursion},
keywords=KEYWORDS,
start={start!r},
)
config = config.replace(**settings)
super().__init__(config=config)
{rules}
class {name}Semantics:
{abstract_rules}
def main(filename, **kwargs):
if not filename or filename == '-':
text = sys.stdin.read()
else:
text = Path(filename).read_text()
parser = {name}Parser()
return parser.parse(
text,
filename=filename,
**kwargs,
)
if __name__ == '__main__':
import json
from tatsu.util import asjson
ast = generic_main(main, {name}Parser, name='{name}')
data = asjson(ast)
print(json.dumps(data, indent=2))
"""