sane-date-math: use Productions as objects
This commit is contained in:
parent
7b01822ee7
commit
51a96525d9
|
@ -183,12 +183,12 @@ class ProductionContext(ParserContext):
|
||||||
and then parse "all in one go", sealing away incomplete state, and converting
|
and then parse "all in one go", sealing away incomplete state, and converting
|
||||||
the parsed tokens into actually useful abstractions (like signed numbers).
|
the parsed tokens into actually useful abstractions (like signed numbers).
|
||||||
"""
|
"""
|
||||||
def __init__(self, production_cls: type, grammar: ParserContext):
|
def __init__(self, production: 'Production', context: ParserContext = None):
|
||||||
self.production_cls = production_cls
|
self.production = production
|
||||||
self.context = grammar
|
self.context = context if context is not None else production.grammar()
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"ProductionContext({self.production_cls.__name__!r}, {self.context!r})"
|
return f"ProductionContext({self.production!r}, {self.context!r})"
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return str(self.context)
|
return str(self.context)
|
||||||
|
@ -209,23 +209,20 @@ class ProductionContext(ParserContext):
|
||||||
def reduce(self) -> object:
|
def reduce(self) -> object:
|
||||||
# XXX this ends up being a leaf -> root reduction,
|
# XXX this ends up being a leaf -> root reduction,
|
||||||
# which generally makes it harder to achieve detailed control when nesting.
|
# which generally makes it harder to achieve detailed control when nesting.
|
||||||
return self.production_cls.reduce(self.reduce_inner(self.context))
|
return self.production.reduce(self.reduce_inner(self.context))
|
||||||
|
|
||||||
class Production:
|
class Production:
|
||||||
"""
|
"""
|
||||||
non-generic, likely multi-token productions,
|
non-generic, likely multi-token productions,
|
||||||
specified in terms of other Productions and the above primitives
|
specified in terms of other Productions and the above primitives
|
||||||
"""
|
"""
|
||||||
@staticmethod
|
def grammar(self) -> ParserContext:
|
||||||
def grammar() -> ParserContext:
|
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@classmethod
|
def context(self) -> ParserContext:
|
||||||
def context(cls) -> ParserContext:
|
return ProductionContext(self)
|
||||||
return ProductionContext(cls, cls.grammar())
|
|
||||||
|
|
||||||
@classmethod
|
def reduce(self, inner: object) -> object:
|
||||||
def reduce(cls, inner: object) -> object:
|
|
||||||
"""
|
"""
|
||||||
use to construct the outer types out of already-converted inner types.
|
use to construct the outer types out of already-converted inner types.
|
||||||
e.g. Number = Then([optional(Minus), Digits, optional(Suffix)])
|
e.g. Number = Then([optional(Minus), Digits, optional(Suffix)])
|
||||||
|
@ -236,25 +233,22 @@ class Production:
|
||||||
|
|
||||||
class DigitProduction(Production):
|
class DigitProduction(Production):
|
||||||
""" one digit token """
|
""" one digit token """
|
||||||
@staticmethod
|
def grammar(self) -> ParserContext:
|
||||||
def grammar() -> ParserContext:
|
|
||||||
return WantToken(DIGITS)
|
return WantToken(DIGITS)
|
||||||
|
|
||||||
@staticmethod
|
def reduce(self, inner: Token) -> int:
|
||||||
def reduce(inner: Token) -> int:
|
|
||||||
return int(inner.c)
|
return int(inner.c)
|
||||||
|
|
||||||
class IntProduction(Production):
|
class IntProduction(Production):
|
||||||
""" multi-digit integer """
|
""" multi-digit integer """
|
||||||
@staticmethod
|
def grammar(self) -> ParserContext:
|
||||||
def grammar() -> ParserContext:
|
|
||||||
return Then([
|
return Then([
|
||||||
DigitProduction,
|
DigitProduction(),
|
||||||
optional(IntProduction),
|
optional(IntProduction()),
|
||||||
])
|
])
|
||||||
|
|
||||||
@staticmethod
|
def reduce(self, inner: list) -> int:
|
||||||
def reduce(inner: list) -> int:
|
# TODO: wrong associativity
|
||||||
leading, trailing = inner
|
leading, trailing = inner
|
||||||
if trailing is None:
|
if trailing is None:
|
||||||
return leading
|
return leading
|
||||||
|
@ -264,14 +258,13 @@ class IntProduction(Production):
|
||||||
class DurationOrIntProduction(Production):
|
class DurationOrIntProduction(Production):
|
||||||
# due to a lack of lookahead, we combine duration and int parsing into one production
|
# due to a lack of lookahead, we combine duration and int parsing into one production
|
||||||
# because a duration shares a complete int as prefix
|
# because a duration shares a complete int as prefix
|
||||||
@staticmethod
|
def grammar(self) -> ParserContext:
|
||||||
def grammar() -> ParserContext:
|
|
||||||
return Then([
|
return Then([
|
||||||
IntProduction,
|
IntProduction(),
|
||||||
optional(WantToken(Token('d'))),
|
optional(WantToken(Token('d'))),
|
||||||
])
|
])
|
||||||
|
|
||||||
def reduce(inner: list) -> 'Literal':
|
def reduce(self, inner: list) -> 'Literal':
|
||||||
value, suffix = inner
|
value, suffix = inner
|
||||||
if suffix is None:
|
if suffix is None:
|
||||||
return Literal(value)
|
return Literal(value)
|
||||||
|
@ -279,46 +272,41 @@ class DurationOrIntProduction(Production):
|
||||||
return Literal(timedelta(value))
|
return Literal(timedelta(value))
|
||||||
|
|
||||||
class Whitespace(Production):
|
class Whitespace(Production):
|
||||||
@staticmethod
|
def grammar(self) -> ParserContext:
|
||||||
def grammar() -> ParserContext:
|
|
||||||
return Then([
|
return Then([
|
||||||
WantToken(SPACE),
|
WantToken(SPACE),
|
||||||
optional(Whitespace),
|
optional(Whitespace()),
|
||||||
])
|
])
|
||||||
|
|
||||||
class ParenthesizedExpr(Production):
|
class ParenthesizedExpr(Production):
|
||||||
@staticmethod
|
def grammar(self) -> ParserContext:
|
||||||
def grammar() -> ParserContext:
|
|
||||||
return Then([
|
return Then([
|
||||||
WantToken(OPEN_PAREN),
|
WantToken(OPEN_PAREN),
|
||||||
Expr,
|
Expr(),
|
||||||
WantToken(CLOSE_PAREN),
|
WantToken(CLOSE_PAREN),
|
||||||
])
|
])
|
||||||
|
|
||||||
def reduce(inner: list) -> object:
|
def reduce(self, inner: list) -> 'AstItem':
|
||||||
open, expr, close = inner
|
open, expr, close = inner
|
||||||
return expr
|
return expr
|
||||||
|
|
||||||
class IdentifierTail(Production):
|
class IdentifierTail(Production):
|
||||||
@staticmethod
|
def grammar(self) -> ParserContext:
|
||||||
def grammar() -> ParserContext:
|
|
||||||
return Then([
|
return Then([
|
||||||
WantToken(ALPHA_NUM_UNDER),
|
WantToken(ALPHA_NUM_UNDER),
|
||||||
optional(IdentifierTail),
|
optional(IdentifierTail()),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
class Identifier(Production):
|
class Identifier(Production):
|
||||||
""" variable-style identifier, e.g. 'TODAY' """
|
""" variable-style identifier, e.g. 'TODAY' """
|
||||||
@staticmethod
|
def grammar(self) -> ParserContext:
|
||||||
def grammar() -> ParserContext:
|
|
||||||
return Then([
|
return Then([
|
||||||
WantToken(ALPHA_UNDER),
|
WantToken(ALPHA_UNDER),
|
||||||
optional(IdentifierTail),
|
optional(IdentifierTail()),
|
||||||
])
|
])
|
||||||
|
|
||||||
@staticmethod
|
def reduce(self, inner: list) -> 'Literal':
|
||||||
def reduce(inner: list) -> 'Literal':
|
|
||||||
# fold the tokens into a string
|
# fold the tokens into a string
|
||||||
first, rest = inner
|
first, rest = inner
|
||||||
head = first.c
|
head = first.c
|
||||||
|
@ -329,46 +317,41 @@ class Identifier(Production):
|
||||||
|
|
||||||
class UnaryExpr(Production):
|
class UnaryExpr(Production):
|
||||||
""" some expression which does not invoke any operators at the outermost level """
|
""" some expression which does not invoke any operators at the outermost level """
|
||||||
@staticmethod
|
def grammar(self) -> ParserContext:
|
||||||
def grammar() -> ParserContext:
|
|
||||||
return Then([
|
return Then([
|
||||||
optional(Whitespace),
|
optional(Whitespace()),
|
||||||
Choice([
|
Choice([
|
||||||
DurationOrIntProduction,
|
DurationOrIntProduction(),
|
||||||
Identifier,
|
Identifier(),
|
||||||
ParenthesizedExpr,
|
ParenthesizedExpr(),
|
||||||
]),
|
]),
|
||||||
optional(Whitespace),
|
optional(Whitespace()),
|
||||||
])
|
])
|
||||||
|
|
||||||
@staticmethod
|
def reduce(self, inner: list):
|
||||||
def reduce(inner: list):
|
|
||||||
# drop the whitespace
|
# drop the whitespace
|
||||||
leading, primary, trailing = inner
|
leading, primary, trailing = inner
|
||||||
return primary
|
return primary
|
||||||
|
|
||||||
class ExprRHS(Production):
|
class ExprRHS(Production):
|
||||||
""" right hand side of a binary operation """
|
""" right hand side of a binary operation """
|
||||||
@staticmethod
|
def grammar(self) -> ParserContext:
|
||||||
def grammar() -> ParserContext:
|
|
||||||
return Then([
|
return Then([
|
||||||
Choice([WantToken(ASTERISK), WantToken(PLUS), WantToken(MINUS)]),
|
Choice([WantToken(ASTERISK), WantToken(PLUS), WantToken(MINUS)]),
|
||||||
# remaining, is just another `Expr`, but we need to keep the fields expanded here to control precedence.
|
# remaining, is just another `Expr`, but we need to keep the fields expanded here to control precedence.
|
||||||
UnaryExpr,
|
UnaryExpr(),
|
||||||
Choice([ExprRHS, Empty()]),
|
Choice([ExprRHS(), Empty()]),
|
||||||
])
|
])
|
||||||
|
|
||||||
class Expr(Production):
|
class Expr(Production):
|
||||||
""" this is the top-level production """
|
""" this is the top-level production """
|
||||||
@staticmethod
|
def grammar(self) -> ParserContext:
|
||||||
def grammar() -> ParserContext:
|
|
||||||
return Then([
|
return Then([
|
||||||
UnaryExpr,
|
UnaryExpr(),
|
||||||
Choice([ExprRHS, Empty()])
|
Choice([ExprRHS(), Empty()])
|
||||||
])
|
])
|
||||||
|
|
||||||
@staticmethod
|
def reduce(self, inner: list):
|
||||||
def reduce(inner: list):
|
|
||||||
lhs, rhs = inner
|
lhs, rhs = inner
|
||||||
if rhs is None: return lhs
|
if rhs is None: return lhs
|
||||||
|
|
||||||
|
@ -378,11 +361,11 @@ class Expr(Production):
|
||||||
# multiplication has high precedence and we grab the adjacent token ASAP
|
# multiplication has high precedence and we grab the adjacent token ASAP
|
||||||
lhs = MulOp(lhs, rhs)
|
lhs = MulOp(lhs, rhs)
|
||||||
if rhs_next is not None:
|
if rhs_next is not None:
|
||||||
lhs = Expr.reduce([lhs, rhs_next])
|
lhs = self.reduce([lhs, rhs_next])
|
||||||
else:
|
else:
|
||||||
# reduce the rhs and *then* apply this operator
|
# reduce the rhs and *then* apply this operator
|
||||||
if rhs_next is not None:
|
if rhs_next is not None:
|
||||||
rhs = Expr.reduce([rhs, rhs_next])
|
rhs = self.reduce([rhs, rhs_next])
|
||||||
|
|
||||||
if oper == PLUS:
|
if oper == PLUS:
|
||||||
lhs = AddOp(lhs, rhs)
|
lhs = AddOp(lhs, rhs)
|
||||||
|
@ -449,7 +432,7 @@ class MulOp(BinaryOp):
|
||||||
def tokenize(stream: str) -> list:
|
def tokenize(stream: str) -> list:
|
||||||
return [Token(char) for char in stream]
|
return [Token(char) for char in stream]
|
||||||
|
|
||||||
def parse(ty: type, tokens: list) -> AstItem:
|
def parse(ty: Production, tokens: list) -> AstItem:
|
||||||
ctx = Then([ty, Empty()])
|
ctx = Then([ty, Empty()])
|
||||||
for i, t in enumerate(tokens):
|
for i, t in enumerate(tokens):
|
||||||
result = ctx.feed(t)
|
result = ctx.feed(t)
|
||||||
|
@ -466,7 +449,7 @@ def parse(ty: type, tokens: list) -> AstItem:
|
||||||
|
|
||||||
def evaluate(expr: str) -> object:
|
def evaluate(expr: str) -> object:
|
||||||
tok = tokenize(expr)
|
tok = tokenize(expr)
|
||||||
expr = parse(Expr, tok)
|
expr = parse(Expr(), tok)
|
||||||
print(expr)
|
print(expr)
|
||||||
env = dict(
|
env = dict(
|
||||||
today=datetime.now()
|
today=datetime.now()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user