Skip to content

Commit

Permalink
Handle empty rule by popping nothing (#16)
Browse files Browse the repository at this point in the history
- Handle empty grammar rule error (closes #15)
- Add test with empty grammar
  • Loading branch information
henrylee97 authored Jul 11, 2024
1 parent 7117446 commit c4cc7d3
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 10 deletions.
21 changes: 14 additions & 7 deletions plare/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,9 @@ def calc_first(self, rules: dict[str, Rule[T]]) -> set[type[Token]]:
recursive_rights = list[list[Symbol]]()
self.first = set()
for right, _ in self.rights:
if len(right) == 0:
self.first.add(EPSILON)
continue
for i, token in enumerate(right):
if isinstance(token, type):
if token == EPSILON:
Expand Down Expand Up @@ -557,15 +560,19 @@ def parse(self, var: str, lexbuf: Iterable[Token]) -> T | Token:
token = None

case Reduce(left, n, maker):
# Pop stack
stack = stack[:-n]
if n > 0:
# Pop stack
stack = stack[:-n]

# Pop symbols
poped_symbols = symbols[-n:]
symbols = symbols[:-n]
# Pop symbols
poped_symbols = symbols[-n:]
symbols = symbols[:-n]

# Make new symbol
symbols.append(maker(*poped_symbols))
# Make new symbol
symbols.append(maker(*poped_symbols))

else:
symbols.append(maker())

# Prepare next
state = stack[-1]
Expand Down
67 changes: 64 additions & 3 deletions tests/test_parser.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from __future__ import annotations

from typing import Any

from plare.lexer import Token
from plare.parser import EOF, Parser
from plare.token import Token


class PLUS(Token):
Expand Down Expand Up @@ -35,8 +37,8 @@ def __init__(self, value: NUM, /) -> None:
self.value = value.value


def make_positive_integer_parser():
return Parser(
def make_positive_integer_parser() -> Parser[Tree]:
return Parser[Tree](
{
"pgm": [
(["exp"], None, [0]),
Expand All @@ -59,3 +61,62 @@ def test_parse_positive_integer_without_add():
)
assert isinstance(tree, Num)
assert tree.value == 1


def test_minimal_empty_rule_parser():
parser = Parser({"pgm": [([], list[int], [])]})
parsed = parser.parse("pgm", [EOF("", lineno=1, offset=0)])
assert isinstance(parsed, list)
assert len(parsed) == 0


class LBRACKET(Token):
pass


class RBRACKET(Token):
pass


class COMMA(Token):
pass


class IntList:
items: list[int]

def __init__(self, item: int, tail: IntList) -> None:
self.items = [item, *tail.items]


class EmptyIntList(IntList):
def __init__(self) -> None:
self.items = []


def make_list_parser() -> Parser[IntList]:
return Parser[IntList](
{
"list": [
([LBRACKET, "items", RBRACKET], None, [1]),
],
"items": [
([NUM, COMMA, "items"], IntList, [0, 1]),
([], EmptyIntList, []),
],
}
)


def test_parse_empty_intlist():
parser = make_list_parser()
tree = parser.parse(
"list",
[
LBRACKET("[", lineno=1, offset=0),
RBRACKET("]", lineno=1, offset=1),
EOF("", lineno=1, offset=2),
],
)
assert isinstance(tree, IntList)
assert len(tree.items) == 0

0 comments on commit c4cc7d3

Please sign in to comment.