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:
parent
3e9d308e40
commit
017aefa750
2 changed files with 66 additions and 2 deletions
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue