add proxy for the first part and try to develop the second part
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ..exceptions import CodegenError
|
||||
from .cgbase import ( # noqa: F401
|
||||
CodeGenerator,
|
||||
DelegatingRenderingFormatter,
|
||||
ModelRenderer,
|
||||
NullModelRenderer,
|
||||
)
|
||||
|
||||
|
||||
def codegen(model, target='python'):
|
||||
if target.lower() == 'python':
|
||||
from tatsu.codegen import python
|
||||
|
||||
return python.codegen(model)
|
||||
else:
|
||||
raise CodegenError(f'Unknown target language: {target}')
|
||||
@@ -0,0 +1,159 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ..exceptions import CodegenError
|
||||
from ..objectmodel import Node
|
||||
from ..rendering import Renderer, RenderingFormatter, render
|
||||
|
||||
__all__ = [
|
||||
'DelegatingRenderingFormatter',
|
||||
'ModelRenderer',
|
||||
'NullModelRenderer',
|
||||
'CodeGenerator',
|
||||
]
|
||||
|
||||
|
||||
class DelegatingRenderingFormatter(RenderingFormatter):
|
||||
def __init__(self, delegate):
|
||||
assert hasattr(delegate, 'render')
|
||||
super().__init__()
|
||||
self.delegate = delegate
|
||||
|
||||
# override
|
||||
def render(self, item, join='', **fields):
|
||||
result = self.delegate.render(item, join=join, **fields)
|
||||
if result is None:
|
||||
result = super().render(item, join=join, **fields)
|
||||
return result
|
||||
|
||||
def convert_field(self, value, conversion):
|
||||
if isinstance(value, Node):
|
||||
return self.render(value)
|
||||
else:
|
||||
return super().convert_field(value, conversion)
|
||||
|
||||
|
||||
class ModelRenderer(Renderer):
|
||||
def __init__(self, codegen, node, template=None):
|
||||
super().__init__(template=template)
|
||||
self._codegen = codegen
|
||||
self._node = node
|
||||
|
||||
self.formatter = codegen.formatter
|
||||
|
||||
self.__postinit__()
|
||||
|
||||
def __postinit__(self):
|
||||
pass
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
super().__getattr__(name)
|
||||
except AttributeError:
|
||||
if name.startswith('_'):
|
||||
raise
|
||||
return getattr(self.node, name)
|
||||
|
||||
@property
|
||||
def node(self):
|
||||
return self._node
|
||||
|
||||
@property
|
||||
def codegen(self):
|
||||
return self._codegen
|
||||
|
||||
@property
|
||||
def context(self):
|
||||
return self._codegen
|
||||
|
||||
def get_renderer(self, item):
|
||||
return self.codegen.get_renderer(item)
|
||||
|
||||
def render(self, **fields):
|
||||
template = fields.pop('template', None)
|
||||
if isinstance(self.node, Node):
|
||||
fields.update(
|
||||
{
|
||||
k: v
|
||||
for k, v in vars(self.node).items()
|
||||
if not k.startswith('_')
|
||||
},
|
||||
)
|
||||
else:
|
||||
fields.update(value=self.node)
|
||||
return super().render(template=template, **fields)
|
||||
|
||||
|
||||
class NullModelRenderer(ModelRenderer):
|
||||
"""A `ModelRenderer` that generates nothing."""
|
||||
|
||||
template = ''
|
||||
|
||||
|
||||
class CodeGenerator:
|
||||
"""
|
||||
A **CodeGenerator** is an abstract class that finds a
|
||||
``ModelRenderer`` class with the same name as each model's node and
|
||||
uses it to render the node.
|
||||
"""
|
||||
|
||||
def __init__(self, modules=None):
|
||||
self.formatter = DelegatingRenderingFormatter(self)
|
||||
self._renderers = {}
|
||||
if modules is not None:
|
||||
self._renderers = self._find_module_renderers(modules)
|
||||
|
||||
def _find_module_renderers(self, modules):
|
||||
result = {}
|
||||
|
||||
for module in modules:
|
||||
for name, dtype in vars(module).items():
|
||||
if not isinstance(dtype, type):
|
||||
continue
|
||||
if not issubclass(dtype, ModelRenderer):
|
||||
continue
|
||||
if dtype is not ModelRenderer:
|
||||
result[name] = dtype
|
||||
|
||||
return result
|
||||
|
||||
def _find_renderer_class(self, node):
|
||||
if not isinstance(node, Node):
|
||||
return None
|
||||
|
||||
node_class_name = node.__class__.__name__
|
||||
classes = [node.__class__]
|
||||
while classes:
|
||||
cls = classes.pop()
|
||||
|
||||
name = cls.__name__
|
||||
if name in self._renderers:
|
||||
renderer = self._renderers[name]
|
||||
self._renderers[node_class_name] = renderer
|
||||
return renderer
|
||||
|
||||
for base in cls.__bases__:
|
||||
if base not in classes:
|
||||
classes.append(base)
|
||||
|
||||
raise CodegenError(f'Renderer for {node_class_name} not found')
|
||||
|
||||
def get_renderer(self, item):
|
||||
if not isinstance(item, Node):
|
||||
return None
|
||||
|
||||
renderer_class = self._find_renderer_class(item)
|
||||
if renderer_class is None:
|
||||
raise CodegenError(
|
||||
f'Renderer not found for {type(item).__name__}',
|
||||
)
|
||||
try:
|
||||
assert issubclass(renderer_class, ModelRenderer)
|
||||
return renderer_class(self, item)
|
||||
except Exception as e:
|
||||
raise type(e)(str(e), renderer_class.__name__) from e
|
||||
|
||||
def render(self, item, join='', **fields):
|
||||
renderer = self.get_renderer(item)
|
||||
if renderer is None:
|
||||
return render(item, join=join, **fields)
|
||||
return str(renderer.render(**fields))
|
||||
@@ -0,0 +1,233 @@
|
||||
import inspect
|
||||
from collections import namedtuple
|
||||
from datetime import datetime
|
||||
|
||||
from tatsu.util import compress_seq, indent, re, safe_name
|
||||
|
||||
from ..codegen.cgbase import CodeGenerator, ModelRenderer
|
||||
from ..exceptions import CodegenError
|
||||
from ..objectmodel import BASE_CLASS_TOKEN, Node
|
||||
from ..rendering import Renderer
|
||||
|
||||
NODE_NAME_PATTERN = r'(?!\d)\w+(' + rf'{BASE_CLASS_TOKEN}' + r'(?!\d)\w+)*'
|
||||
|
||||
|
||||
TypeSpec = namedtuple('TypeSpec', ['class_name', 'base'])
|
||||
|
||||
DEFAULT_BASE_TYPE = """
|
||||
@dataclass(eq=False)
|
||||
class ModelBase(Node):
|
||||
pass
|
||||
"""
|
||||
|
||||
|
||||
def codegen(model, base_type=None):
|
||||
return ObjectModelCodeGenerator().render(model, base_type=base_type)
|
||||
|
||||
|
||||
def _get_node_class_name(rule):
|
||||
if not rule.params:
|
||||
return None
|
||||
|
||||
typespec = rule.params[0]
|
||||
if not isinstance(typespec, str):
|
||||
return None
|
||||
if not re.match(NODE_NAME_PATTERN, typespec):
|
||||
return None
|
||||
if not typespec[0].isupper():
|
||||
return None
|
||||
return typespec
|
||||
|
||||
|
||||
def _typespec(rule):
|
||||
if not _get_node_class_name(rule):
|
||||
return []
|
||||
|
||||
spec = rule.params[0].split(BASE_CLASS_TOKEN)
|
||||
class_names = [safe_name(n) for n in spec] + ['ModelBase']
|
||||
|
||||
typespec = []
|
||||
for i, class_name in enumerate(class_names[:-1]):
|
||||
base = class_names[i + 1]
|
||||
typespec.append(TypeSpec(class_name, base))
|
||||
|
||||
return typespec
|
||||
|
||||
|
||||
def _get_full_name(cls):
|
||||
if not inspect.isclass(cls):
|
||||
raise CodegenError('Base type has to be a class')
|
||||
module = inspect.getmodule(cls)
|
||||
if not module:
|
||||
raise CodegenError('Base type has to be inside a module')
|
||||
modulename = module.__name__
|
||||
|
||||
name = cls.__qualname__
|
||||
|
||||
# Try to reference the class
|
||||
try:
|
||||
idents = name.split('.')
|
||||
cls_ = getattr(module, idents[0])
|
||||
for ident in idents[1:]:
|
||||
cls_ = getattr(cls_, ident)
|
||||
|
||||
assert cls_ == cls
|
||||
except AttributeError as e:
|
||||
raise CodegenError(
|
||||
"Couldn't find base type, it has to be importable",
|
||||
) from e
|
||||
|
||||
return modulename, name
|
||||
|
||||
|
||||
class BaseTypeRenderer(Renderer):
|
||||
def __init__(self, base_type):
|
||||
super().__init__()
|
||||
self.base_type = base_type
|
||||
|
||||
def render_fields(self, fields):
|
||||
module, name = _get_full_name(self.base_type)
|
||||
if '.' in name:
|
||||
lookup = f'\nModelBase = {name}'
|
||||
name = name.split('.')[0]
|
||||
else:
|
||||
lookup = ' as ModelBase'
|
||||
|
||||
fields.update(module=module, name=name, lookup=lookup)
|
||||
|
||||
template = """
|
||||
from {module} import {name}{lookup}\
|
||||
"""
|
||||
|
||||
|
||||
class BaseClassRenderer(Renderer):
|
||||
def __init__(self, spec):
|
||||
super().__init__()
|
||||
self.class_name = spec.class_name
|
||||
self.base = spec.base
|
||||
|
||||
template = """
|
||||
@dataclass(eq=False)
|
||||
class {class_name}({base}):
|
||||
pass\
|
||||
"""
|
||||
|
||||
|
||||
class ObjectModelCodeGenerator(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, ModelRenderer):
|
||||
raise CodegenError(f'Renderer for {name} not found')
|
||||
return renderer
|
||||
|
||||
|
||||
class Rule(ModelRenderer):
|
||||
def render_fields(self, fields):
|
||||
defs = [safe_name(d) for d, _ in compress_seq(self.defines())]
|
||||
defs = sorted(set(defs))
|
||||
spec = fields['spec']
|
||||
|
||||
kwargs = '\n'.join(f'{d}: Any = None' for d in defs)
|
||||
kwargs = indent(kwargs) if kwargs else indent('pass')
|
||||
|
||||
fields.update(
|
||||
class_name=spec.class_name, base=spec.base, kwargs=kwargs,
|
||||
)
|
||||
|
||||
template = """
|
||||
@dataclass(eq=False)
|
||||
class {class_name}({base}):
|
||||
{kwargs}\
|
||||
"""
|
||||
|
||||
|
||||
class Grammar(ModelRenderer):
|
||||
def render_fields(self, fields):
|
||||
node_class_names = set()
|
||||
|
||||
bases = []
|
||||
model_rules = []
|
||||
for rule in self.node.rules:
|
||||
specs = _typespec(rule)
|
||||
if not specs:
|
||||
continue
|
||||
|
||||
node_spec = specs[0]
|
||||
base_spec = reversed(specs[1:])
|
||||
|
||||
if node_spec.class_name not in node_class_names:
|
||||
model_rules.append((rule, node_spec))
|
||||
|
||||
bases.extend(
|
||||
base
|
||||
for base in base_spec
|
||||
if base.class_name not in node_class_names
|
||||
)
|
||||
|
||||
node_class_names.update(s.class_name for s in specs)
|
||||
|
||||
base_class_declarations = [
|
||||
BaseClassRenderer(spec).render() for spec in bases
|
||||
]
|
||||
|
||||
model_class_declarations = [
|
||||
self.get_renderer(rule).render(spec=spec)
|
||||
for rule, spec in model_rules
|
||||
]
|
||||
|
||||
base_class_declarations = '\n\n\n'.join(base_class_declarations)
|
||||
if base_class_declarations:
|
||||
base_class_declarations += '\n\n'
|
||||
model_class_declarations = '\n\n\n'.join(model_class_declarations)
|
||||
|
||||
version = datetime.now().strftime('%Y.%m.%d.%H')
|
||||
|
||||
base_type = fields['base_type']
|
||||
|
||||
fields.update(
|
||||
name=self.node.name,
|
||||
base_class_declarations=base_class_declarations,
|
||||
model_class_declarations=model_class_declarations,
|
||||
version=version,
|
||||
base_type=BaseTypeRenderer(base_type).render()
|
||||
if base_type
|
||||
else DEFAULT_BASE_TYPE,
|
||||
)
|
||||
|
||||
template = """\
|
||||
#!/usr/bin/env python
|
||||
|
||||
# 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.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from dataclasses import dataclass
|
||||
|
||||
from tatsu.objectmodel import Node
|
||||
from tatsu.semantics import ModelBuilderSemantics
|
||||
|
||||
{base_type}
|
||||
|
||||
class {name}ModelBuilderSemantics(ModelBuilderSemantics):
|
||||
def __init__(self, context=None, types=None):
|
||||
types = [
|
||||
t for t in globals().values()
|
||||
if type(t) is type and issubclass(t, ModelBase)
|
||||
] + (types or [])
|
||||
super().__init__(context=context, types=types)
|
||||
|
||||
{base_class_declarations}
|
||||
{model_class_declarations}
|
||||
"""
|
||||
@@ -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))
|
||||
"""
|
||||
Reference in New Issue
Block a user