From 9627b9060364c7fcf1b33d9724c677ca421c4ca7 Mon Sep 17 00:00:00 2001 From: Seokhyun Lee <7948302+henrylee97@users.noreply.github.com> Date: Thu, 11 Jul 2024 16:37:29 +0900 Subject: [PATCH] Release 1.2.0 (#17) - Add example to sum the given list - Version up plare to 1.2.0 --- examples/sum_of_list/README.md | 41 +++++++++++++++ examples/sum_of_list/sum.py | 96 ++++++++++++++++++++++++++++++++++ plare/__init__.py | 2 +- 3 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 examples/sum_of_list/README.md create mode 100644 examples/sum_of_list/sum.py diff --git a/examples/sum_of_list/README.md b/examples/sum_of_list/README.md new file mode 100644 index 0000000..efb6554 --- /dev/null +++ b/examples/sum_of_list/README.md @@ -0,0 +1,41 @@ +# Sum of list with Plare + +`sum.py` offers a simple sum of list implemented with `plare`. +To test the program first, run the following command. +```bash +$ python sum.py "[1, 2, 3, 4, 5]" +``` +The program will parse the input, interprete, and show the answer as follows. +``` +15 +``` + +## Defining Language +To make a parser, first thing to do is defining a language. +The following is the language of the sum of list, where `N` represents an integer: +``` +list -> N::list + | [N] + | [] +``` + +A node in AST must initialized with `Token` objects or other node objects. +That is, You need to define 3 different type of list, which are initialized with different arguments, to support the arbitrary length of list: `IntList`, `SingleIntList`, and `EmptyIntList`. + +## Defining Lexer +A lexer will tokenize the given source string. +To make a lexer you only have to do is make tokens that inherits`Token` class. +In this example, you only need to define 4 tokens: `COMMA`, `LBRACKET`, `RBRACKET`, and `NUM`. + +## Defining Parser +A parser will parse the tokens and build an AST. +The CFG for the list is as follows: +``` +list -> LBRACKET items RBRACKET -> $1 +items -> N COMMA items -> IntList($0, $2) + | N -> SingleIntList($0) + | -> EmptyIntList() +``` + +## Implementation +You can find implementation of the above example in `sum.py`. diff --git a/examples/sum_of_list/sum.py b/examples/sum_of_list/sum.py new file mode 100644 index 0000000..7b4e1ca --- /dev/null +++ b/examples/sum_of_list/sum.py @@ -0,0 +1,96 @@ +from __future__ import annotations + +from argparse import ArgumentParser +from typing import Any + +from plare.lexer import Lexer +from plare.parser import Parser +from plare.token import Token + + +class NUM(Token): + value: int + + def __init__(self, value: str, *, lineno: int, offset: int) -> None: + super().__init__(value, lineno=lineno, offset=offset) + self.value = int(value) + + def __hash__(self) -> int: + return hash(self.value) + super().__hash__() + + def __eq__(self, other: Any) -> bool: + return ( + isinstance(other, self.__class__) + and self.value == other.value + and super().__eq__(other) + ) + + +class LBRACKET(Token): + pass + + +class RBRACKET(Token): + pass + + +class COMMA(Token): + pass + + +class IntList: + items: list[int] + + def __init__(self, item: NUM, tail: IntList) -> None: + self.items = [item.value, *tail.items] + + +class SingleIntList(IntList): + def __init__(self, item: NUM) -> None: + self.items = [item.value] + + +class EmptyIntList(IntList): + def __init__(self) -> None: + self.items = [] + + +def main() -> None: + argparser = ArgumentParser() + argparser.add_argument("input", type=str) + args = argparser.parse_args() + + lexer = Lexer( + { + "start": [ + (r"[ \t\n]+", "start"), + (r"\[", LBRACKET), + (r"\]", RBRACKET), + (r",", COMMA), + (r"-?\d+", NUM), + ] + } + ) + + parser = Parser[IntList]( + { + "list": [ + ([LBRACKET, "items", RBRACKET], None, [1]), + ], + "items": [ + ([NUM, COMMA, "items"], IntList, [0, 2]), + ([NUM], SingleIntList, [0]), + ([], EmptyIntList, []), + ], + } + ) + + lexbuf = lexer.lex("start", args.input) + parsed = parser.parse("list", lexbuf) + if isinstance(parsed, Token): # Just for type checking + raise Exception("Something went wrong") + print(sum(parsed.items)) + + +if __name__ == "__main__": + main() diff --git a/plare/__init__.py b/plare/__init__.py index 6849410..c68196d 100644 --- a/plare/__init__.py +++ b/plare/__init__.py @@ -1 +1 @@ -__version__ = "1.1.0" +__version__ = "1.2.0"