Skip to content

Commit

Permalink
Use typing
Browse files Browse the repository at this point in the history
  • Loading branch information
joente committed Jul 23, 2022
1 parent bb61afb commit b7307c9
Show file tree
Hide file tree
Showing 23 changed files with 146 additions and 110 deletions.
2 changes: 1 addition & 1 deletion examples/json_grammar.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
'''JSON Grammar.'''
"""JSON Grammar."""
from pyleri import (
Ref,
Choice,
Expand Down
4 changes: 2 additions & 2 deletions pyleri/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
'''Py-LeRi (pyleri) Python LR-parsing module.
"""Py-LeRi (pyleri) Python LR-parsing module.
This module is inspired by lrparsing (http://lrparsing.sourceforge.net/),
a Python parser written by Russell Stuart, 2014-05-29.
Expand All @@ -10,7 +10,7 @@
:copyright: 2021, Jeroen van der Heijden <[email protected]>
:license: MIT
'''
"""

from .choice import Choice
from .endofstatement import end_of_statement
Expand Down
14 changes: 8 additions & 6 deletions pyleri/choice.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
'''pyleri.Choice Class.
"""pyleri.Choice Class.
Choose one of the given elements. When most_greedy is True we will choose
the 'longest' element when multiple elements are valid. If most_greedy is
False we will return the first match.
:copyright: 2021, Jeroen van der Heijden <[email protected]>
'''

from .elements import NamedElement, c_export, go_export, java_export
"""
from .elements import Element, NamedElement, c_export, go_export, java_export


class Choice(NamedElement):

__slots__ = ('_elements', '_get_node_result')

def __init__(self, *elements, most_greedy=True):
def __init__(
self,
*elements: Element,
most_greedy: bool = True):
self._elements = self._validate_elements(elements)
self._get_node_result = \
self._most_greedy_result if most_greedy else \
self._stop_at_first_match

@property
def most_greedy(self):
def most_greedy(self) -> bool:
return self._get_node_result == self._most_greedy_result

def _most_greedy_result(self, root, tree, rule, s, node):
Expand Down
26 changes: 17 additions & 9 deletions pyleri/elements.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
'''Element and NamedElement Class.
"""Element and NamedElement Class.
These are the base classes used for all other elements.
:copyright: 2021, Jeroen van der Heijden <[email protected]>
'''
"""
import typing as t


def camel_case(s):
def camel_case(s: str) -> str:
return ''.join(
p[0].upper() + p[1:] if n else p
for n, p in enumerate(s.split('_')))


def cap_case(s):
def cap_case(s: str) -> str:
return ''.join(p[0].upper() + p[1:] for p in s.split('_') if p)


Expand Down Expand Up @@ -63,26 +64,33 @@ class Element:

__slots__ = tuple()

name: t.Optional[str]

@staticmethod
def _validate_element(element):
def _validate_element(element: 'Element') -> 'Element':
if isinstance(element, str):
return Token(element)
if isinstance(element, Element):
return element
raise TypeError(
'Expecting an element or string but received type: {}'.format(
type(element)))
'Expecting an element or string '
'but received type: {}'.format(type(element)))

@classmethod
def _validate_elements(cls, elements):
def _validate_elements(cls, elements) -> t.List['Element']:
return [cls._validate_element(elem) for elem in elements]


class NamedElement(Element):

__slots__ = ('name',)

def _export_js(self, js_indent, indent, classes, cname):
def _export_js(
self,
js_indent: int,
indent: int,
classes,
cname) -> str:
classes.add(self.__class__.__name__.lstrip('_'))
if hasattr(self, 'name') and indent > 0:
return '{}.{}'.format(cname, self.name) if cname else self.name
Expand Down
4 changes: 2 additions & 2 deletions pyleri/endofstatement.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
'''end_of_statement Variable.
"""end_of_statement Variable.
end_of_statement is an instance of _EndOfStatement and will be added to
an 'expecting' in a node result when an 'End of Statement' is possible.
:copyright: 2021, Jeroen van der Heijden <[email protected]>
'''
"""


class _EndOfStatement:
Expand Down
4 changes: 2 additions & 2 deletions pyleri/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'''Exceptions.
"""Exceptions.
:copyright: 2021, Jeroen van der Heijden <[email protected]>
'''
"""


class CompileError(Exception):
Expand Down
21 changes: 12 additions & 9 deletions pyleri/expecting.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
'''Expecting Class.
"""Expecting Class.
Expecting Class is used to return possible elements at a position when a given
statement is not correct. This helps building auto-completion, suggestions or
returning nice messages to the user.
:copyright: 2021, Jeroen van der Heijden <[email protected]>
'''
"""
import typing as t
if t.TYPE_CHECKING:
from .elements import Element


class Expecting:

__slots__ = ('required', 'optional', 'pos', '_modes')

def __init__(self):
self.required = set()
self.optional = set()
self.pos = 0
self._modes = {self.pos: self.required}
self.required: t.Set['Element'] = set()
self.optional: t.Set['Element'] = set()
self.pos: int = 0
self._modes: t.Dict[int, t.Set['Element']] = {self.pos: self.required}

def set_mode_required(self, pos, is_required):
def set_mode_required(self, pos: int, is_required: bool):
# do nothing when mode is already set to optional
if pos in self._modes and self._modes[pos] is self.optional:
return
Expand All @@ -29,13 +32,13 @@ def empty(self):
self.required.clear()
self.optional.clear()

