"""
Implementation of the renderer.
"""
import re
from dataclasses import dataclass
from typing import Any, List, Union
from paxter.core import (
Fragment, FragmentList, Identifier, Number, Operator,
PaxterApply, PaxterPhrase, Text, Token, TokenList,
)
from paxter.core.exceptions import PaxterRenderError
from paxter.core.line_col import LineCol
from paxter.renderers.python import flatten
from paxter.renderers.python.wrappers import BaseApply, NormalApply
BACKSLASH_NEWLINE_RE = re.compile(r'\\\n')
[docs]@dataclass
class RenderContext:
"""
A suite of Paxter document tree renderer.
Users of this renderer may embed and run python code
directly from within the Paxter document source file.
"""
#: Document source text
input_text: str
#: Python execution environment data
env: dict
#: Parsed document tree
tree: FragmentList
[docs] def render(self) -> str:
"""
Transforms the already provided input source text,
the initial python execution environment data,
and the parsed document tree, into the final output.
"""
return flatten(self.transform_fragment_list(self.tree))
def transform_token(self, token: Token) -> Any:
if isinstance(token, Fragment):
return self.transform_fragment(token)
if isinstance(token, TokenList):
return self.transform_token_list(token)
if isinstance(token, Identifier):
return self.transform_identifier(token)
if isinstance(token, Operator):
return self.transform_operator(token)
if isinstance(token, Number):
return self.transform_number(token)
raise PaxterRenderError(
"unrecognized token at %(pos)s",
pos=LineCol(self.input_text, token.start_pos),
)
def transform_fragment(self, fragment: Fragment) -> Any:
if isinstance(fragment, FragmentList):
return self.transform_fragment_list(fragment)
if isinstance(fragment, Text):
return self.transform_text(fragment)
if isinstance(fragment, PaxterPhrase):
return self.transform_paxter_phrase(fragment)
if isinstance(fragment, PaxterApply):
return self.transform_paxter_apply(fragment)
raise PaxterRenderError(
"unrecognized fragment at %(pos)s",
pos=LineCol(self.input_text, fragment.start_pos),
)
def transform_token_list(self, seq: TokenList):
raise PaxterRenderError(
"token list not expected at %(pos)s",
pos=LineCol(self.input_text, seq.start_pos),
)
def transform_identifier(self, token: Identifier):
raise PaxterRenderError(
"identifier not expected at %(pos)",
pos=LineCol(self.input_text, token.start_pos),
)
def transform_operator(self, token: Operator):
raise PaxterRenderError(
"operator not expected at %(pos)",
pos=LineCol(self.input_text, token.start_pos),
)
def transform_number(self, token: Number) -> Union[int, float]:
return token.value
def transform_fragment_list(self, seq: FragmentList) -> List[Any]:
return [
self.transform_fragment(fragment)
for fragment in seq.children
]
def transform_text(self, token: Text) -> str:
text = token.inner
if not token.scope_pattern.opening:
text = BACKSLASH_NEWLINE_RE.sub('', text)
return text
def transform_paxter_phrase(self, token: PaxterPhrase) -> Any:
# Fetch the phrase evaluation function from within the environment
try:
phrase_eval = self.env['_phrase_eval_']
except KeyError as exc:
raise PaxterRenderError(
"expected '_phrase_eval_' to be defined at %(pos)s",
pos=LineCol(self.input_text, token.start_pos),
) from exc
# Evaluate the expression embedded within the phrase
try:
return phrase_eval(token.inner, self.env)
except PaxterRenderError:
raise
except Exception as exc:
raise PaxterRenderError(
f"paxter phrase evaluation error at %(pos)s",
pos=LineCol(self.input_text, token.start_pos),
) from exc
def transform_paxter_apply(self, token: PaxterApply):
# Fetch the function from within the environment
try:
func = self.env[token.id.name]
except KeyError as exc:
raise PaxterRenderError(
f"unknown paxter application with id {token.id.name!r} at %(pos)s",
pos=LineCol(self.input_text, token.start_pos),
) from exc
# Wrap the function if not yet wrapped
if not isinstance(func, BaseApply):
func = NormalApply(func)
# Make the call to the wrapped function
try:
return func.call(self, token)
except PaxterRenderError:
raise
except Exception as exc:
raise PaxterRenderError(
f"paxter apply evaluation error at %(pos)s",
pos=LineCol(self.input_text, token.start_pos),
) from exc