add proxy for the first part and try to develop the second part
This commit is contained in:
@@ -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}
|
||||
"""
|
||||
Reference in New Issue
Block a user