~kf5jwc/imp-parser

4a8b0d0c4a9b3e235854c13031f981f55fc7ece5 — Kyle Jones 1 year, 11 months ago a91cece
More typing!
M imp_parser/ast/types.py => imp_parser/ast/types.py +2 -2
@@ 1,9 1,9 @@
class Equality(object):
    def __eq__(self, other):
    def __eq__(self, other) -> bool:
        return isinstance(other, self.__class__) and \
               self.__dict__ == other.__dict__

    def __ne__(self, other):
    def __ne__(self, other) -> bool:
        return not self.__eq__(other)



M imp_parser/lexer/__init__.py => imp_parser/lexer/__init__.py +4 -2
@@ 1,11 1,13 @@
import sys
import re
from typing import List, Tuple, Type, Pattern
from enum import Enum
from . import exceptions
from .exceptions import NoMatchFound


# characters is an ~~iterable~~ indexable item which we can match against regex tokens, offered in token_exprs
def lex(characters, token_exprs):
def lex(characters, token_exprs) -> List[Tuple[str, Type[Enum]]]:
    position = 0
    ret_tokens = []
    exprs = precompile_tokens(token_exprs)


@@ 20,7 22,7 @@ def lex(characters, token_exprs):


# Compile all expressions so we avoid doing it for each token
def precompile_tokens(given_tokens):
def precompile_tokens(given_tokens) -> List[Tuple[Pattern[str], Type[Enum]]]:
    tokens = []
    for expr, tag in given_tokens:
        tokens.append((re.compile(expr), tag))

M imp_parser/parser/__init__.py => imp_parser/parser/__init__.py +1 -1
@@ 5,5 5,5 @@ from . import combinators
from .arithmetic import aexp


def parser():
def parser() -> Phrase:
    return Phrase(stmt_list())

