Skip to content
This repository was archived by the owner on May 6, 2024. It is now read-only.

Parser idea #30

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,8 @@ pip-wheel-metadata/

# Docs
_build/

# ply
lextab.py
parsetab.py
parser.out
3 changes: 3 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ ignore_missing_imports = True
[mypy-uvloop]
ignore_missing_imports = True

[mypy-ply.*]
ignore_missing_imports = True

# disable mypy completely for now
[mypy-tests.*]
ignore_errors = True
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import setuptools # type: ignore

install_requires = ["aioredis"]
install_requires = ["aioredis", "ply"]

with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read()
Expand Down
163 changes: 163 additions & 0 deletions yarpc/parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
from enum import Enum
from typing import Union

import ply.lex as lex
import ply.yacc as yacc


class SyntaxError(Exception):
def __init__(self, text: str):
self.text = text


class Action(Enum):
CONNECT = "REQUEST"
PING = "PING"
INFO = "INFO"
REQUEST = "REQUEST"
RESPOND = "RESPOND"


class Query:
def __init__(
self,
action: Action,
parameter: str = None,
destination: Union[int, str] = "ALL",
):
self.action = action
self.parameter = parameter
if parameter:
self.clean_parameter = parameter.replace("'", "\\'")
self.destination = destination

@property
def has_parameter(self) -> bool:
return self.parameter is not None

def __str__(self) -> str:
if not self.has_parameter:
return f"{self.action};"
else:
return f"{self.action} '{self.clean_parameter}' TO {self.destination};"


tokens = (
"SEMICOLON",
"ALL",
"TO",
"STRING",
"INTEGER",
"PING",
"INFO",
"REQUEST",
"RESPOND",
"CONNECT",
)

# very basic integers
t_INTEGER = r"\d+"

# our actions
t_PING = r"PING"
t_INFO = r"INFO"
t_REQUEST = r"REQUEST|REQ"
t_RESPOND = r"RESPOND|RESP"
t_CONNECT = r"CONNECT|CONN"

t_SEMICOLON = r";"
t_TO = r"TO|FROM"
t_ALL = r"ALL"
t_ignore_WS = r"\s+"


def t_STRING(t: lex.LexToken) -> lex.LexToken:
r'("(?:\\"|.)*?"|\'(?:\\\'|.)*?\')'

# make multiple quotes possible like this
t.value = t.value[1:-1]
t.value = bytes(t.value, "utf-8").decode("unicode_escape")

return t


def t_error(t: lex.LexToken) -> None:
print("Illegal character '%s'" % t.value[0])
t.lexer.skip(1)


lex.lex(optimize=1)


def p_query(p: yacc.YaccProduction) -> None:
"""
query : action_noargs SEMICOLON
| action_args STRING SEMICOLON
| action_args STRING TO destination SEMICOLON
"""
nargs = len(p) - 2 # minus [0] and ;
if nargs == 1: # action_noargs
p[0] = Query(action=p[1])
elif nargs == 2: # action_args STRING
p[0] = Query(action=p[1], parameter=p[2])
elif nargs == 4: # action_args STRING TO destination
p[0] = Query(action=p[1], parameter=p[2], destination=p[4])


# def p_action(p):
# """
# action : PING
# | INFO
# | REQUEST
# | RESPOND
# | CONNECT
# """
# p[0] = p[1]


def p_action_no_args(p: yacc.YaccProduction) -> None:
"""
action_noargs : PING
| INFO
| CONNECT
"""
p[0] = p[1]


def p_action_args(p: yacc.YaccProduction) -> None:
"""
action_args : REQUEST
| RESPOND
"""
p[0] = p[1]


def p_destination(p: yacc.YaccProduction) -> None:
"""
destination : INTEGER
| ALL
"""
p[0] = p[1]


def p_error(p: yacc.YaccProduction) -> None:
if not p: # missing ;
raise SyntaxError("Missing ; after query")
raise SyntaxError("Syntax error at '%s'" % p.value)


parser = yacc.yacc()

if __name__ == "__main__":
while True:
try:
s = input("yarpc > ")
except (EOFError, KeyboardInterrupt):
break
if not s:
continue
try:
r = parser.parse(s)
except SyntaxError as e:
print(e.text)
print(r)