From d063ebbac63eea153578725ef4e822b6b303c769 Mon Sep 17 00:00:00 2001 From: JOLIMAITRE Matthieu Date: Tue, 21 May 2024 07:27:23 +0200 Subject: [PATCH] add concat and examples --- examples/evalexpr.py | 89 ++++++++++++++++++++++++++ src/pyalibert/parser.py | 16 ++++- src/pyalibert/transformers/__init__.py | 5 +- src/pyalibert/transformers/concat.py | 27 ++++++++ src/pyalibert/transformers/join.py | 24 +++++++ 5 files changed, 158 insertions(+), 3 deletions(-) create mode 100755 examples/evalexpr.py create mode 100644 src/pyalibert/transformers/concat.py create mode 100644 src/pyalibert/transformers/join.py diff --git a/examples/evalexpr.py b/examples/evalexpr.py new file mode 100755 index 0000000..b3c1aeb --- /dev/null +++ b/examples/evalexpr.py @@ -0,0 +1,89 @@ +#!/bin/env -S python + +from dataclasses import dataclass +from typing import TypeVar, Union +from os.path import dirname +import sys + +sys.path.append(f"{dirname(__file__)}/../src") +from pyalibert import regex, Parser, just, Declare + +# ast modeling + +@dataclass +class OpAdd: + def apply(self, left: float, right: float): return left + right + +@dataclass +class OpSub: + def apply(self, left: float, right: float): return left - right + +@dataclass +class OpMul: + def apply(self, left: float, right: float): return left * right + +@dataclass +class OpDiv: + def apply(self, left: float, right: float): return left / right + +@dataclass +class BinOp: + actual: Union[OpAdd, OpSub, OpMul, OpDiv] + def apply(self, left: float, right: float): return self.actual.apply(left, right) + +@dataclass +class Value: + actual: float + +@dataclass +class Math: + first: "Expr" + rest: list[tuple[BinOp, "Expr"]] + def eval(self) -> float: + value = self.first.eval() + for (op, oth) in self.rest: value = op.apply(value, oth.eval()) + return value + +@dataclass +class Expr: + actual: Union[Value, Math] + def eval(self) -> float: + if isinstance(self.actual, Value): return self.actual.actual + else: return self.actual.eval() + + +T = TypeVar("T") +def lexeme(p: Parser[T]): return p << just(" ").repeat() + +none = just('') +oper = lexeme(just("+").set(OpAdd()) | just("-").set(OpSub()) | just("*").set(OpMul()) | just("/").set(OpDiv())).map(BinOp) +(lbrace, rbrace) = (lexeme(just('(')), lexeme(just(')'))) + +digit = regex("[0-9]") +integer = digit + digit.repeat().join() +signed = (just("-") | none) + integer +decimal = just(".") + integer +value = lexeme(signed + (decimal | none)).map(float).map(Value) + +math = Declare[Math]() +expr = (value | math.parser()).map(Expr) +math_inner = (expr & (oper & expr).repeat()).map(lambda r: Math(*r)) +math.define(lbrace >> math_inner << rbrace) + +parser = math_inner + + +def repl(): + print("[ New super EvalExpr. ]") + print("[ 100% composed and typed ]") + print("> ", end="") + sys.stdout.flush() + for line in sys.stdin: + result = parser.parse(line.strip()) + print(result) + print(f"= {result.eval()}") + print("> ", end="") + sys.stdout.flush() + + +if __name__ == "__main__": repl() diff --git a/src/pyalibert/parser.py b/src/pyalibert/parser.py index b382217..a54b741 100644 --- a/src/pyalibert/parser.py +++ b/src/pyalibert/parser.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Callable, Generic, TypeVar +from typing import Callable, Generic, Optional, TypeVar, Union from .result import ParseError, Failure from .transformer import Transformer @@ -13,7 +13,9 @@ from .transformers import ( SepListTransform, OptionTransform, MapTransform, - ValueTransform + ValueTransform, + JoinTransform, + ConcatTransform, ) @@ -64,6 +66,12 @@ class Parser(Generic[P]): def sep_by(self, sep: "Parser[T]"): return Parser(SepListTransform(self.inner, sep.inner)) + def join(self: "Parser[list[str]]", join: str = ""): + return Parser(JoinTransform(self.inner, join)) + + def concat(self: "Parser[tuple[str,str]]", join: str = ""): + return Parser(ConcatTransform(self.inner, join)) + # | def __or__(self, other: "Parser[T]"): return self.or_(other) @@ -79,3 +87,7 @@ class Parser(Generic[P]): # << def __lshift__(self, other: "Parser[T]"): return self.and_(other).map(lambda v: v[0]) + + # + + def __add__(self: "Parser[str]", other: "Parser[str]"): + return self.and_(other).concat() diff --git a/src/pyalibert/transformers/__init__.py b/src/pyalibert/transformers/__init__.py index 1705940..08dc3bb 100644 --- a/src/pyalibert/transformers/__init__.py +++ b/src/pyalibert/transformers/__init__.py @@ -10,4 +10,7 @@ from .sep_list import SepListTransform from .option import OptionTransform from .map import MapTransform -from .value import ValueTransform \ No newline at end of file +from .value import ValueTransform + +from .concat import ConcatTransform +from .join import JoinTransform diff --git a/src/pyalibert/transformers/concat.py b/src/pyalibert/transformers/concat.py new file mode 100644 index 0000000..a9c43b4 --- /dev/null +++ b/src/pyalibert/transformers/concat.py @@ -0,0 +1,27 @@ +from dataclasses import dataclass + +from ..result import Result +from ..transformer import Transformer +from .map import MapTransform + + +ConcatInput = Transformer[str] | tuple["ConcatInput", "ConcatInput"] + + +@dataclass +class ConcatTransform(Transformer[str]): + from_: Transformer[tuple[str, str]] + join: str + + def __init__(self, from_: Transformer[tuple[str, str]], join: str) -> None: + self.from_ = from_ + self.join = join + self.actual = MapTransform(self.from_, lambda l: join.join(l)) + + def parse(self, stream: str, at_index: int) -> Result[str]: + return self.actual.parse(stream, at_index) + + +from okipy.lib import test + +# TOTEST diff --git a/src/pyalibert/transformers/join.py b/src/pyalibert/transformers/join.py new file mode 100644 index 0000000..438e655 --- /dev/null +++ b/src/pyalibert/transformers/join.py @@ -0,0 +1,24 @@ +from dataclasses import dataclass + +from ..result import Result +from ..transformer import Transformer +from .map import MapTransform + + +@dataclass +class JoinTransform(Transformer[str]): + items: Transformer[list[str]] + join: str + + def __init__(self, items: Transformer[list[str]], join: str) -> None: + self.items = items + self.join = "" + self.actual = MapTransform(items, lambda l: join.join(l)) + + def parse(self, stream: str, at_index: int) -> Result[str]: + return self.actual.parse(stream, at_index) + + +from okipy.lib import test + +# TOTEST