logger+parser: add a Tracer class featuring function-wrappers and reflection

Generates clean function call stack traces as they are called
This commit is contained in:
Antoine Viallon 2023-05-10 01:21:59 +02:00
parent 3e9d308e40
commit 017aefa750
Signed by: aviallon
GPG key ID: D126B13AB555E16F
2 changed files with 66 additions and 2 deletions

View file

@ -1,8 +1,11 @@
from __future__ import annotations
import enum
import inspect
import logging
import os
import typing
from typing import Callable
_TRACE = (logging.DEBUG + logging.NOTSET) // 2
logging.addLevelName(_TRACE, "TRACE")
@ -78,4 +81,53 @@ class Logger:
self.log(LogLevel.Trace, *args)
T = typing.TypeVar("T")
class Tracer:
def __init__(self, logger: Logger, level: LogLevel = LogLevel.Trace, max_depth: int = 15):
self.depth: int = 0
self.level = level
self.logger = logger
self.max_depth: int = max_depth
def trace_method(self, func_or_nothing: Callable[..., T] | None = None, **kwargs):
if func_or_nothing is not None:
return self._trace_method(func=func_or_nothing, **kwargs)
return lambda func: self._trace_method(func, **kwargs)
def _trace_method(self, func: Callable[..., T], level: LogLevel = None) -> Callable[..., T]:
if level is None:
level = self.level
if self.logger.level > level:
return func
func_info = inspect.signature(func)
def _wrapped(*args, **kwargs) -> T:
bound_args = func_info.bind(*args, **kwargs)
signature = ", ".join(
key if key == "self" else f"{key}={repr(value)}"
for key, value in bound_args.arguments.items()
)
prefix = f"[{self.depth: >4}]" + " " * self.depth
if self.depth == self.max_depth:
self.logger.log(level, f"{prefix} > ... (max depth of {self.max_depth} reached)")
if self.depth < self.max_depth:
self.logger.log(level, f"{prefix} > Entering {func.__name__}({signature})")
self.depth += 1
r: T = func(*args, **kwargs)
self.depth -= 1
if self.depth < self.max_depth:
self.logger.log(level, f"{prefix} < Leaving {func.__name__}({signature})")
return r
return _wrapped
logger = Logger("compiler")

View file

@ -3,13 +3,13 @@ from __future__ import annotations
from beartype.typing import List, Dict, Callable
from .errors import CompilationError, UnexpectedTokenError
from .logger import Logger
from .logger import Logger, Tracer, LogLevel
from .nodes import Float, Sum, Value, Product, Node, Division, Sub, Integer, Expression, Identifier, Assignment, \
Variable
from .tokenizer import Tokens, Token
logger = Logger(__name__)
tracer = Tracer(logger, level=LogLevel.Debug)
class Parser:
def __init__(self, tokens: List[Token]):
@ -31,6 +31,7 @@ class Parser:
self.pos += 1
logger.debug(f"Advancing to token {self.pos} {self.token}")
@tracer.trace_method(level=LogLevel.Trace)
def accept(self, *token_types: Tokens) -> False | Token:
tok = self.token
if self.token.kind in token_types:
@ -45,6 +46,7 @@ class Parser:
return tok
return False
@tracer.trace_method(level=LogLevel.Trace)
def expect(self, token_type: Tokens) -> Token:
r = self.accept(token_type)
logger.debug(f"Expecting {token_type}, got {r}")
@ -52,6 +54,7 @@ class Parser:
raise UnexpectedTokenError(self.token, token_type)
return r
@tracer.trace_method
def number(self, mandatory: bool = False):
if tok := self.accept(Tokens.Float):
logger.debug(f"Found float {tok}")
@ -62,18 +65,21 @@ class Parser:
elif mandatory:
raise UnexpectedTokenError(self.token, "integer or float")
@tracer.trace_method
def identifier(self, mandatory: bool = False) -> Identifier:
if ident := self.accept(Tokens.Identifier):
return Identifier(location=ident.loc, name=str(ident.value))
elif mandatory:
raise UnexpectedTokenError(self.token, "identifier")
@tracer.trace_method
def variable(self, mandatory: bool = False) -> Variable:
if ident := self.identifier(mandatory=False):
return Variable(location=ident.location(), identifier=ident)
elif mandatory:
raise UnexpectedTokenError(self.token, "variable identifier")
@tracer.trace_method
def binary_op(self, operand_func: Callable[[], Value], operators: Dict[Tokens, Value]):
operand = operand_func()
@ -85,6 +91,7 @@ class Parser:
return operand
@tracer.trace_method
def factor(self) -> Value:
if self.accept(Tokens.Parens_Left):
v = self.expression()
@ -97,18 +104,21 @@ class Parser:
else:
raise UnexpectedTokenError(self.token, "parenthesized expression, number or variable")
@tracer.trace_method
def term(self) -> Value:
return self.binary_op(self.factor, operators={
Tokens.Op_Multiply: Product,
Tokens.Op_Divide: Division,
})
@tracer.trace_method
def summation(self) -> Sum:
return self.binary_op(self.term, operators={
Tokens.Op_Plus: Sum,
Tokens.Op_Minus: Sub,
})
@tracer.trace_method
def assignment(self, mandatory: bool = False) -> Assignment:
if ident := self.identifier(mandatory):
self.expect(Tokens.Equal)
@ -117,12 +127,14 @@ class Parser:
elif mandatory:
raise UnexpectedTokenError(self.token, "assignment")
@tracer.trace_method
def expression(self) -> Value:
if self.peek(Tokens.Identifier):
return Expression(self.assignment())
else:
return Expression(self.summation())
@tracer.trace_method
def root(self) -> Node:
return self.expression()