def update(self, element, pos):
def update(self, element: 'Element', pos: int):
if pos > self.pos:
self.empty()
self.pos = pos

if pos == self.pos:
self._modes[pos].add(element)

def get_expecting(self):
def get_expecting(self) -> t.Set['Element']:
return self.required | self.optional
37 changes: 18 additions & 19 deletions pyleri/grammar.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
'''pyleri.Grammar Class.
"""pyleri.Grammar Class.
When creating a new grammar this class should be used as the base class.
Expand All @@ -9,7 +9,7 @@ class MyGrammar(Grammar):
:copyright: 2021, Jeroen van der Heijden <[email protected]>
'''
"""


import re
Expand Down Expand Up @@ -328,11 +328,11 @@ class {name}(Grammar):
'''.lstrip()

def __init__(self):
'''Initialize the grammar.
"""Initialize the grammar.
Note: usually you should only initialize a Grammar instance
once in a project.
'''
"""
self._element = self.START
self._string = None
self._expecting = None
Expand All @@ -343,13 +343,13 @@ def export_js(
js_module_name=JS_MODULE_NAME,
js_template=JS_ES6_IMPORT_EXPORT_TEMPLATE,
js_indent=JS_INDENTATION):
'''Export the grammar to a JavaScript file which can be
"""Export the grammar to a JavaScript file which can be
used with the js-lrparsing module.
Two templates are available:
Grammar.JS_WINDOW_TEMPLATE
Grammar.JS_ES6_IMPORT_EXPORT_TEMPLATE (default)
'''
"""

language = []
refs = []
Expand Down Expand Up @@ -406,10 +406,10 @@ def export_py(
py_module_name=PY_MODULE_NAME,
py_template=PY_TEMPLATE,
py_indent=PY_INDENTATION):
'''Export the grammar to a python file which can be
"""Export the grammar to a python file which can be
used with the pyleri module. This can be useful when python code
if used to auto-create a grammar and an export of the final result is
required.'''
required."""

language = []
classes = {'Grammar'}
Expand Down Expand Up @@ -450,8 +450,8 @@ def export_py(
for n in classes if n != 'Rule'])))

def export_c(self, target=C_TARGET, c_indent=C_INDENTATION, headerf=None):
'''Export the grammar to a c (source and header) file which can be
used with the libcleri module.'''
"""Export the grammar to a c (source and header) file which can be
used with the libcleri module."""
language = []
indent = 0
enums = set()
Expand Down Expand Up @@ -518,8 +518,8 @@ def export_go(
go_template=GO_TEMPLATE,
go_indent=GO_INDENTATION,
go_package=GO_PACKAGE):
'''Export the grammar to a Go file which can be
used with the goleri module.'''
"""Export the grammar to a Go file which can be
used with the goleri module."""

language = []
enums = set()
Expand Down Expand Up @@ -569,8 +569,8 @@ def export_java(
java_indent=JAVA_INDENTATION,
java_package=JAVA_PACKAGE,
is_public=True):
'''Export the grammar to a Java file which can be
used with the jleri module.'''
"""Export the grammar to a Java file which can be
used with the jleri module."""

language = []
enums = set()
Expand Down Expand Up @@ -630,7 +630,7 @@ def export_java(
public='public ' if is_public else '')

def parse(self, string):
'''Parse some string to the Grammar.
"""Parse some string to the Grammar.
Returns a nodeResult with the following attributes:
- is_valid: True when the string is successfully parsed
Expand All @@ -641,7 +641,7 @@ def parse(self, string):
'pos' in the string.
- tree: the parse_tree containing a structured
result for the given string.
'''
"""
self._string = string
self._expecting = Expecting()
self._cached_kw_match.clear()
Expand All @@ -653,7 +653,8 @@ def parse(self, string):
0,
tree.children,
self._element,
True))
True),
tree)

# get rest if anything
rest = self._string[node_res.pos:].lstrip()
Expand All @@ -673,8 +674,6 @@ def parse(self, string):
if not node_res.is_valid:
node_res.pos = self._expecting.pos

node_res.tree = tree

return node_res

def _append_tree(self, tree, node, pos):
Expand Down
8 changes: 4 additions & 4 deletions pyleri/keyword.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
'''pyleri.Keyword Class.
"""pyleri.Keyword Class.
Try matching a given keyword string. The keyword should match
Grammer.RE_KEYWORDS otherwise the keyword will not be found. It's possible
however to overwrite the default RE_KEYWORDS in you own Grammar class.
:copyright: 2021, Jeroen van der Heijden <[email protected]>
'''
"""
from .elements import NamedElement, c_export, go_export, java_export


class Keyword(NamedElement):

__slots__ = ('_keyword', '_ign_case')

def __init__(self, keyword, ign_case=False):
def __init__(self, keyword: str, ign_case: bool = False):

if not isinstance(keyword, str):
raise TypeError(
Expand All @@ -23,7 +23,7 @@ def __init__(self, keyword, ign_case=False):
self._ign_case = bool(ign_case)

@property
def ign_case(self):
def ign_case(self) -> bool:
return self._ign_case

def __repr__(self):
Expand Down
Loading

0 comments on commit b7307c9

Please sign in to comment.