storm-cli 1.0.0__py3-none-any.whl

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.
src/keyword.py ADDED
@@ -0,0 +1,18 @@
1
+ from enum import Enum
2
+
3
+
4
+ class Keyword(Enum):
5
+ ENUM = 'enum'
6
+ TABLE = 'table'
7
+ UUID = 'uuid'
8
+ INT = 'int'
9
+ LONG = 'long'
10
+ FLOAT = 'float'
11
+ DOUBLE = 'double'
12
+ DATETIME = 'datetime'
13
+ STRING = 'string'
14
+ BOOL = 'bool'
15
+ PK = 'pk'
16
+ UNIQUE = 'unique'
17
+ TRUE = 'true'
18
+ FALSE = 'false'
src/parser.py ADDED
@@ -0,0 +1,397 @@
1
+ from src.ast import AstType, ASTNode
2
+ from src.tokenizer import Tokenizer
3
+ from src.tok_type import TokenType
4
+ from src.keyword import Keyword
5
+ from src.error_handler import raise_error
6
+
7
+
8
+ class Parser(Tokenizer):
9
+ def __init__(self, fpath):
10
+ with open(fpath) as f:
11
+ fdata = f.read()
12
+ super().__init__(fpath, fdata)
13
+ self.current_token = None
14
+
15
+ def next_token(self):
16
+ self.current_token = self.nextToken()
17
+
18
+ def __check_type(self, expected_type):
19
+ if self.current_token is None:
20
+ raise_error(self.file_path, self.file_data, f"Expected token of type {expected_type.value}, but got None", self._pos())
21
+ return self.current_token.tok_type == expected_type
22
+
23
+ def __check_value(self, expected_value):
24
+ if self.current_token is None:
25
+ raise_error(self.file_path, self.file_data, f"Expected token with value '{expected_value}', but got None", self._pos())
26
+ return self.current_token.value == expected_value and (
27
+ self.__check_type(TokenType.KEYWORD) or self.__check_type(TokenType.SYMBOL)
28
+ )
29
+
30
+ def __consume_type(self, expected_type):
31
+ if self.current_token is None:
32
+ raise_error(self.file_path, self.file_data, f"Expected token of type {expected_type.value}, but got None", self._pos())
33
+ if self.current_token.tok_type != expected_type:
34
+ raise_error(self.file_path, self.file_data, f"Expected token of type {expected_type.value}, but got {self.current_token.tok_type.value}", self.current_token.position)
35
+ self.next_token()
36
+
37
+ def __consume_value(self, expected_value):
38
+ if self.current_token is None:
39
+ raise_error(self.file_path, self.file_data, f"Expected token with value '{expected_value}', but got None", self._pos())
40
+ if self.current_token.value != expected_value:
41
+ raise_error(self.file_path, self.file_data, f"Expected token with value '{expected_value}', but got '{self.current_token.value}'", self.current_token.position)
42
+ self.next_token()
43
+
44
+ def _parse_primary(self):
45
+ if self.__check_type(TokenType.STRING):
46
+ node = ASTNode.create_terminal_node(AstType.STR_LITERAL, self.current_token.value, self.current_token.position)
47
+ self.__consume_type(TokenType.STRING)
48
+ return node
49
+ if self.current_token.tok_type in (TokenType.INT, TokenType.HEX, TokenType.OCT, TokenType.BIN):
50
+ node = ASTNode.create_terminal_node(AstType.INT_LITERAL, self.current_token.value, self.current_token.position)
51
+ self.next_token()
52
+ return node
53
+ if self.__check_type(TokenType.KEYWORD) and self.current_token.value in (Keyword.TRUE.value, Keyword.FALSE.value):
54
+ node = ASTNode.create_terminal_node(AstType.BOOL_LITERAL, self.current_token.value, self.current_token.position)
55
+ self.__consume_type(TokenType.KEYWORD)
56
+ return node
57
+ if self.__check_type(TokenType.IDENTIFIER):
58
+ node = ASTNode.create_terminal_node(AstType.IDENTIFIER, self.current_token.value, self.current_token.position)
59
+ self.__consume_type(TokenType.IDENTIFIER)
60
+ return node
61
+ if self.__check_value('('):
62
+ self.__consume_value('(')
63
+ node = self._parse_expression()
64
+ self.__consume_value(')')
65
+ return node
66
+ raise_error(self.file_path, self.file_data, f"unexpected token in expression", self.current_token.position)
67
+
68
+ def _parse_unary(self):
69
+ if self.current_token and self.__check_type(TokenType.SYMBOL) and self.current_token.value in ('+', '-', '!', '~'):
70
+ pos = self.current_token.position
71
+ op = self.current_token.value
72
+ self.__consume_value(op)
73
+ operand = self._parse_unary()
74
+ if operand is None:
75
+ raise_error(self.file_path, self.file_data, "expected expression after unary operator", pos)
76
+ ast_type = {
77
+ '+': AstType.UNARY_PLUS,
78
+ '-': AstType.UNARY_MINUS,
79
+ '!': AstType.UNARY_NOT,
80
+ '~': AstType.UNARY_BITWISE_NOT,
81
+ }[op]
82
+ return ASTNode.create_unary(ast_type, operand, pos)
83
+ return self._parse_primary()
84
+
85
+ def _parse_multiplicative(self):
86
+ left = self._parse_unary()
87
+ while self.current_token and self.__check_type(TokenType.SYMBOL) and self.current_token.value in ('*', '/', '%'):
88
+ pos = self.current_token.position
89
+ op = self.current_token.value
90
+ self.__consume_value(op)
91
+ right = self._parse_unary()
92
+ if right is None:
93
+ raise_error(self.file_path, self.file_data, "expected expression", pos)
94
+ ast_type = {
95
+ '*': AstType.BINARY_MUL,
96
+ '/': AstType.BINARY_DIV,
97
+ '%': AstType.BINARY_MOD,
98
+ }[op]
99
+ left = ASTNode.create_binary(ast_type, left, right, pos)
100
+ return left
101
+
102
+ def _parse_additive(self):
103
+ left = self._parse_multiplicative()
104
+ while self.current_token and self.__check_type(TokenType.SYMBOL) and self.current_token.value in ('+', '-'):
105
+ pos = self.current_token.position
106
+ op = self.current_token.value
107
+ self.__consume_value(op)
108
+ right = self._parse_multiplicative()
109
+ if right is None:
110
+ raise_error(self.file_path, self.file_data, "expected expression", pos)
111
+ ast_type = AstType.BINARY_ADD if op == '+' else AstType.BINARY_SUB
112
+ left = ASTNode.create_binary(ast_type, left, right, pos)
113
+ return left
114
+
115
+ def _parse_shift(self):
116
+ left = self._parse_additive()
117
+ while self.current_token and self.__check_type(TokenType.SYMBOL) and self.current_token.value in ('<<', '>>'):
118
+ pos = self.current_token.position
119
+ op = self.current_token.value
120
+ self.__consume_value(op)
121
+ right = self._parse_additive()
122
+ if right is None:
123
+ raise_error(self.file_path, self.file_data, "expected expression", pos)
124
+ ast_type = AstType.BINARY_SHL if op == '<<' else AstType.BINARY_SHR
125
+ left = ASTNode.create_binary(ast_type, left, right, pos)
126
+ return left
127
+
128
+ def _parse_relational(self):
129
+ left = self._parse_shift()
130
+ while self.current_token and self.__check_type(TokenType.SYMBOL) and self.current_token.value in ('<', '<=', '>', '>='):
131
+ pos = self.current_token.position
132
+ op = self.current_token.value
133
+ self.__consume_value(op)
134
+ right = self._parse_shift()
135
+ if right is None:
136
+ raise_error(self.file_path, self.file_data, "expected expression", pos)
137
+ ast_type = {
138
+ '<': AstType.BINARY_LT,
139
+ '<=': AstType.BINARY_LTE,
140
+ '>': AstType.BINARY_GT,
141
+ '>=': AstType.BINARY_GTE,
142
+ }[op]
143
+ left = ASTNode.create_binary(ast_type, left, right, pos)
144
+ return left
145
+
146
+ def _parse_equality(self):
147
+ left = self._parse_relational()
148
+ while self.current_token and self.__check_type(TokenType.SYMBOL) and self.current_token.value in ('==', '!='):
149
+ pos = self.current_token.position
150
+ op = self.current_token.value
151
+ self.__consume_value(op)
152
+ right = self._parse_relational()
153
+ if right is None:
154
+ raise_error(self.file_path, self.file_data, "expected expression", pos)
155
+ ast_type = AstType.BINARY_EQ if op == '==' else AstType.BINARY_NE
156
+ left = ASTNode.create_binary(ast_type, left, right, pos)
157
+ return left
158
+
159
+ def _parse_bitwise_and(self):
160
+ left = self._parse_equality()
161
+ while self.current_token and self.__check_type(TokenType.SYMBOL) and self.current_token.value == '&':
162
+ pos = self.current_token.position
163
+ self.__consume_value('&')
164
+ right = self._parse_equality()
165
+ if right is None:
166
+ raise_error(self.file_path, self.file_data, "expected expression", pos)
167
+ left = ASTNode.create_binary(AstType.BINARY_BITWISE_AND, left, right, pos)
168
+ return left
169
+
170
+ def _parse_bitwise_xor(self):
171
+ left = self._parse_bitwise_and()
172
+ while self.current_token and self.__check_type(TokenType.SYMBOL) and self.current_token.value == '^':
173
+ pos = self.current_token.position
174
+ self.__consume_value('^')
175
+ right = self._parse_bitwise_and()
176
+ if right is None:
177
+ raise_error(self.file_path, self.file_data, "expected expression", pos)
178
+ left = ASTNode.create_binary(AstType.BINARY_BITWISE_XOR, left, right, pos)
179
+ return left
180
+
181
+ def _parse_bitwise_or(self):
182
+ left = self._parse_bitwise_xor()
183
+ while self.current_token and self.__check_type(TokenType.SYMBOL) and self.current_token.value == '|':
184
+ pos = self.current_token.position
185
+ self.__consume_value('|')
186
+ right = self._parse_bitwise_xor()
187
+ if right is None:
188
+ raise_error(self.file_path, self.file_data, "expected expression", pos)
189
+ left = ASTNode.create_binary(AstType.BINARY_BITWISE_OR, left, right, pos)
190
+ return left
191
+
192
+ def _parse_logical_and(self):
193
+ left = self._parse_bitwise_or()
194
+ while self.current_token and self.__check_type(TokenType.SYMBOL) and self.current_token.value == '&&':
195
+ pos = self.current_token.position
196
+ self.__consume_value('&&')
197
+ right = self._parse_bitwise_or()
198
+ if right is None:
199
+ raise_error(self.file_path, self.file_data, "expected expression", pos)
200
+ left = ASTNode.create_binary(AstType.BINARY_LOGICAL_AND, left, right, pos)
201
+ return left
202
+
203
+ def _parse_logical_or(self):
204
+ left = self._parse_logical_and()
205
+ while self.current_token and self.__check_type(TokenType.SYMBOL) and self.current_token.value == '||':
206
+ pos = self.current_token.position
207
+ self.__consume_value('||')
208
+ right = self._parse_logical_and()
209
+ if right is None:
210
+ raise_error(self.file_path, self.file_data, "expected expression", pos)
211
+ left = ASTNode.create_binary(AstType.BINARY_LOGICAL_OR, left, right, pos)
212
+ return left
213
+
214
+ def _parse_expression(self):
215
+ return self._parse_logical_or()
216
+
217
+ def _parse_value(self):
218
+ if self.current_token.tok_type == TokenType.STRING:
219
+ node = ASTNode.create_terminal_node(AstType.STR_LITERAL, self.current_token.value, self.current_token.position)
220
+ self.__consume_type(TokenType.STRING)
221
+ elif self.current_token.tok_type in (TokenType.INT, TokenType.HEX, TokenType.OCT, TokenType.BIN):
222
+ node = ASTNode.create_terminal_node(AstType.INT_LITERAL, self.current_token.value, self.current_token.position)
223
+ self.next_token()
224
+ elif self.current_token.tok_type == TokenType.KEYWORD and self.current_token.value in (Keyword.TRUE.value, Keyword.FALSE.value):
225
+ node = ASTNode.create_terminal_node(AstType.BOOL_LITERAL, self.current_token.value, self.current_token.position)
226
+ self.__consume_type(TokenType.KEYWORD)
227
+ elif self.current_token.tok_type == TokenType.IDENTIFIER:
228
+ node = ASTNode.create_terminal_node(AstType.IDENTIFIER, self.current_token.value, self.current_token.position)
229
+ self.__consume_type(TokenType.IDENTIFIER)
230
+ else:
231
+ raise_error(self.file_path, self.file_data, f"unexpected token in value", self.current_token.position)
232
+ return node
233
+
234
+ def _parse_enum_item(self):
235
+ pos = self.current_token.position
236
+ name_node = ASTNode.create_terminal_node(AstType.IDENTIFIER, self.current_token.value, self.current_token.position)
237
+ self.__consume_type(TokenType.IDENTIFIER)
238
+
239
+ node = ASTNode.create_enum_item(pos)
240
+ node.a = name_node
241
+
242
+ if self.__check_value('='):
243
+ self.__consume_value('=')
244
+ node.b = self._parse_expression()
245
+
246
+ return node
247
+
248
+ def _parse_enum(self):
249
+ pos = self.current_token.position
250
+ self.__consume_value(Keyword.ENUM.value)
251
+ name_node = ASTNode.create_terminal_node(AstType.IDENTIFIER, self.current_token.value, self.current_token.position)
252
+ self.__consume_type(TokenType.IDENTIFIER)
253
+ self.__consume_value('{')
254
+
255
+ node = ASTNode.create_enum_decl(pos)
256
+ node.a = name_node
257
+
258
+ prev = None
259
+ while not self.__check_value('}'):
260
+ item = self._parse_enum_item()
261
+ if prev:
262
+ prev.next = item
263
+ else:
264
+ node.b = item
265
+ prev = item
266
+ if self.__check_value(','):
267
+ self.__consume_value(',')
268
+
269
+ self.__consume_value('}')
270
+ return node
271
+
272
+ def _parse_attr(self):
273
+ pos = self.current_token.position
274
+ name_node = ASTNode.create_terminal_node(AstType.IDENTIFIER, self.current_token.value, self.current_token.position)
275
+ self.__consume_type(TokenType.IDENTIFIER)
276
+ self.__consume_value('=')
277
+
278
+ node = ASTNode.create_field_attr(pos)
279
+ node.a = name_node
280
+
281
+ if self.current_token.tok_type == TokenType.STRING:
282
+ val_node = ASTNode.create_terminal_node(AstType.STR_LITERAL, self.current_token.value, self.current_token.position)
283
+ self.__consume_type(TokenType.STRING)
284
+ elif self.current_token.tok_type in (TokenType.INT, TokenType.HEX, TokenType.OCT, TokenType.BIN):
285
+ val_node = ASTNode.create_terminal_node(AstType.INT_LITERAL, self.current_token.value, self.current_token.position)
286
+ self.next_token()
287
+ else:
288
+ raise_error(self.file_path, self.file_data, f"expected value in attribute", self.current_token.position)
289
+
290
+ node.b = val_node
291
+ return node
292
+
293
+ def _parse_attrs(self):
294
+ pos = self.current_token.position
295
+ self.__consume_value('(')
296
+ node = ASTNode.create_field_attrs(pos)
297
+
298
+ prev = None
299
+ while not self.__check_value(')'):
300
+ attr = self._parse_attr()
301
+ if prev:
302
+ prev.next = attr
303
+ else:
304
+ node.a = attr
305
+ prev = attr
306
+ if self.__check_value(','):
307
+ self.__consume_value(',')
308
+
309
+ self.__consume_value(')')
310
+ return node
311
+
312
+ def _parse_type(self):
313
+ pos = self.current_token.position
314
+ if not (self.__check_type(TokenType.IDENTIFIER) or self.__check_type(TokenType.KEYWORD)):
315
+ raise_error(self.file_path, self.file_data, f"expected type name, got '{self.current_token.value}'", self.current_token.position)
316
+ type_name = self.current_token.value
317
+ self.next_token()
318
+
319
+ if self.__check_value('?'):
320
+ self.__consume_value('?')
321
+ type_name += '?'
322
+
323
+ node = ASTNode.create_terminal_node(AstType.TYPE, type_name, pos)
324
+
325
+ if self.__check_value('('):
326
+ node.a = self._parse_attrs()
327
+
328
+ return node
329
+
330
+ def _parse_field(self):
331
+ pos = self.current_token.position
332
+ name_node = ASTNode.create_terminal_node(AstType.IDENTIFIER, self.current_token.value, self.current_token.position)
333
+ self.__consume_type(TokenType.IDENTIFIER)
334
+ self.__consume_value(':')
335
+
336
+ type_node = self._parse_type()
337
+
338
+ node = ASTNode.create_field_decl(pos)
339
+ node.a = name_node
340
+ node.b = type_node
341
+
342
+ if self.__check_value('='):
343
+ self.__consume_value('=')
344
+ val_node = self._parse_expression()
345
+ node.d = val_node
346
+
347
+ if self.__check_type(TokenType.KEYWORD) and self.current_token.value in (Keyword.PK.value, Keyword.UNIQUE.value):
348
+ node.value = self.current_token.value
349
+ self.__consume_type(TokenType.KEYWORD)
350
+
351
+ if self.__check_value(';'):
352
+ self.__consume_value(';')
353
+
354
+ return node
355
+
356
+ def _parse_table(self):
357
+ pos = self.current_token.position
358
+ self.__consume_value(Keyword.TABLE.value)
359
+ name_node = ASTNode.create_terminal_node(AstType.IDENTIFIER, self.current_token.value, self.current_token.position)
360
+ self.__consume_type(TokenType.IDENTIFIER)
361
+ self.__consume_value('{')
362
+
363
+ node = ASTNode.create_table_decl(pos)
364
+ node.a = name_node
365
+
366
+ prev = None
367
+ while not self.__check_value('}'):
368
+ field = self._parse_field()
369
+ if prev:
370
+ prev.next = field
371
+ else:
372
+ node.b = field
373
+ prev = field
374
+
375
+ self.__consume_value('}')
376
+ return node
377
+
378
+ def _parse_decl(self):
379
+ if self.__check_type(TokenType.KEYWORD):
380
+ if self.current_token.value == Keyword.ENUM.value:
381
+ return self._parse_enum()
382
+ if self.current_token.value == Keyword.TABLE.value:
383
+ return self._parse_table()
384
+ raise_error(self.file_path, self.file_data, f"unexpected token '{self.current_token.value}'", self.current_token.position)
385
+
386
+ def _parse_program(self):
387
+ node = ASTNode.create_program(self.current_token.position)
388
+ last = node
389
+ while self.current_token.tok_type != TokenType.EOF:
390
+ decl = self._parse_decl()
391
+ last.next = decl
392
+ last = decl
393
+ return node
394
+
395
+ def parse(self):
396
+ self.next_token()
397
+ return self._parse_program()
src/pos.py ADDED
@@ -0,0 +1,11 @@
1
+
2
+
3
+
4
+ class Pos:
5
+ def __init__(self, line, column):
6
+ self.line = line
7
+ self.column = column
8
+
9
+ def __repr__(self):
10
+ return f"Pos({self.line}, {self.column})"
11
+
src/table.py ADDED
@@ -0,0 +1,37 @@
1
+
2
+
3
+
4
+
5
+ from src.column import ColumnType
6
+
7
+
8
+ PRIMITIVE_TYPES = {'int', 'long', 'float', 'double', 'string', 'bool', 'uuid', 'datetime'}
9
+
10
+
11
+ class Table:
12
+ def __init__(self, name, columns=[]):
13
+ self.name = name
14
+ self.columns: list[tuple[str, ColumnType]] = columns
15
+
16
+ @classmethod
17
+ def from_ast(cls, node, table_names=None):
18
+ table_names = table_names or set()
19
+ name = node.a.value
20
+ columns = []
21
+ cur = node.b
22
+ while cur:
23
+ col_name = cur.a.value
24
+ type_node = cur.b
25
+ col_type = ColumnType.from_ast(type_node, table_names)
26
+ if cur.d is not None:
27
+ col_type.default_value_node = cur.d
28
+ columns.append((col_name, col_type))
29
+ cur = cur.next
30
+ return cls(name, columns)
31
+
32
+ def dependencies(self):
33
+ deps = set()
34
+ for _, col_type in self.columns:
35
+ if col_type.is_fk and col_type.ref_table:
36
+ deps.add(col_type.ref_table)
37
+ return deps
src/template.py ADDED
@@ -0,0 +1,14 @@
1
+
2
+
3
+
4
+ import enum
5
+
6
+
7
+ class Template(enum.Enum):
8
+ """
9
+ Enum class for template types.
10
+ """
11
+ DOTNETCSHARP = "dotnet-csharp"
12
+ DOTNETCSHARP_CLEANARCHITECTURE = "dotnet-csharp-clean-architecture"
13
+ LARAVELPHP = "laravel-php"
14
+
src/tok.py ADDED
@@ -0,0 +1,12 @@
1
+ from src.pos import Pos
2
+ from src.tok_type import TokenType
3
+
4
+
5
+ class Tok:
6
+ def __init__(self, tok_type: TokenType, value, position: Pos):
7
+ self.tok_type = tok_type
8
+ self.value = value
9
+ self.position = position
10
+
11
+ def __repr__(self):
12
+ return f"Tok({self.tok_type.value}, {self.value}, {self.position})"
src/tok_type.py ADDED
@@ -0,0 +1,13 @@
1
+ from enum import Enum
2
+
3
+
4
+ class TokenType(Enum):
5
+ IDENTIFIER = 'IDENTIFIER'
6
+ KEYWORD = 'KEYWORD'
7
+ STRING = 'STRING'
8
+ INT = 'INT'
9
+ HEX = 'HEX'
10
+ OCT = 'OCT'
11
+ BIN = 'BIN'
12
+ SYMBOL = 'SYMBOL'
13
+ EOF = 'EOF'