M imp_parser/parser/arithmetic.py => imp_parser/parser/arithmetic.py +11 -9
@@ 1,4 1,5 @@
from functools import reduce
from typing import Callable, List, Union
from ..ast import IntAexp, BinopAexp, VarAexp
from ..imp_lexer import Tags
from .combinators import (


@@ 22,37 23,38 @@ ID = Tag(Tags.ID)
AEXP_PRECEDENCE_LEVELS = [["*", "/"], ["+", "-"]]


def keyword(kw):
def keyword(kw) -> Reserved:
    return Reserved(kw, Tags.Reserved)


def aexp_value():
def aexp_value() -> Alternate:
    # Note! The pipe is shorthand for a combinator here.
    a = NUM ^ (lambda i: IntAexp(i))
    b = ID ^ (lambda v: VarAexp(v))
    return a | b


def process_group(parsed):
def process_group(parsed) -> int:
    ((_, p), _) = parsed
    return p


def aexp_group():
def aexp_group() -> Process:
    return keyword("(") + Lazy(aexp) + keyword(")") ^ process_group


def aexp_term():
def aexp_term() -> Alternate:
    return aexp_value() | aexp_group()


def process_binop(op):
def process_binop(op) -> Callable[[int, int], BinopAexp]:
    return lambda l, r: BinopAexp(op, l, r)


def any_operator_in_list(ops):
    op_parsers = [keyword(op) for op in ops]
    parser = reduce(lambda l, r: l | r, op_parsers)
def any_operator_in_list(ops) -> Reserved:
    op_parsers: List[Reserved] = [keyword(op) for op in ops]
    reducer: Callable[..., Reserved] = lambda l, r: l | r;
    parser = reduce(reducer, op_parsers)
    return parser



M imp_parser/parser/boolean.py => imp_parser/parser/boolean.py +8 -7
@@ 1,34 1,35 @@
from typing import Any, Callable, Union
from ..ast import AndBexp, NotBexp, OrBexp, RelopBexp
from .arithmetic import aexp, any_operator_in_list, keyword, precedence, process_group
from .combinators import Lazy
from .combinators import Alternate, Process, Lazy


RELOPS = ["<", "<=", ">", ">=", "=", "!="]
BEXP_PRECEDENCE_LEVELS = [["and"], ["or"]]


def process_relop(parsed):
def process_relop(parsed) -> RelopBexp:
    ((left, op), right) = parsed
    return RelopBexp(op, left, right)


def bexp_relop():
def bexp_relop() -> Process:
    return aexp() + any_operator_in_list(RELOPS) + aexp() ^ process_relop


def bexp_not():
def bexp_not() -> Process:
    return keyword("not") + Lazy(bexp_term) ^ (lambda parsed: NotBexp(parsed[1]))


def bexp_group():
def bexp_group() -> Process:
    return keyword("(") + Lazy(bexp) + keyword(")") ^ process_group


def bexp_term():
def bexp_term() -> Alternate:
    return bexp_not() | bexp_relop() | bexp_group()


def process_logic(op):
def process_logic(op) -> Callable[..., Union[AndBexp, OrBexp]]:
    if op == "and":
        return lambda l, r: AndBexp(l, r)
    elif op == "or":

M imp_parser/parser/combinators.py => imp_parser/parser/combinators.py +13 -15
@@ 1,27 1,25 @@
import attr
from typing import Type, TypeVar, Union
from typing import Type, TypeVar, Union, Optional
from .result import Result


T_PARSER = TypeVar("Parser")

class Parser(object):
    def __call__(self, tokens, pos):
    def __call__(self, tokens, pos) -> Optional[Union[Type["Parser"], Result]]:
        # subclasses should change this
        # I wonder if python has a way to warn about that
        pass

    # oooh, fun. Operator overloading can get confusing.
    def __add__(self, other) -> Type[T_PARSER]:
    def __add__(self, other) -> Concat:
        return Concat(self, other)

    def __mul__(self, other) -> Type[T_PARSER]:
    def __mul__(self, other) -> Exp:
        return Exp(self, other)

    def __or__(self, other) -> Type[T_PARSER]:
    def __or__(self, other) -> Alternate:
        return Alternate(self, other)

    def __xor__(self, other) -> Type[T_PARSER]:
    def __xor__(self, other) -> Process:
        return Process(self, other)




@@ 30,7 28,7 @@ class Reserved(Parser):
    value = attr.ib()
    tag = attr.ib()

    def __call__(self, tokens, pos) -> Union[Result, None]:
    def __call__(self, tokens, pos) -> Optional[Result]:
        if pos < len(tokens):
            (value, tag) = tokens[pos]
            if value == self.value and tag == self.tag:


@@ 43,7 41,7 @@ class Reserved(Parser):
class Tag(Parser):
    tag = attr.ib()

    def __call__(self, tokens, pos) -> Union[Result, None]:
    def __call__(self, tokens, pos) -> Optional[Result]:
        if pos < len(tokens):
            (value, tag) = tokens[pos]
            if tag is self.tag:


@@ 57,7 55,7 @@ class Concat(Parser):
    left = attr.ib()
    right = attr.ib()

    def __call__(self, tokens, pos) -> Union[Result, None]:
    def __call__(self, tokens, pos) -> Optional[Result]:
        left_result = self.left(tokens, pos)
        if left_result is None:
            return None


@@ 97,7 95,7 @@ class Alternate(Parser):
    left = attr.ib()
    right = attr.ib()

    def __call__(self, tokens, pos) -> Union[None, Type[Parser]]:
    def __call__(self, tokens, pos) -> Optional[Type[Parser]]:
        left_result = self.left(tokens, pos)
        if left_result is not None:
            return left_result


@@ 108,7 106,7 @@ class Alternate(Parser):
class Opt(Parser):
    parser = attr.ib()

    def __call__(self, tokens, pos) -> Union[Type[Parser], Result]:
    def __call__(self, tokens, pos) -> Union[Result, Type[Parser]]:
        result = self.parser(tokens, pos)
        if result is not None:
            return result


@@ 136,7 134,7 @@ class Process(Parser):
    parser = attr.ib()
    function = attr.ib()

    def __call__(self, tokens, pos) -> Union[Type[Parser], None]:
    def __call__(self, tokens, pos) -> Optional[Type[Parser]]:
        result = self.parser(tokens, pos)
        if result is not None:
            result.value = self.function(result.value)


@@ 158,7 156,7 @@ class Lazy(Parser):
class Phrase(Parser):
    parser = attr.ib()

    def __call__(self, tokens, pos) -> Union[Type[Parser], None]:
    def __call__(self, tokens, pos) -> Optional[Type[Parser]]:
        result = self.parser(tokens, pos)

        if result is not None:

M imp_parser/parser/statements.py => imp_parser/parser/statements.py +6 -6
@@ 1,10 1,10 @@
from ..ast import AssignStatement, CompoundStatement, IfStatement, WhileStatement
from .arithmetic import aexp, ID, keyword
from .boolean import bexp
from .combinators import Exp, Lazy, Opt
from .combinators import Alternate, Exp, Lazy, Opt, Process


def assign_stmt():
def assign_stmt() -> Process:
    def process(parsed):
        ((name, _), exp) = parsed
        return AssignStatement(name, exp)


@@ 12,12 12,12 @@ def assign_stmt():
    return ID + keyword(":=") + aexp() ^ process


def stmt_list():
def stmt_list() -> Exp:
    separator = keyword(";") ^ (lambda x: lambda l, r: CompoundStatement(l, r))
    return Exp(stmt(), separator)


def if_stmt():
def if_stmt() -> Process:
    def process(parsed):
        # Okay, so at this point I *hate* how this combinator return structure is organized.
        (((((_, condition), _), true_stmt), false_parsed), _) = parsed


@@ 33,7 33,7 @@ def if_stmt():
         + Opt(keyword("else") + Lazy(stmt_list)) \
         + keyword("end") ^ process

def while_stmt():
def while_stmt() -> Process:
    def process(parsed):
        ((((_, condition), _), body), _) = parsed
        return WhileStatement(condition, body)


@@ 43,7 43,7 @@ def while_stmt():
         + keyword("end") ^ process


def stmt():
def stmt() -> Alternate:
    return assign_stmt() \
         | if_stmt() \
         | while_stmt()