kaqing 2.0.171__py3-none-any.whl → 2.0.203__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.
- adam/app_session.py +5 -10
- adam/apps.py +18 -4
- adam/batch.py +7 -7
- adam/checks/check_utils.py +3 -1
- adam/checks/disk.py +2 -3
- adam/columns/memory.py +3 -4
- adam/commands/__init__.py +15 -6
- adam/commands/alter_tables.py +26 -41
- adam/commands/app/__init__.py +0 -0
- adam/commands/{app_cmd.py → app/app.py} +2 -2
- adam/commands/{show → app}/show_app_actions.py +7 -15
- adam/commands/{show → app}/show_app_queues.py +1 -4
- adam/{utils_app.py → commands/app/utils_app.py} +9 -1
- adam/commands/audit/audit.py +9 -26
- adam/commands/audit/audit_repair_tables.py +5 -7
- adam/commands/audit/audit_run.py +1 -1
- adam/commands/audit/completions_l.py +15 -0
- adam/commands/audit/show_last10.py +2 -14
- adam/commands/audit/show_slow10.py +2 -13
- adam/commands/audit/show_top10.py +2 -11
- adam/commands/audit/utils_show_top10.py +15 -3
- adam/commands/bash/bash.py +1 -1
- adam/commands/bash/utils_bash.py +1 -1
- adam/commands/cassandra/__init__.py +0 -0
- adam/commands/cassandra/download_cassandra_log.py +45 -0
- adam/commands/cassandra/nodetool.py +64 -0
- adam/commands/cassandra/nodetool_commands.py +120 -0
- adam/commands/cassandra/restart_cluster.py +47 -0
- adam/commands/cassandra/restart_node.py +51 -0
- adam/commands/cassandra/restart_nodes.py +47 -0
- adam/commands/cassandra/rollout.py +88 -0
- adam/commands/cat.py +5 -19
- adam/commands/cd.py +7 -9
- adam/commands/check.py +10 -18
- adam/commands/cli_commands.py +6 -1
- adam/commands/{cp.py → clipboard_copy.py} +34 -36
- adam/commands/code.py +2 -2
- adam/commands/command.py +139 -22
- adam/commands/commands_utils.py +14 -12
- adam/commands/cql/alter_tables.py +66 -0
- adam/commands/cql/completions_c.py +29 -0
- adam/commands/cql/cqlsh.py +3 -7
- adam/commands/cql/utils_cql.py +23 -61
- adam/commands/debug/__init__.py +0 -0
- adam/commands/debug/debug.py +22 -0
- adam/commands/debug/debug_completes.py +35 -0
- adam/commands/debug/debug_timings.py +35 -0
- adam/commands/deploy/deploy_pg_agent.py +2 -2
- adam/commands/deploy/deploy_pod.py +2 -4
- adam/commands/deploy/undeploy_pg_agent.py +2 -2
- adam/commands/devices/device.py +40 -9
- adam/commands/devices/device_app.py +19 -29
- adam/commands/devices/device_auit_log.py +3 -3
- adam/commands/devices/device_cass.py +17 -23
- adam/commands/devices/device_export.py +12 -11
- adam/commands/devices/device_postgres.py +79 -63
- adam/commands/devices/devices.py +1 -1
- adam/commands/download_cassandra_log.py +45 -0
- adam/commands/download_file.py +47 -0
- adam/commands/export/clean_up_all_export_sessions.py +3 -3
- adam/commands/export/clean_up_export_sessions.py +7 -19
- adam/commands/export/completions_x.py +11 -0
- adam/commands/export/download_export_session.py +40 -0
- adam/commands/export/drop_export_database.py +6 -22
- adam/commands/export/drop_export_databases.py +3 -9
- adam/commands/export/export.py +1 -17
- adam/commands/export/export_databases.py +109 -32
- adam/commands/export/export_select.py +8 -55
- adam/commands/export/export_sessions.py +211 -0
- adam/commands/export/export_use.py +13 -16
- adam/commands/export/export_x_select.py +48 -0
- adam/commands/export/exporter.py +176 -167
- adam/commands/export/import_files.py +44 -0
- adam/commands/export/import_session.py +10 -6
- adam/commands/export/importer.py +24 -9
- adam/commands/export/importer_athena.py +114 -44
- adam/commands/export/importer_sqlite.py +45 -23
- adam/commands/export/show_column_counts.py +11 -20
- adam/commands/export/show_export_databases.py +5 -2
- adam/commands/export/show_export_session.py +6 -15
- adam/commands/export/show_export_sessions.py +4 -11
- adam/commands/export/utils_export.py +79 -27
- adam/commands/find_files.py +51 -0
- adam/commands/find_processes.py +76 -0
- adam/commands/generate_report.py +52 -0
- adam/commands/head.py +36 -0
- adam/commands/help.py +2 -2
- adam/commands/intermediate_command.py +6 -3
- adam/commands/login.py +3 -6
- adam/commands/ls.py +2 -2
- adam/commands/medusa/medusa_backup.py +13 -16
- adam/commands/medusa/medusa_restore.py +26 -37
- adam/commands/medusa/medusa_show_backupjobs.py +7 -7
- adam/commands/medusa/medusa_show_restorejobs.py +6 -6
- adam/commands/medusa/utils_medusa.py +15 -0
- adam/commands/nodetool.py +3 -8
- adam/commands/os/__init__.py +0 -0
- adam/commands/os/cat.py +36 -0
- adam/commands/os/download_file.py +47 -0
- adam/commands/os/find_files.py +51 -0
- adam/commands/os/find_processes.py +76 -0
- adam/commands/os/head.py +36 -0
- adam/commands/os/shell.py +41 -0
- adam/commands/param_get.py +10 -12
- adam/commands/param_set.py +7 -10
- adam/commands/postgres/completions_p.py +22 -0
- adam/commands/postgres/postgres.py +25 -40
- adam/commands/postgres/postgres_databases.py +269 -0
- adam/commands/postgres/utils_postgres.py +33 -20
- adam/commands/preview_table.py +4 -2
- adam/commands/pwd.py +4 -6
- adam/commands/reaper/reaper_forward.py +2 -2
- adam/commands/reaper/reaper_run_abort.py +4 -10
- adam/commands/reaper/reaper_runs.py +3 -3
- adam/commands/reaper/reaper_schedule_activate.py +12 -12
- adam/commands/reaper/reaper_schedule_start.py +7 -12
- adam/commands/reaper/reaper_schedule_stop.py +7 -12
- adam/commands/reaper/utils_reaper.py +13 -6
- adam/commands/repair/repair_log.py +1 -4
- adam/commands/repair/repair_run.py +3 -8
- adam/commands/repair/repair_scan.py +1 -6
- adam/commands/repair/repair_stop.py +1 -5
- adam/commands/restart_cluster.py +47 -0
- adam/commands/restart_node.py +51 -0
- adam/commands/restart_nodes.py +47 -0
- adam/commands/shell.py +9 -2
- adam/commands/show/show.py +4 -4
- adam/commands/show/show_adam.py +3 -3
- adam/commands/show/show_cassandra_repairs.py +5 -6
- adam/commands/show/show_cassandra_status.py +29 -29
- adam/commands/show/show_cassandra_version.py +1 -4
- adam/commands/show/{show_commands.py → show_cli_commands.py} +3 -6
- adam/commands/show/show_login.py +3 -9
- adam/commands/show/show_params.py +2 -5
- adam/commands/show/show_processes.py +15 -16
- adam/commands/show/show_storage.py +9 -8
- adam/config.py +4 -5
- adam/embedded_params.py +1 -1
- adam/log.py +4 -4
- adam/repl.py +26 -18
- adam/repl_commands.py +32 -20
- adam/repl_session.py +9 -1
- adam/repl_state.py +39 -10
- adam/sql/async_executor.py +44 -0
- adam/sql/lark_completer.py +286 -0
- adam/sql/lark_parser.py +604 -0
- adam/sql/qingl.lark +1076 -0
- adam/sql/sql_completer.py +4 -6
- adam/sql/sql_state_machine.py +25 -13
- adam/sso/authn_ad.py +2 -5
- adam/sso/authn_okta.py +2 -4
- adam/sso/cred_cache.py +2 -5
- adam/sso/idp.py +8 -11
- adam/utils.py +299 -105
- adam/utils_athena.py +18 -18
- adam/utils_audits.py +3 -7
- adam/utils_issues.py +2 -2
- adam/utils_k8s/app_clusters.py +4 -4
- adam/utils_k8s/app_pods.py +8 -6
- adam/utils_k8s/cassandra_clusters.py +16 -5
- adam/utils_k8s/cassandra_nodes.py +9 -6
- adam/utils_k8s/custom_resources.py +11 -17
- adam/utils_k8s/jobs.py +7 -11
- adam/utils_k8s/k8s.py +14 -5
- adam/utils_k8s/kube_context.py +3 -6
- adam/{pod_exec_result.py → utils_k8s/pod_exec_result.py} +4 -4
- adam/utils_k8s/pods.py +85 -23
- adam/utils_k8s/statefulsets.py +5 -2
- adam/utils_local.py +42 -0
- adam/utils_repl/appendable_completer.py +6 -0
- adam/utils_repl/repl_completer.py +45 -2
- adam/utils_repl/state_machine.py +3 -3
- adam/utils_sqlite.py +58 -30
- adam/version.py +1 -1
- {kaqing-2.0.171.dist-info → kaqing-2.0.203.dist-info}/METADATA +1 -1
- kaqing-2.0.203.dist-info/RECORD +277 -0
- kaqing-2.0.203.dist-info/top_level.txt +2 -0
- teddy/__init__.py +0 -0
- teddy/lark_parser.py +436 -0
- teddy/lark_parser2.py +618 -0
- adam/commands/cql/cql_completions.py +0 -33
- adam/commands/export/export_handlers.py +0 -71
- adam/commands/export/export_select_x.py +0 -54
- adam/commands/logs.py +0 -37
- adam/commands/postgres/postgres_context.py +0 -274
- adam/commands/postgres/psql_completions.py +0 -10
- adam/commands/report.py +0 -61
- adam/commands/restart.py +0 -60
- kaqing-2.0.171.dist-info/RECORD +0 -236
- kaqing-2.0.171.dist-info/top_level.txt +0 -1
- /adam/commands/{app_ping.py → app/app_ping.py} +0 -0
- /adam/commands/{show → app}/show_app_id.py +0 -0
- {kaqing-2.0.171.dist-info → kaqing-2.0.203.dist-info}/WHEEL +0 -0
- {kaqing-2.0.171.dist-info → kaqing-2.0.203.dist-info}/entry_points.txt +0 -0
adam/sql/lark_parser.py
ADDED
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
from copy import copy
|
|
2
|
+
import re
|
|
3
|
+
from typing import Union, cast
|
|
4
|
+
from lark import Lark, Token, Tree
|
|
5
|
+
from lark.lexer import TerminalDef
|
|
6
|
+
from lark.grammar import NonTerminal, Terminal
|
|
7
|
+
|
|
8
|
+
def log(msg: str = None, debug: bool = False, depth: int = 0):
|
|
9
|
+
if not msg:
|
|
10
|
+
print()
|
|
11
|
+
return
|
|
12
|
+
|
|
13
|
+
if debug:
|
|
14
|
+
print(depth * ' ' + msg)
|
|
15
|
+
|
|
16
|
+
class GNode:
|
|
17
|
+
def __init__(self, name: str, token: Token = None, choices: list[int] = []):
|
|
18
|
+
self.name = name
|
|
19
|
+
self.token = token
|
|
20
|
+
self.choices = choices
|
|
21
|
+
|
|
22
|
+
def __eq__(self, other: 'GNode'):
|
|
23
|
+
return self.__repr__() == other.__repr__()
|
|
24
|
+
|
|
25
|
+
def __hash__(self):
|
|
26
|
+
return hash(self.__repr__())
|
|
27
|
+
|
|
28
|
+
def __repr__(self):
|
|
29
|
+
n = self.name if self.name else f'{self.token}'
|
|
30
|
+
if self.choices:
|
|
31
|
+
n = f'{n}-{"-".join([f"{c}" for c in self.choices])}'
|
|
32
|
+
|
|
33
|
+
return n
|
|
34
|
+
|
|
35
|
+
def pname(self):
|
|
36
|
+
return self.name if self.name else f'{self.token}'
|
|
37
|
+
|
|
38
|
+
def choice(self, depth: int):
|
|
39
|
+
if depth < len(self.choices):
|
|
40
|
+
return self.choices[depth]
|
|
41
|
+
|
|
42
|
+
return -1
|
|
43
|
+
|
|
44
|
+
def drop_last_choice(self):
|
|
45
|
+
new_node = copy(self)
|
|
46
|
+
new_node.choices = self.choices[:-1]
|
|
47
|
+
|
|
48
|
+
return new_node
|
|
49
|
+
|
|
50
|
+
def parse(s: str):
|
|
51
|
+
name_n_choices = s.split('-')
|
|
52
|
+
choices = []
|
|
53
|
+
if len(name_n_choices) > 1:
|
|
54
|
+
choices = {int(k) for k in name_n_choices[1:]}
|
|
55
|
+
|
|
56
|
+
return GNode(name_n_choices[0], choices=choices)
|
|
57
|
+
|
|
58
|
+
def add_choice(self, choice: int):
|
|
59
|
+
self.choices.append(choice)
|
|
60
|
+
|
|
61
|
+
class GPath:
|
|
62
|
+
def __init__(self, nodes: list[GNode], complete: bool):
|
|
63
|
+
self.nodes = nodes
|
|
64
|
+
self.complete = complete
|
|
65
|
+
|
|
66
|
+
def __eq__(self, other: 'GPath'):
|
|
67
|
+
return self.__repr__() == other.__repr__()
|
|
68
|
+
|
|
69
|
+
def __hash__(self):
|
|
70
|
+
return hash(self.__repr__())
|
|
71
|
+
|
|
72
|
+
def __repr__(self):
|
|
73
|
+
r = '.'.join([f'{p}' for p in self.nodes])
|
|
74
|
+
|
|
75
|
+
return r
|
|
76
|
+
|
|
77
|
+
def token(self):
|
|
78
|
+
return self.nodes[-1] if self.nodes else GNode(None, choices={})
|
|
79
|
+
|
|
80
|
+
def append(self, node):
|
|
81
|
+
new_path = copy(self)
|
|
82
|
+
nodes = []
|
|
83
|
+
for n in self.nodes:
|
|
84
|
+
n1 = copy(n)
|
|
85
|
+
ss = []
|
|
86
|
+
for k in n.choices:
|
|
87
|
+
ss.append(k)
|
|
88
|
+
n1.choices = ss
|
|
89
|
+
nodes.append(n1)
|
|
90
|
+
nodes.append(node)
|
|
91
|
+
new_path.nodes = nodes
|
|
92
|
+
|
|
93
|
+
return new_path
|
|
94
|
+
|
|
95
|
+
def drop_last(self):
|
|
96
|
+
new_path = copy(self)
|
|
97
|
+
nodes = []
|
|
98
|
+
for n in self.nodes[:-1]:
|
|
99
|
+
nodes.append(n)
|
|
100
|
+
|
|
101
|
+
new_path.nodes = nodes
|
|
102
|
+
|
|
103
|
+
return new_path
|
|
104
|
+
|
|
105
|
+
def clone(self):
|
|
106
|
+
new_path = copy(self)
|
|
107
|
+
nodes = []
|
|
108
|
+
for n in self.nodes:
|
|
109
|
+
n1 = copy(n)
|
|
110
|
+
ss = []
|
|
111
|
+
for k in n.choices:
|
|
112
|
+
ss.append(k)
|
|
113
|
+
n1.choices = ss
|
|
114
|
+
nodes.append(n1)
|
|
115
|
+
new_path.nodes = nodes
|
|
116
|
+
|
|
117
|
+
return new_path
|
|
118
|
+
|
|
119
|
+
def terminal(self):
|
|
120
|
+
last_node = self.nodes[-1]
|
|
121
|
+
return last_node.pname()
|
|
122
|
+
|
|
123
|
+
def terminals(paths: set['GPath']):
|
|
124
|
+
return ','.join(p.terminal() for p in list(paths))
|
|
125
|
+
|
|
126
|
+
def with_new_choice(self, choice: int):
|
|
127
|
+
new_path = self.clone()
|
|
128
|
+
new_path.nodes[-1].add_choice(choice)
|
|
129
|
+
|
|
130
|
+
return new_path
|
|
131
|
+
|
|
132
|
+
def with_next_choice(self):
|
|
133
|
+
new_path = self.clone()
|
|
134
|
+
new_path.nodes[-1].choices[-1] += 1
|
|
135
|
+
|
|
136
|
+
return new_path
|
|
137
|
+
|
|
138
|
+
def drop_last_choice(self):
|
|
139
|
+
new_path = self.clone()
|
|
140
|
+
new_path.nodes[-1] = new_path.nodes[-1].drop_last_choice()
|
|
141
|
+
|
|
142
|
+
return new_path
|
|
143
|
+
|
|
144
|
+
def name_path(self):
|
|
145
|
+
# select_clause-0-2.projection-0-1-0-0.result_expr-1-0.expr-0-0-0-0-1-0.path-0-0.identifier_ref-0-0
|
|
146
|
+
return '.'.join([n.name for n in self.nodes])
|
|
147
|
+
|
|
148
|
+
class LarkParser:
|
|
149
|
+
show_returns = False
|
|
150
|
+
|
|
151
|
+
def __init__(self, grammar: str = None, contexts_by_path: dict[str, str] = {}):
|
|
152
|
+
if not grammar:
|
|
153
|
+
grammar = """
|
|
154
|
+
start: expression
|
|
155
|
+
expression: term (("+" | "-") term)*
|
|
156
|
+
term: factor (("*" | "/") factor)*
|
|
157
|
+
factor: NUMBER | "(" expression ")"
|
|
158
|
+
NUMBER: /[0-9]+/
|
|
159
|
+
%ignore " "
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
self.contexts_by_path = contexts_by_path
|
|
163
|
+
|
|
164
|
+
parser = Lark(grammar, start='start')
|
|
165
|
+
self.parser = parser
|
|
166
|
+
|
|
167
|
+
self.rules = {rule_def[0]: rule_def[2] for rule_def in parser.grammar.rule_defs}
|
|
168
|
+
self.terminals = {rule_def[0]: rule_def[1] for rule_def in parser.grammar.term_defs}
|
|
169
|
+
self.rules_by_name1 = {rule_def[0]: rule_def[1] for rule_def in parser.grammar.rule_defs}
|
|
170
|
+
self.rules_by_name2 = {rule_def[0]: rule_def[3] for rule_def in parser.grammar.rule_defs}
|
|
171
|
+
|
|
172
|
+
self.trees: dict[str, Tree] = {}
|
|
173
|
+
self._token_values_by_type: dict[str, str] = {}
|
|
174
|
+
|
|
175
|
+
def find_next_terminals(self, path: GPath, debug=False):
|
|
176
|
+
if path == 'start':
|
|
177
|
+
return self.visit_current(LarkParser.build_path(path), debug=debug)
|
|
178
|
+
|
|
179
|
+
if isinstance(path, set):
|
|
180
|
+
paths = set()
|
|
181
|
+
|
|
182
|
+
for p in list(path):
|
|
183
|
+
paths |= self.visit_next(p, debug=debug)
|
|
184
|
+
|
|
185
|
+
return paths
|
|
186
|
+
elif isinstance(path, str):
|
|
187
|
+
path = LarkParser.build_path(path)
|
|
188
|
+
|
|
189
|
+
if debug: print(f'find_next_terminals({path}, {path.token().token})')
|
|
190
|
+
|
|
191
|
+
return self.visit_next(path, debug=debug)
|
|
192
|
+
|
|
193
|
+
def visit_current(self, path: GPath, tree: Tree = None, debug=False):
|
|
194
|
+
paths: set[GPath] = set()
|
|
195
|
+
|
|
196
|
+
if debug: print(f'visit_current({path}, {path.token().token}), {path.nodes[-1].choices}')
|
|
197
|
+
if not tree:
|
|
198
|
+
tree, _ = self.tree_by_choices(path, debug=debug)
|
|
199
|
+
|
|
200
|
+
if isinstance(tree, Token):
|
|
201
|
+
if debug: print('token', tree)
|
|
202
|
+
if path.nodes[-1].token:
|
|
203
|
+
path = path.drop_last()
|
|
204
|
+
|
|
205
|
+
p = path.append(GNode(name=self.token_type_from_raw(f'{tree}'), token=tree, choices=[]))
|
|
206
|
+
if LarkParser.show_returns: print('<- ', tree.value, '\t\t', p)
|
|
207
|
+
paths.add(p)
|
|
208
|
+
elif isinstance(tree, Terminal):
|
|
209
|
+
if debug: print('terminal', tree.name)
|
|
210
|
+
p = path.append(GNode(tree.name, choices=[]))
|
|
211
|
+
if LarkParser.show_returns: print('<- ', tree.name, '\t\t', p)
|
|
212
|
+
paths.add(p)
|
|
213
|
+
elif isinstance(tree, NonTerminal):
|
|
214
|
+
if debug: print('non-termial', tree.name, tree)
|
|
215
|
+
paths |= self.visit_current(path.append(GNode(tree.name, choices=[])), debug=debug)
|
|
216
|
+
elif isinstance(tree, tuple):
|
|
217
|
+
paths |= self.visit_current(path, tree=tree[0], debug=debug)
|
|
218
|
+
elif not tree:
|
|
219
|
+
raise Exception(f'null tree at {path}')
|
|
220
|
+
elif tree.data == 'expr':
|
|
221
|
+
if debug: print('expr')
|
|
222
|
+
paths |= self.visit_current(path.with_new_choice(0), tree=tree.children[0], debug=debug)
|
|
223
|
+
|
|
224
|
+
if cast(Token, tree.children[1]).value in ['*', '?']:
|
|
225
|
+
paths |= self.visit_next(path, debug=debug)
|
|
226
|
+
elif tree.data == 'maybe':
|
|
227
|
+
if debug: print('maybe')
|
|
228
|
+
|
|
229
|
+
paths |= self.visit_current(path.with_new_choice(0), tree=tree.children[0], debug=debug)
|
|
230
|
+
paths |= self.visit_next(path, debug=debug)
|
|
231
|
+
elif tree.data == 'value':
|
|
232
|
+
if debug: print('value')
|
|
233
|
+
for child in tree.children:
|
|
234
|
+
paths |= self.visit_current(path, tree=child, debug=debug)
|
|
235
|
+
elif tree.data == 'range':
|
|
236
|
+
if debug: print('range')
|
|
237
|
+
token0: Token = tree.children[0]
|
|
238
|
+
token1: Token = tree.children[1]
|
|
239
|
+
if token0.value == '"0"' and token1.value == '"9"':
|
|
240
|
+
p = path.append(GNode(name=self.token_type_from_raw(f'{token0}'), token=token0, choices=[]))
|
|
241
|
+
if LarkParser.show_returns: print('<- ', token0, '\t\t', p)
|
|
242
|
+
paths.add(p)
|
|
243
|
+
else:
|
|
244
|
+
raise Exception('not implented')
|
|
245
|
+
elif tree.data == 'literal':
|
|
246
|
+
if debug: print('literal')
|
|
247
|
+
for child in tree.children:
|
|
248
|
+
paths |= self.visit_current(path, tree=child, debug=debug)
|
|
249
|
+
elif tree.data == 'expansions':
|
|
250
|
+
for i, child in enumerate(tree.children):
|
|
251
|
+
paths |= self.visit_current(path.with_new_choice(i), tree=child, debug=debug)
|
|
252
|
+
elif tree.data == 'expansion':
|
|
253
|
+
paths |= self.visit_current(path.with_new_choice(0), tree=tree.children[0], debug=debug)
|
|
254
|
+
elif tree.data == 'alias':
|
|
255
|
+
paths |= self.visit_current(path, tree=tree.children[0], debug=debug)
|
|
256
|
+
|
|
257
|
+
return paths
|
|
258
|
+
|
|
259
|
+
def visit_next(self, path: GPath, debug=False):
|
|
260
|
+
paths: set[GPath] = set()
|
|
261
|
+
|
|
262
|
+
if not path.nodes:
|
|
263
|
+
return paths
|
|
264
|
+
|
|
265
|
+
path = self.strip_terminal_nodes(path)
|
|
266
|
+
node = path.nodes[-1]
|
|
267
|
+
|
|
268
|
+
me: Tree
|
|
269
|
+
parent: Tree
|
|
270
|
+
me, parent = self.tree_by_choices(path)
|
|
271
|
+
|
|
272
|
+
check_parent = True
|
|
273
|
+
if isinstance(me, Tree) and me.data == 'expr' and me.children[1] in ['*', '+']:
|
|
274
|
+
if debug: print(' add expr repeat ', path)
|
|
275
|
+
paths |= self.visit_current(path.with_new_choice(0), debug=debug)
|
|
276
|
+
elif isinstance(me, Tree) and me.data == 'maybe':
|
|
277
|
+
if debug: print(' add maybe repeat ', path)
|
|
278
|
+
paths |= self.visit_current(path.with_new_choice(0), debug=debug)
|
|
279
|
+
|
|
280
|
+
if isinstance(parent, Tree) and parent.data == 'expansion' and (node.choices[-1] + 1) < len(parent.children):
|
|
281
|
+
np = path.with_next_choice()
|
|
282
|
+
if debug: print(' move to next sibling ', np)
|
|
283
|
+
paths |= self.visit_current(np, debug=debug)
|
|
284
|
+
check_parent = False
|
|
285
|
+
|
|
286
|
+
if check_parent:
|
|
287
|
+
if len(node.choices) > 0:
|
|
288
|
+
p = path.drop_last_choice()
|
|
289
|
+
if debug: print(' move up to parent tree', p)
|
|
290
|
+
paths |= self.visit_next(p, debug=debug)
|
|
291
|
+
else:
|
|
292
|
+
path = path.drop_last()
|
|
293
|
+
if debug: print(' move up to parent node', path)
|
|
294
|
+
paths |= self.visit_next(path, debug=debug)
|
|
295
|
+
|
|
296
|
+
return paths
|
|
297
|
+
|
|
298
|
+
def tree_by_choices(self, path: GPath, debug=False):
|
|
299
|
+
debug = False
|
|
300
|
+
|
|
301
|
+
c = f'{path.nodes[-1]}'
|
|
302
|
+
p = f'{path.nodes[-1].drop_last_choice()}'
|
|
303
|
+
if c not in self.trees:
|
|
304
|
+
for key, value in self.trees_by_choices(path, debug=debug).items():
|
|
305
|
+
if not key:
|
|
306
|
+
self.trees[f'{path.token().name}'] = value
|
|
307
|
+
else:
|
|
308
|
+
self.trees[f'{path.token().name}-{key}'] = value
|
|
309
|
+
|
|
310
|
+
return self.trees[c], self.trees[p]
|
|
311
|
+
|
|
312
|
+
def trees_by_choices(self, path_or_tree: Union[GPath, Tree], choices: list[int] = [], depth: int = 0, debug=False):
|
|
313
|
+
if debug: print(f'trees_by_choices({path_or_tree}, {choices})')
|
|
314
|
+
|
|
315
|
+
if isinstance(path_or_tree, GPath):
|
|
316
|
+
n = self.find_last_rule(path_or_tree)
|
|
317
|
+
tree = self.rules[n.name]
|
|
318
|
+
else:
|
|
319
|
+
tree = path_or_tree
|
|
320
|
+
|
|
321
|
+
trees: dict[str, Tree] = {}
|
|
322
|
+
|
|
323
|
+
if isinstance(tree, Token):
|
|
324
|
+
if debug: log('tree', debug, depth)
|
|
325
|
+
pass
|
|
326
|
+
elif isinstance(tree, Terminal):
|
|
327
|
+
if debug: log('terminal', debug, depth)
|
|
328
|
+
|
|
329
|
+
choices_str = '-'.join([f"{c}" for c in choices])
|
|
330
|
+
trees[choices_str] = tree
|
|
331
|
+
|
|
332
|
+
pass
|
|
333
|
+
elif isinstance(tree, NonTerminal):
|
|
334
|
+
if debug: log('non-terminal', debug, depth)
|
|
335
|
+
pass
|
|
336
|
+
elif isinstance(tree, tuple):
|
|
337
|
+
if debug: log('tuple', debug, depth)
|
|
338
|
+
trees |= self.trees_by_choices(tree[0], choices, depth=depth, debug=debug)
|
|
339
|
+
elif tree.data in ['expr', 'maybe']:
|
|
340
|
+
if debug: log('expr', debug, depth)
|
|
341
|
+
choices_str = '-'.join([f"{c}" for c in choices])
|
|
342
|
+
trees[choices_str] = tree
|
|
343
|
+
|
|
344
|
+
for child in tree.children[:1]:
|
|
345
|
+
trees |= self.trees_by_choices(child, choices + [0], depth=depth+1, debug=debug)
|
|
346
|
+
elif tree.data == 'value':
|
|
347
|
+
if debug: log('value', debug, depth)
|
|
348
|
+
|
|
349
|
+
choices_str = '-'.join([f"{c}" for c in choices])
|
|
350
|
+
trees[choices_str] = tree
|
|
351
|
+
|
|
352
|
+
for child in tree.children:
|
|
353
|
+
trees |= self.trees_by_choices(child, choices, depth=depth+1, debug=debug)
|
|
354
|
+
elif tree.data == 'literal':
|
|
355
|
+
if debug: log('literal', debug, depth)
|
|
356
|
+
|
|
357
|
+
choices_str = '-'.join([f"{c}" for c in choices])
|
|
358
|
+
trees[choices_str] = tree
|
|
359
|
+
|
|
360
|
+
for child in tree.children:
|
|
361
|
+
trees |= self.trees_by_choices(child, choices, depth=depth+1, debug=debug)
|
|
362
|
+
elif tree.data == 'expansions':
|
|
363
|
+
if debug: log('expansions', debug, depth)
|
|
364
|
+
|
|
365
|
+
choices_str = '-'.join([f"{c}" for c in choices])
|
|
366
|
+
trees[choices_str] = tree
|
|
367
|
+
for i, child in enumerate(tree.children):
|
|
368
|
+
trees |= self.trees_by_choices(child, choices + [i], depth=depth+1, debug=debug)
|
|
369
|
+
elif tree.data == 'expansion':
|
|
370
|
+
if debug: log('expansion', debug, depth)
|
|
371
|
+
|
|
372
|
+
choices_str = '-'.join([f"{c}" for c in choices])
|
|
373
|
+
trees[choices_str] = tree
|
|
374
|
+
for i, child in enumerate(tree.children):
|
|
375
|
+
trees |= self.trees_by_choices(child, choices + [i], depth=depth+1, debug=debug)
|
|
376
|
+
elif tree.data == 'alias':
|
|
377
|
+
if debug: log('alias', debug, depth)
|
|
378
|
+
trees |= self.trees_by_choices(tree.children[0], choices, depth=depth, debug=debug)
|
|
379
|
+
else:
|
|
380
|
+
if debug: log('else', debug, depth)
|
|
381
|
+
|
|
382
|
+
return trees
|
|
383
|
+
|
|
384
|
+
def strip_terminal_nodes(self, path: GPath):
|
|
385
|
+
while path.nodes[-1].name not in self.rules:
|
|
386
|
+
path = path.drop_last()
|
|
387
|
+
|
|
388
|
+
return path
|
|
389
|
+
|
|
390
|
+
def non_terminal_node_name_path(self, path: GPath):
|
|
391
|
+
return self.strip_terminal_nodes(path).name_path()
|
|
392
|
+
|
|
393
|
+
def find_last_rule(self, path: GPath):
|
|
394
|
+
for n in reversed(path.nodes):
|
|
395
|
+
if n.name in self.rules:
|
|
396
|
+
return n
|
|
397
|
+
|
|
398
|
+
return None
|
|
399
|
+
|
|
400
|
+
def build_path(p: str):
|
|
401
|
+
nodes = []
|
|
402
|
+
for n in p.split('.'):
|
|
403
|
+
name_n_choices = n.split('-')
|
|
404
|
+
nodes.append(GNode(name_n_choices[0], choices=[int(c) for c in name_n_choices[1:]]))
|
|
405
|
+
|
|
406
|
+
return GPath(nodes, False)
|
|
407
|
+
|
|
408
|
+
def token_type_for_value(self, token):
|
|
409
|
+
for t in reversed(self.parser.terminals):
|
|
410
|
+
td: TerminalDef = t
|
|
411
|
+
td_value = td.pattern.raw
|
|
412
|
+
if td.pattern.type == 'str':
|
|
413
|
+
lower = False
|
|
414
|
+
if td_value.endswith('i'):
|
|
415
|
+
td_value = td_value[:-1]
|
|
416
|
+
if len(td_value) > 1 and td_value.startswith('"') and td_value.endswith('"'):
|
|
417
|
+
td_value = td_value[1:-1].lower()
|
|
418
|
+
lower = True
|
|
419
|
+
elif len(td_value) > 1 and td_value.startswith('"') and td_value.endswith('"'):
|
|
420
|
+
td_value = td_value[1:-1]
|
|
421
|
+
td_value = td_value.encode('utf-8').decode('unicode-escape')
|
|
422
|
+
|
|
423
|
+
t = token
|
|
424
|
+
if lower:
|
|
425
|
+
t = t.lower()
|
|
426
|
+
if t == td_value:
|
|
427
|
+
return td.name
|
|
428
|
+
|
|
429
|
+
for t in reversed(self.parser.terminals):
|
|
430
|
+
td: TerminalDef = t
|
|
431
|
+
td_value = td.pattern.raw
|
|
432
|
+
if td.pattern.type != 'str' and re.fullmatch(td.pattern.to_regexp(), token):
|
|
433
|
+
return td.name
|
|
434
|
+
|
|
435
|
+
raise Exception('cannot resolve string to a token')
|
|
436
|
+
|
|
437
|
+
def token_type_from_raw(self, raw_token):
|
|
438
|
+
td: TerminalDef = None
|
|
439
|
+
for t in reversed(self.parser.terminals):
|
|
440
|
+
if t.pattern.raw == raw_token:
|
|
441
|
+
td = t
|
|
442
|
+
return td.name
|
|
443
|
+
|
|
444
|
+
return self.token_type_for_value(raw_token.strip('"'))
|
|
445
|
+
|
|
446
|
+
def token_value_from_type(self, token_type: str):
|
|
447
|
+
value = None
|
|
448
|
+
|
|
449
|
+
# print('token_value', token_name)
|
|
450
|
+
|
|
451
|
+
if not self._token_values_by_type:
|
|
452
|
+
for t in self.parser.terminals:
|
|
453
|
+
td: TerminalDef = t
|
|
454
|
+
if td.pattern.type == 'str':
|
|
455
|
+
value = td.pattern.raw
|
|
456
|
+
if value.endswith('i'):
|
|
457
|
+
value = value[:-1]
|
|
458
|
+
if len(value) > 1 and value.startswith('"') and value.endswith('"'):
|
|
459
|
+
value = value[1:-1].lower()
|
|
460
|
+
elif len(value) > 1 and value.startswith('"') and value.endswith('"'):
|
|
461
|
+
value = value[1:-1]
|
|
462
|
+
|
|
463
|
+
value = value.encode('utf-8').decode('unicode-escape')
|
|
464
|
+
elif td.pattern.type == 're' and td.pattern.value == '[0-9]':
|
|
465
|
+
value = '`DIGIT'
|
|
466
|
+
else:
|
|
467
|
+
value = f'`{td.name}'
|
|
468
|
+
|
|
469
|
+
self._token_values_by_type[td.name] = value
|
|
470
|
+
|
|
471
|
+
return self._token_values_by_type[token_type]
|
|
472
|
+
|
|
473
|
+
def next_terminals(self, s: str, debug=False, print_terminals=False, context: dict[any, any] = {}):
|
|
474
|
+
terminals = set()
|
|
475
|
+
|
|
476
|
+
ps = 'start'
|
|
477
|
+
ps = self.find_next_terminals(ps, debug=debug)
|
|
478
|
+
ps0: set[GPath] = None
|
|
479
|
+
last_id_value = None
|
|
480
|
+
|
|
481
|
+
state: str = ''
|
|
482
|
+
|
|
483
|
+
def emit(ps: set[GPath], token_type: str, token_value: str):
|
|
484
|
+
ps0 = ps
|
|
485
|
+
ps = self.choose(ps, token_type, token_value)
|
|
486
|
+
ps = self.find_next_terminals(ps, debug=debug)
|
|
487
|
+
|
|
488
|
+
return ps0, ps
|
|
489
|
+
|
|
490
|
+
token_type_for_a = self.token_type_for_value('a')
|
|
491
|
+
token_type: str = None
|
|
492
|
+
for token in self.parser.lex(s):
|
|
493
|
+
token_type = token.type
|
|
494
|
+
if token_type not in ['IDENTIFIER']:
|
|
495
|
+
token_type = self.token_type_for_value(token)
|
|
496
|
+
|
|
497
|
+
if state == '':
|
|
498
|
+
if token_type == 'QUOTE':
|
|
499
|
+
state = 'quote'
|
|
500
|
+
elif token_type == 'DBLQUOTE':
|
|
501
|
+
state = 'dblquote'
|
|
502
|
+
elif state == 'quote':
|
|
503
|
+
if token_type == 'QUOTE':
|
|
504
|
+
state = 'quote_in_quote'
|
|
505
|
+
elif state == 'dblquote':
|
|
506
|
+
if token_type == 'DBLQUOTE':
|
|
507
|
+
state = 'dblquote_in_dblquote'
|
|
508
|
+
elif state == 'quote_in_quote':
|
|
509
|
+
if token_type == 'QUOTE':
|
|
510
|
+
token_type = token_type_for_a
|
|
511
|
+
state = 'quote'
|
|
512
|
+
else:
|
|
513
|
+
ps0, ps = emit(ps, 'QUOTE', '\'')
|
|
514
|
+
state = ''
|
|
515
|
+
elif state == 'dblquote_in_dblquote':
|
|
516
|
+
if token_type == 'DBLQUOTE':
|
|
517
|
+
token_type = token_type_for_a
|
|
518
|
+
state = 'dblquote'
|
|
519
|
+
else:
|
|
520
|
+
ps0, ps = emit(ps, 'DBLQUOTE', '"')
|
|
521
|
+
state = ''
|
|
522
|
+
|
|
523
|
+
# inside quotes, a word comes back as an IDENTIFIER while escaped quote comes back as their specific anonymous terminals
|
|
524
|
+
if token_type == 'IDENTIFIER' and state in ['quote', 'dblquote']:
|
|
525
|
+
for t1 in token:
|
|
526
|
+
token_type1 = self.token_type_for_value(t1)
|
|
527
|
+
|
|
528
|
+
ps0, ps = emit(ps, token_type1, t1)
|
|
529
|
+
elif state not in ['quote_in_quote', 'dblquote_in_dblquote']:
|
|
530
|
+
ps0, ps = emit(ps, token_type, token)
|
|
531
|
+
|
|
532
|
+
if token_type == 'IDENTIFIER':
|
|
533
|
+
last_id_value = token.value
|
|
534
|
+
context['last-id'] = last_id_value
|
|
535
|
+
|
|
536
|
+
# emit left-overs
|
|
537
|
+
if state == 'quote_in_quote':
|
|
538
|
+
ps0, ps = emit(ps, 'QUOTE', '\'')
|
|
539
|
+
state = ''
|
|
540
|
+
elif state == 'dblquote_in_dblquote':
|
|
541
|
+
ps0, ps = emit(ps, 'DBLQUOTE', '"')
|
|
542
|
+
state = ''
|
|
543
|
+
|
|
544
|
+
if token_type:
|
|
545
|
+
last_value: str = self.token_value_from_type(token_type)
|
|
546
|
+
if not s.endswith(' ') and self.isalpha(last_value):
|
|
547
|
+
ps = ps0
|
|
548
|
+
|
|
549
|
+
for p in ps:
|
|
550
|
+
name_path = self.non_terminal_node_name_path(p)
|
|
551
|
+
|
|
552
|
+
translated = False
|
|
553
|
+
for k, v in self.contexts_by_path.items():
|
|
554
|
+
if k in name_path:
|
|
555
|
+
translated = True
|
|
556
|
+
terminals.add(f'`{v}')
|
|
557
|
+
|
|
558
|
+
if not translated:
|
|
559
|
+
next_value: str = self.token_value_from_type(f'{p.token()}')
|
|
560
|
+
|
|
561
|
+
# TODO hack to pass in all hosts to repl completer
|
|
562
|
+
if not s and next_value == '@':
|
|
563
|
+
terminals.add('`hosts')
|
|
564
|
+
else:
|
|
565
|
+
terminals.add(next_value)
|
|
566
|
+
|
|
567
|
+
if print_terminals:
|
|
568
|
+
print(terminals)
|
|
569
|
+
|
|
570
|
+
return terminals
|
|
571
|
+
|
|
572
|
+
def isalpha(self, value: str):
|
|
573
|
+
return self.contains_letter(value) or value == '*' or self.is_non_terminal_value(value)
|
|
574
|
+
|
|
575
|
+
def is_non_terminal_value(self, value: str):
|
|
576
|
+
return len(value) > 1 and value.startswith('`')
|
|
577
|
+
|
|
578
|
+
def contains_letter(self, s):
|
|
579
|
+
return any(c.isalpha() for c in s)
|
|
580
|
+
|
|
581
|
+
def parse(self, s: str, debug=False, show_terminals=False):
|
|
582
|
+
ps = 'start'
|
|
583
|
+
ps = self.find_next_terminals(ps, debug=debug)
|
|
584
|
+
|
|
585
|
+
for token in self.parser.lex(s):
|
|
586
|
+
token_name = self.token_type_for_value(token)
|
|
587
|
+
|
|
588
|
+
ps = self.choose(ps, token_name)
|
|
589
|
+
ps = self.find_next_terminals(ps, debug=debug)
|
|
590
|
+
|
|
591
|
+
terminals = {p.terminal().strip('"') for p in ps}
|
|
592
|
+
|
|
593
|
+
return terminals
|
|
594
|
+
|
|
595
|
+
def choose(self, paths: list[GPath], token_name: str, token_value: str):
|
|
596
|
+
if LarkParser.show_returns: print(f'\n{token_name}({token_value})\t-----------------------------')
|
|
597
|
+
|
|
598
|
+
ps = set()
|
|
599
|
+
for p in paths:
|
|
600
|
+
# print('choose', token, 'from', p.token())
|
|
601
|
+
if f'{p.token()}' == token_name:
|
|
602
|
+
ps.add(p)
|
|
603
|
+
|
|
604
|
+
return ps
|