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 from __future__ import annotations
import enum import enum
import inspect
import logging import logging
import os import os
import typing
from typing import Callable
_TRACE = (logging.DEBUG + logging.NOTSET) // 2 _TRACE = (logging.DEBUG + logging.NOTSET) // 2
logging.addLevelName(_TRACE, "TRACE") logging.addLevelName(_TRACE, "TRACE")
@ -78,4 +81,53 @@ class Logger:
self.log(LogLevel.Trace, *args) 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") logger = Logger("compiler")

View file

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