syntactes 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.1
2
+ Name: syntactes
3
+ Version: 0.1.0
4
+ Summary: Parser generator
5
+ Author-email: Maximos Nikiforakis <nikiforos@live.co.uk>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Maxcode123/syntactes
8
+ Project-URL: Issues, https://github.com/Maxcode123/syntactes/issues
9
+ Keywords: parser,generator
10
+ Classifier: Programming Language :: Python
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Natural Language :: English
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Topic :: Software Development :: Compilers
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
File without changes
@@ -0,0 +1,37 @@
1
+ [tool.setuptools.packages.find]
2
+ where = ["src"]
3
+ include = ["syntactes"]
4
+ exclude = ["syntactes.tests*"]
5
+ namespaces = false
6
+
7
+ [build-system]
8
+ requires = ["setuptools"]
9
+ build-backend = "setuptools.build_meta"
10
+
11
+ [project]
12
+ name = "syntactes"
13
+ version = "0.1.0"
14
+ authors = [
15
+ { name="Maximos Nikiforakis", email="nikiforos@live.co.uk" }
16
+ ]
17
+ description = "Parser generator"
18
+ keywords = ["parser", "generator"]
19
+ readme = "README.md"
20
+ license = {text = "MIT"}
21
+ requires-python = ">=3.10"
22
+ dependencies = []
23
+ classifiers = [
24
+ "Programming Language :: Python",
25
+ "Programming Language :: Python :: 3",
26
+ "Programming Language :: Python :: 3.10",
27
+ "Programming Language :: Python :: 3.11",
28
+ "Programming Language :: Python :: 3.12",
29
+ "Programming Language :: Python :: 3.13",
30
+ "Natural Language :: English",
31
+ "License :: OSI Approved :: MIT License",
32
+ "Topic :: Software Development :: Compilers",
33
+ ]
34
+
35
+ [project.urls]
36
+ Homepage = "https://github.com/Maxcode123/syntactes"
37
+ Issues = "https://github.com/Maxcode123/syntactes/issues"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,4 @@
1
+ from .generator import LR0Generator
2
+ from .grammar import Grammar
3
+ from .rule import Rule
4
+ from .token import Token
@@ -0,0 +1,65 @@
1
+ from typing import Protocol
2
+ from enum import Enum
3
+
4
+
5
+ class Actionable(Protocol):
6
+ """
7
+ State or Rule.
8
+ """
9
+
10
+ number: int
11
+
12
+
13
+ class ActionType(Enum):
14
+ SHIFT = "SHIFT"
15
+ REDUCE = "REDUCE"
16
+ ACCEPT = "ACCEPT"
17
+ REJECT = "REJECT"
18
+
19
+ def abbreviated(self) -> str:
20
+ """
21
+ Returns an abbreviated version of the action type.
22
+ """
23
+ if self == self.SHIFT:
24
+ return "s"
25
+ if self == self.REDUCE:
26
+ return "r"
27
+ if self == self.ACCEPT:
28
+ return "a"
29
+ if self == self.REJECT:
30
+ return "e"
31
+
32
+
33
+ class Action:
34
+ """
35
+ Parsing action. Contains data relevant to the actionable object (State or Rule)
36
+ and the relevant action to be applied.
37
+
38
+ The `actionable_number` refers to the state number for `State` actionables and
39
+ to the rule number for `Rule` actionables.
40
+ """
41
+
42
+ def __init__(self, actionable: Actionable, action_type: ActionType) -> None:
43
+ self.actionable = actionable
44
+ self.action_type = action_type
45
+
46
+ def __repr__(self) -> str:
47
+ return f"<Action: {self}>"
48
+
49
+ def __str__(self) -> str:
50
+ if self.action_type in {ActionType.ACCEPT, ActionType.REJECT}:
51
+ return str(self.action_type.abbreviated())
52
+
53
+ return f"{self.action_type.abbreviated()}{self.actionable.number}"
54
+
55
+ def __hash__(self) -> int:
56
+ return hash((self.actionable, self.action_type))
57
+
58
+ def __eq__(self, other) -> bool:
59
+ if not isinstance(other, Action):
60
+ return False
61
+
62
+ return (
63
+ self.actionable == other.actionable
64
+ and self.action_type == other.action_type
65
+ )
@@ -0,0 +1,61 @@
1
+ from typing import Optional
2
+
3
+ from syntactes.rule import Rule
4
+ from syntactes.token import Token
5
+
6
+
7
+ class LR0Item:
8
+ """
9
+ Item of LR0 parser. Contains rule and current position in rule.
10
+ Current position is denoted with the dot '.'.
11
+ """
12
+
13
+ def __init__(self, rule: Rule, position: int) -> None:
14
+ self.rule = rule
15
+ self.position = position
16
+
17
+ def dot_is_last(self) -> bool:
18
+ """
19
+ Returns True if the dot in the item is in the last position of the rhs,
20
+ False otherwise.
21
+ """
22
+ return self.position == self.rule.rhs_len
23
+
24
+ @property
25
+ def after_dot(self) -> Optional[Token]:
26
+ """
27
+ Returns the symbol after the dot in the current item.
28
+ If the dot is in the last position, returns None
29
+ """
30
+ if self.dot_is_last():
31
+ return None
32
+
33
+ return self.rule.rhs[self.position]
34
+
35
+ @property
36
+ def before_dot(self) -> Optional[Token]:
37
+ """
38
+ Returns the symbol before the dot in the current item.
39
+ If the dot is in the first position, returns None.
40
+ """
41
+ if self.position == 0:
42
+ return None
43
+
44
+ return self.rule.rhs[self.position - 1]
45
+
46
+ def __repr__(self) -> str:
47
+ return f"<LR0Item: {self}>"
48
+
49
+ def __str__(self) -> str:
50
+ rhs = [s for s in self.rule.rhs]
51
+ rhs.insert(self.position, ".")
52
+ return f"{self.rule.lhs} -> " + " ".join(map(str, rhs))
53
+
54
+ def __hash__(self) -> int:
55
+ return hash((self.rule, self.position))
56
+
57
+ def __eq__(self, other) -> bool:
58
+ if not isinstance(other, LR0Item):
59
+ return False
60
+
61
+ return self.rule == other.rule and self.position == other.position
@@ -0,0 +1,60 @@
1
+ from typing import Iterable, Protocol
2
+
3
+ from syntactes._item import LR0Item
4
+
5
+
6
+ class State(Protocol):
7
+ """
8
+ State of parser automaton. A state is a set of items.
9
+ """
10
+
11
+ items: set
12
+
13
+ def __repr__(self) -> str: ...
14
+ def __str__(self) -> str: ...
15
+ def __hash__(self) -> int: ...
16
+ def __eq__(self) -> bool: ...
17
+
18
+
19
+ class LR0State:
20
+ """
21
+ State of LR0 parser. A LR0 state is a set of LR0 items.
22
+ """
23
+
24
+ def __init__(self) -> None:
25
+ self.number = None
26
+ self.items = set()
27
+
28
+ @staticmethod
29
+ def from_items(items: Iterable[LR0Item]) -> "LR0State":
30
+ """
31
+ Create an LR0 state from a set of LR0 items.
32
+ """
33
+ state = LR0State()
34
+ {state.add_item(item) for item in items}
35
+
36
+ return state
37
+
38
+ def add_item(self, item: LR0Item) -> None:
39
+ """
40
+ Adds an item to the state.
41
+ """
42
+ self.items.add(item)
43
+
44
+ def set_number(self, number: int) -> None:
45
+ self.number = number
46
+
47
+ def __repr__(self) -> str:
48
+ return f"<LR0State: {self.number}>"
49
+
50
+ def __str__(self) -> str:
51
+ return f"{self.number}:" + "(" + ", ".join(map(str, self.items)) + ")"
52
+
53
+ def __hash__(self) -> int:
54
+ return hash(frozenset(self.items))
55
+
56
+ def __eq__(self, other) -> bool:
57
+ if not isinstance(other, LR0State):
58
+ return False
59
+
60
+ return self.items == other.items
@@ -0,0 +1,255 @@
1
+ from syntactes._action import Action, ActionType
2
+ from syntactes._item import LR0Item
3
+ from syntactes._state import LR0State
4
+ from syntactes.grammar import Grammar
5
+ from syntactes.table import Entry, ParsingTable
6
+ from syntactes.token import Token
7
+
8
+
9
+ class LR0Generator:
10
+ """
11
+ Generator of LR0 parsing tables.
12
+ """
13
+
14
+ def __init__(self, grammar: Grammar) -> None:
15
+ self.grammar = grammar
16
+
17
+ def generate(self) -> ParsingTable:
18
+ """
19
+ Generates an LR0 parsing table for the configured grammar.
20
+ """
21
+ states, shift_entries = self._create_states_and_shift_entries()
22
+ reduce_entries = self._create_reduce_entries(states)
23
+
24
+ entries = shift_entries | reduce_entries
25
+
26
+ table = ParsingTable.from_entries(entries, self.grammar)
27
+
28
+ return table
29
+
30
+ def closure(self, items: set[LR0Item]) -> set[LR0Item]:
31
+ """
32
+ Computes and returns the closure for the given set of items.
33
+
34
+ The closure operation adds more items to a set of items when there
35
+ is a dot to the left of a non-terminal symbol.
36
+
37
+ e.g.
38
+ for any item S -> . E in the given items, closure adds E -> . T
39
+ and T -> . x, where E -> T and T -> x are production rules.
40
+ """
41
+ _set = {item for item in items}
42
+
43
+ for item in items:
44
+ if item.dot_is_last():
45
+ continue
46
+
47
+ new_items = self._get_related_items(item.after_dot)
48
+ _set |= new_items
49
+
50
+ return _set
51
+
52
+ def goto(self, items: set[LR0Item], token: Token) -> set[LR0Item]:
53
+ """
54
+ Computes and returns the GOTO set for the given set of items.
55
+
56
+ The goto operation creates a set where all items have the dot past the
57
+ given symbol.
58
+ """
59
+ _set: set[LR0Item] = set()
60
+
61
+ for item in items:
62
+ if item.dot_is_last() or item.after_dot != token:
63
+ continue
64
+
65
+ next_item = LR0Item(item.rule, item.position + 1)
66
+ _set.add(next_item)
67
+
68
+ return self.closure(_set)
69
+
70
+ def get_states(self) -> set[LR0State]:
71
+ """
72
+ Returns the set of automaton states for the configured grammar.
73
+ """
74
+ states, _ = self._create_states_and_shift_entries()
75
+ return states
76
+
77
+ def _first(self, symbol: Token) -> set[Token]:
78
+ """
79
+ Computes and returns the FIRST set for the given symbol.
80
+
81
+ The FIRST set of a symbol 'G' is the set of terminal symbols that are
82
+ first in the right-hand side of a rule where 'G' is the left-hand side.
83
+
84
+ e.g. 't', 'k' and 'a' would be the FIRST set of G for the below rules:
85
+ 1. G -> t
86
+ 2. G -> kM
87
+ 3. G -> T
88
+ 4. T -> a
89
+ where M is either terminal or non-terminal and T is non-terminal.
90
+ 'a' would be included in the FIRST set because if rule 4 is substituted in
91
+ rule 3, 'a' (which is a terminal) could be derived from 'G'.
92
+ """
93
+ if symbol.is_terminal:
94
+ return {symbol}
95
+
96
+ _set: set[Token] = set()
97
+
98
+ for rule in self.grammar.rules:
99
+ if rule.lhs != symbol:
100
+ continue
101
+
102
+ if rule.rhs[0].is_terminal:
103
+ _set.add(rule.rhs[0])
104
+ elif rule.rhs_len == 1:
105
+ _set |= self._first(rule.rhs[0])
106
+
107
+ return _set
108
+
109
+ def _follow(self, symbol: Token) -> set[Token]:
110
+ """
111
+ Computes and returns the FOLLOW set for the given symbol.
112
+
113
+ The FOLLOW set of a symbol 'G' is the set of terminals that can immediately
114
+ follow 'G' in a rule.
115
+ """
116
+ if symbol.is_terminal:
117
+ return set()
118
+
119
+ _set: set[Token] = set()
120
+
121
+ for rule in self.grammar.rules:
122
+ for i, s in enumerate(rule.rhs):
123
+ if s != symbol:
124
+ continue
125
+
126
+ if i == rule.rhs_len - 1:
127
+ if rule.lhs == symbol:
128
+ continue
129
+
130
+ _set |= self._follow(rule.lhs)
131
+ else:
132
+ _set |= self._first(rule.rhs[i + 1])
133
+
134
+ return _set
135
+
136
+ def _get_related_items(self, symbol: Token) -> set[LR0Item]:
137
+ """
138
+ e.g. the items X -> .g, Y -> .p would be returned for the below grammar rules:
139
+ 1. X -> g
140
+ 2. X -> Y
141
+ 3. Y -> p
142
+ where 'g' and 'p' are terminals.
143
+ """
144
+ _set: set[LR0Item] = set()
145
+
146
+ for rule in self.grammar.rules:
147
+ if rule.lhs == symbol:
148
+ _set.add(LR0Item(rule, 0))
149
+
150
+ if rule.rhs_len == 1 and not rule.rhs[0].is_terminal:
151
+ _set |= self._get_related_items(rule.rhs[0])
152
+
153
+ return _set
154
+
155
+ def _create_states_and_shift_entries(self) -> tuple[set[LR0State], set[Entry]]:
156
+ """
157
+ Computes and returns the states and entries for shift actions.
158
+ """
159
+ states, entries = dict(), set()
160
+
161
+ initial_items = self.closure({LR0Item(self.grammar.starting_rule, 0)})
162
+ initial_state = LR0State.from_items(initial_items)
163
+ initial_state.set_number(1)
164
+ states[initial_state] = 1
165
+
166
+ _states, _entries = dict(), set()
167
+ while (_states, _entries) != (states, entries):
168
+ _states = {s: n for s, n in states.items()}
169
+ _entries = {e for e in entries}
170
+ states, entries = self._extend_states_and_shift_entries(_states, _entries)
171
+
172
+ return set(states.keys()), entries
173
+
174
+ def _extend_states_and_shift_entries(
175
+ self, states: dict[LR0State, int], entries: set[Entry]
176
+ ) -> tuple[dict[LR0State, int], set[Entry]]:
177
+ """
178
+ Extends states and entries following the below algorithm:
179
+
180
+ ```
181
+ for each state S in states
182
+ for each item A -> a.Xb in S
183
+ J = goto(S, X)
184
+ states.add(J)
185
+ entries.add((S->J, X))
186
+ ```
187
+ """
188
+ _states = {s: n for s, n in states.items()}
189
+ _entries = {e for e in entries}
190
+
191
+ EOF = Token.eof()
192
+ for state in states:
193
+ for item in state.items:
194
+ if item.dot_is_last() or item.after_dot == EOF:
195
+ continue
196
+
197
+ new_items = self.goto(state.items, item.after_dot)
198
+
199
+ if len(new_items) == 0:
200
+ continue
201
+
202
+ new = LR0State.from_items(new_items)
203
+
204
+ number = _states.setdefault(new, len(_states) + 1)
205
+ new.set_number(number)
206
+
207
+ action = Action(new, ActionType.SHIFT)
208
+ _entries.add(Entry(state, item.after_dot, action))
209
+
210
+ return _states, _entries
211
+
212
+ def _create_reduce_entries(self, states: set[LR0State]) -> set[Entry]:
213
+ """
214
+ Computes and returns the entries for reduce actions and the accept action.
215
+ """
216
+ entries: set[Entry] = set()
217
+
218
+ for state in states:
219
+ for item in state.items:
220
+ if item.after_dot == Token.eof():
221
+ action = Action(item.rule, ActionType.ACCEPT)
222
+ entries.add(Entry(state, Token.eof(), action))
223
+
224
+ if not item.dot_is_last():
225
+ continue
226
+
227
+ action = Action(item.rule, ActionType.REDUCE)
228
+ for token in self.grammar.tokens:
229
+ if token.is_terminal:
230
+ entries.add(Entry(state, token, action))
231
+
232
+ return entries
233
+
234
+
235
+ class SLRGenerator(LR0Generator):
236
+ def _create_reduce_entries(self, states: set[LR0State]) -> set[Entry]:
237
+ """
238
+ Computes and returns the entries for reduce actions and the accept action.
239
+ """
240
+ entries: set[Entry] = set()
241
+
242
+ for state in states:
243
+ for item in state.items:
244
+ if item.after_dot == Token.eof():
245
+ action = Action(item.rule, ActionType.ACCEPT)
246
+ entries.add(Entry(state, Token.eof(), action))
247
+
248
+ if not item.dot_is_last():
249
+ continue
250
+
251
+ action = Action(item.rule, ActionType.REDUCE)
252
+ for token in self._follow(item.rule.lhs):
253
+ entries.add(Entry(state, token, action))
254
+
255
+ return entries
@@ -0,0 +1,22 @@
1
+ from typing import Iterable
2
+
3
+ from syntactes.rule import Rule
4
+ from syntactes.token import Token
5
+
6
+
7
+ class Grammar:
8
+ """
9
+ A grammar is a set of rules that describe a language.
10
+
11
+ The only valid 'words' of the language are the given tokens.
12
+ """
13
+
14
+ def __init__(
15
+ self, starting_rule: Rule, rules: Iterable[Rule], tokens: set[Token]
16
+ ) -> None:
17
+ """
18
+ `starting_rule` should also be included in `rules`.
19
+ """
20
+ self.starting_rule = starting_rule
21
+ self.rules = rules
22
+ self.tokens = tokens
@@ -0,0 +1,31 @@
1
+ from syntactes.token import Token
2
+
3
+
4
+ class Rule:
5
+ """
6
+ Production rule. Describes the break-down of a non-terminal symbol to
7
+ other symbols.
8
+
9
+ LHS -> RHS1 RHS2...
10
+ """
11
+
12
+ def __init__(self, number: int, lhs: Token, *args: tuple[Token]) -> None:
13
+ self.number = number
14
+ self.lhs = lhs
15
+ self.rhs = args
16
+ self.rhs_len = len(args)
17
+
18
+ def __repr__(self) -> str:
19
+ return f"<Rule: {self}>"
20
+
21
+ def __str__(self) -> str:
22
+ return f"{self.lhs} -> " + " ".join(map(str, self.rhs))
23
+
24
+ def __hash__(self) -> int:
25
+ return hash((self.lhs, self.rhs))
26
+
27
+ def __eq__(self, other) -> bool:
28
+ if not isinstance(other, Rule):
29
+ return False
30
+
31
+ return self.lhs == other.lhs and self.rhs == other.rhs
@@ -0,0 +1,130 @@
1
+ from typing import Iterable, Optional, TypeAlias
2
+
3
+ from syntactes._action import Action
4
+ from syntactes._state import State
5
+ from syntactes.grammar import Grammar
6
+ from syntactes.token import Token
7
+
8
+ Row: TypeAlias = dict[Token, list[Action]]
9
+
10
+
11
+ class Entry:
12
+ """
13
+ An entry of the parsing table. Holds the information of a transition from
14
+ a state to another state via a symbol.
15
+ """
16
+
17
+ def __init__(self, from_state: State, token: Token, action: Action) -> None:
18
+ self.from_state = from_state
19
+ self.token = token
20
+ self.action = action
21
+
22
+ def __repr__(self) -> str:
23
+ return f"<Entry: {str(self)}>"
24
+
25
+ def __str__(self) -> str:
26
+ return f"{self.from_state.number}, {self.action}, {self.token}"
27
+
28
+ def __hash__(self) -> int:
29
+ return hash((self.from_state, self.token, self.action))
30
+
31
+ def __eq__(self, other) -> bool:
32
+ if not isinstance(other, Entry):
33
+ return False
34
+
35
+ return (
36
+ self.from_state == other.from_state
37
+ and self.token == other.token
38
+ and self.action == other.action
39
+ )
40
+
41
+
42
+ class ParsingTable:
43
+ """
44
+ Table that contains all the transitions from state to state with a symbol.
45
+ """
46
+
47
+ def __init__(self, grammar: Grammar) -> None:
48
+ self.rows: dict[State, Row] = dict()
49
+ self._grammar = grammar
50
+
51
+ def get_actions(self, state: State, token: Token) -> Optional[list[Action]]:
52
+ """
53
+ Get the actions from state with given number with `token`.
54
+ If there are no actions, returns None.
55
+ """
56
+ return self.rows.get(state, {}).get(token, None)
57
+
58
+ def get(self, state: State) -> Optional[Row]:
59
+ """
60
+ Get the mapping of tokens to actions for the given state number.
61
+ Returns None if the state is not found.
62
+ """
63
+ return self.rows.get(state, None)
64
+
65
+ def add_entry(self, entry: Entry) -> None:
66
+ """
67
+ Adds an entry to the parsing table.
68
+ """
69
+ self.rows.setdefault(entry.from_state, {}).setdefault(
70
+ entry.token, list()
71
+ ).append(entry.action)
72
+
73
+ @staticmethod
74
+ def from_entries(
75
+ entries: Iterable[Entry], tokens: Iterable[Token]
76
+ ) -> "ParsingTable":
77
+ """
78
+ Create a parsing table from the given entries.
79
+ """
80
+ table = ParsingTable(tokens)
81
+ {table.add_entry(entry) for entry in entries}
82
+ return table
83
+
84
+ def pretty_str(self) -> str:
85
+ """
86
+ Returns a pretty-formatted string representation of the table.
87
+ """
88
+ return self._rules_pretty_str() + "\n\n" + self._table_pretty_str()
89
+
90
+ def _rules_pretty_str(self) -> str:
91
+ rules = [str(i) + ". " + str(r) for i, r in enumerate(self._grammar.rules)]
92
+ rules_str = "\n".join(rules)
93
+ rules_str = "GRAMMAR RULES\n" + "-" * max(map(len, rules)) + "\n" + rules_str
94
+ rules_str += "\n" + "-" * max(map(len, rules))
95
+
96
+ return rules_str
97
+
98
+ def _table_pretty_str(self) -> str:
99
+ rows = []
100
+ tokens = sorted(self._grammar.tokens)
101
+ for number, row in sorted(
102
+ map(lambda tpl: (tpl[0].number, tpl[1]), self.rows.items())
103
+ ):
104
+ r = [str(number)]
105
+ for token in sorted(tokens):
106
+ actions = row.get(token, [])
107
+ if len(actions) >= 1:
108
+ actions_str = ",".join(map(str, actions))
109
+ else:
110
+ actions_str = "--"
111
+
112
+ r.append(actions_str)
113
+
114
+ rows.append(r)
115
+
116
+ table = "| | "
117
+ table += " | ".join(str(token) for token in tokens) + " |\n"
118
+
119
+ header = self._header_str() + "\n" + "-" * len(table) + "\n"
120
+ table += "-" * len(table) + "\n"
121
+
122
+ for row in rows:
123
+ new_row = "| " + " | ".join(row) + " |" + "\n"
124
+ table += new_row
125
+ table += "-" * len(new_row) + "\n"
126
+
127
+ return header + table
128
+
129
+ def _header_str(self) -> str:
130
+ return "LR0 PARSING TABLE"
@@ -0,0 +1,45 @@
1
+ class Token:
2
+ """
3
+ A token of the grammar. Can be a terminal or non-terminal symbol.
4
+ """
5
+
6
+ def __init__(self, symbol: str, is_terminal: bool) -> None:
7
+ self.symbol = symbol
8
+ self.is_terminal = is_terminal
9
+
10
+ @staticmethod
11
+ def null() -> "Token":
12
+ """
13
+ Returns the NULL token.
14
+ """
15
+ return Token("ε", True)
16
+
17
+ @staticmethod
18
+ def eof() -> "Token":
19
+ """
20
+ Returns the EOF token.
21
+ """
22
+ return Token("$", True)
23
+
24
+ def __repr__(self) -> str:
25
+ return f"<Token: {self}>"
26
+
27
+ def __str__(self) -> str:
28
+ return self.symbol
29
+
30
+ def __hash__(self) -> int:
31
+ return hash(str(self))
32
+
33
+ def __eq__(self, other) -> bool:
34
+ if not isinstance(other, Token):
35
+ return False
36
+
37
+ return self.symbol == other.symbol and self.is_terminal is other.is_terminal
38
+
39
+ def __lt__(self, other) -> bool:
40
+ if not isinstance(other, Token):
41
+ raise ValueError(
42
+ f"'<' not supported between instances of 'Token' and {type(other).__name__}"
43
+ )
44
+
45
+ return self.symbol < other.symbol
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.1
2
+ Name: syntactes
3
+ Version: 0.1.0
4
+ Summary: Parser generator
5
+ Author-email: Maximos Nikiforakis <nikiforos@live.co.uk>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Maxcode123/syntactes
8
+ Project-URL: Issues, https://github.com/Maxcode123/syntactes/issues
9
+ Keywords: parser,generator
10
+ Classifier: Programming Language :: Python
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Natural Language :: English
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Topic :: Software Development :: Compilers
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
@@ -0,0 +1,15 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/syntactes/__init__.py
4
+ src/syntactes/_action.py
5
+ src/syntactes/_item.py
6
+ src/syntactes/_state.py
7
+ src/syntactes/generator.py
8
+ src/syntactes/grammar.py
9
+ src/syntactes/rule.py
10
+ src/syntactes/table.py
11
+ src/syntactes/token.py
12
+ src/syntactes.egg-info/PKG-INFO
13
+ src/syntactes.egg-info/SOURCES.txt
14
+ src/syntactes.egg-info/dependency_links.txt
15
+ src/syntactes.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ syntactes