|
1 | 1 | from __future__ import annotations
|
2 | 2 |
|
3 |
| -import json |
4 | 3 | import logging
|
5 | 4 | import sys
|
6 |
| -from argparse import ArgumentParser, FileType |
7 |
| -from enum import Enum |
8 |
| -from pathlib import Path |
9 | 5 | from typing import TYPE_CHECKING
|
10 | 6 |
|
11 |
| -from graphviz import Digraph |
12 |
| - |
13 |
| -from pyk.kast.inner import KInner |
14 |
| -from pyk.kore.rpc import ExecuteResult |
15 |
| - |
16 |
| -from .cli.args import KCLIArgs |
17 |
| -from .cli.utils import LOG_FORMAT, dir_path, loglevel |
18 |
| -from .coverage import get_rule_by_id, strip_coverage_logger |
19 |
| -from .cterm import CTerm |
20 |
| -from .kast.manip import ( |
21 |
| - flatten_label, |
22 |
| - minimize_rule, |
23 |
| - minimize_term, |
24 |
| - propagate_up_constraints, |
25 |
| - remove_source_map, |
26 |
| - split_config_and_constraints, |
| 7 | +from .cli.args import LoggingOptions |
| 8 | +from .cli.cli import CLI |
| 9 | +from .cli.pyk import ( |
| 10 | + CoverageCommand, |
| 11 | + GraphImportsCommand, |
| 12 | + JsonToKoreCommand, |
| 13 | + KoreToJsonCommand, |
| 14 | + PrintCommand, |
| 15 | + ProveCommand, |
| 16 | + RPCKastCommand, |
| 17 | + RPCPrintCommand, |
27 | 18 | )
|
28 |
| -from .kast.outer import read_kast_definition |
29 |
| -from .kast.pretty import PrettyPrinter |
30 |
| -from .kore.parser import KoreParser |
31 |
| -from .kore.rpc import StopReason |
32 |
| -from .kore.syntax import Pattern, kore_term |
33 |
| -from .ktool.kprint import KPrint |
34 |
| -from .ktool.kprove import KProve |
35 |
| -from .prelude.k import GENERATED_TOP_CELL |
36 |
| -from .prelude.ml import is_top, mlAnd, mlOr |
| 19 | +from .cli.utils import LOG_FORMAT, loglevel |
37 | 20 |
|
38 | 21 | if TYPE_CHECKING:
|
39 |
| - from argparse import Namespace |
40 |
| - from typing import Any, Final |
| 22 | + from typing import Final |
41 | 23 |
|
42 | 24 |
|
43 | 25 | _LOGGER: Final = logging.getLogger(__name__)
|
44 | 26 |
|
45 | 27 |
|
46 |
| -class PrintInput(Enum): |
47 |
| - KORE_JSON = 'kore-json' |
48 |
| - KAST_JSON = 'kast-json' |
49 |
| - |
50 |
| - |
51 | 28 | def main() -> None:
|
52 | 29 | # KAST terms can end up nested quite deeply, because of the various assoc operators (eg. _Map_, _Set_, ...).
|
53 | 30 | # Most pyk operations are defined recursively, meaning you get a callstack the same depth as the term.
|
54 | 31 | # This change makes it so that in most cases, by default, pyk doesn't run out of stack space.
|
55 | 32 | sys.setrecursionlimit(10**7)
|
56 | 33 |
|
57 |
| - cli_parser = create_argument_parser() |
58 |
| - args = cli_parser.parse_args() |
59 |
| - |
60 |
| - logging.basicConfig(level=loglevel(args), format=LOG_FORMAT) |
61 |
| - |
62 |
| - executor_name = 'exec_' + args.command.lower().replace('-', '_') |
63 |
| - if executor_name not in globals(): |
64 |
| - raise AssertionError(f'Unimplemented command: {args.command}') |
65 |
| - |
66 |
| - execute = globals()[executor_name] |
67 |
| - execute(args) |
68 |
| - |
69 |
| - |
70 |
| -def exec_print(args: Namespace) -> None: |
71 |
| - kompiled_dir: Path = args.definition_dir |
72 |
| - printer = KPrint(kompiled_dir) |
73 |
| - if args.input == PrintInput.KORE_JSON: |
74 |
| - _LOGGER.info(f'Reading Kore JSON from file: {args.term.name}') |
75 |
| - kore = Pattern.from_json(args.term.read()) |
76 |
| - term = printer.kore_to_kast(kore) |
77 |
| - else: |
78 |
| - _LOGGER.info(f'Reading Kast JSON from file: {args.term.name}') |
79 |
| - term = KInner.from_json(args.term.read()) |
80 |
| - if is_top(term): |
81 |
| - args.output_file.write(printer.pretty_print(term)) |
82 |
| - _LOGGER.info(f'Wrote file: {args.output_file.name}') |
83 |
| - else: |
84 |
| - if args.minimize: |
85 |
| - if args.omit_labels != '' and args.keep_cells != '': |
86 |
| - raise ValueError('You cannot use both --omit-labels and --keep-cells.') |
87 |
| - |
88 |
| - abstract_labels = args.omit_labels.split(',') if args.omit_labels != '' else [] |
89 |
| - keep_cells = args.keep_cells.split(',') if args.keep_cells != '' else [] |
90 |
| - minimized_disjuncts = [] |
91 |
| - |
92 |
| - for disjunct in flatten_label('#Or', term): |
93 |
| - try: |
94 |
| - minimized = minimize_term(disjunct, abstract_labels=abstract_labels, keep_cells=keep_cells) |
95 |
| - config, constraint = split_config_and_constraints(minimized) |
96 |
| - except ValueError as err: |
97 |
| - raise ValueError('The minimized term does not contain a config cell.') from err |
98 |
| - |
99 |
| - if not is_top(constraint): |
100 |
| - minimized_disjuncts.append(mlAnd([config, constraint], sort=GENERATED_TOP_CELL)) |
101 |
| - else: |
102 |
| - minimized_disjuncts.append(config) |
103 |
| - term = propagate_up_constraints(mlOr(minimized_disjuncts, sort=GENERATED_TOP_CELL)) |
104 |
| - |
105 |
| - args.output_file.write(printer.pretty_print(term)) |
106 |
| - _LOGGER.info(f'Wrote file: {args.output_file.name}') |
107 |
| - |
108 |
| - |
109 |
| -def exec_rpc_print(args: Namespace) -> None: |
110 |
| - kompiled_dir: Path = args.definition_dir |
111 |
| - printer = KPrint(kompiled_dir) |
112 |
| - input_dict = json.loads(args.input_file.read()) |
113 |
| - output_buffer = [] |
114 |
| - |
115 |
| - def pretty_print_request(request_params: dict[str, Any]) -> list[str]: |
116 |
| - output_buffer = [] |
117 |
| - non_state_keys = set(request_params.keys()).difference(['state']) |
118 |
| - for key in non_state_keys: |
119 |
| - output_buffer.append(f'{key}: {request_params[key]}') |
120 |
| - state = CTerm.from_kast(printer.kore_to_kast(kore_term(request_params['state']))) |
121 |
| - output_buffer.append('State:') |
122 |
| - output_buffer.append(printer.pretty_print(state.kast, sort_collections=True)) |
123 |
| - return output_buffer |
124 |
| - |
125 |
| - def pretty_print_execute_response(execute_result: ExecuteResult) -> list[str]: |
126 |
| - output_buffer = [] |
127 |
| - output_buffer.append(f'Depth: {execute_result.depth}') |
128 |
| - output_buffer.append(f'Stop reason: {execute_result.reason.value}') |
129 |
| - if execute_result.reason == StopReason.TERMINAL_RULE or execute_result.reason == StopReason.CUT_POINT_RULE: |
130 |
| - output_buffer.append(f'Stop rule: {execute_result.rule}') |
131 |
| - output_buffer.append( |
132 |
| - f'Number of next states: {len(execute_result.next_states) if execute_result.next_states is not None else 0}' |
133 |
| - ) |
134 |
| - state = CTerm.from_kast(printer.kore_to_kast(execute_result.state.kore)) |
135 |
| - output_buffer.append('State:') |
136 |
| - output_buffer.append(printer.pretty_print(state.kast, sort_collections=True)) |
137 |
| - if execute_result.next_states is not None: |
138 |
| - next_states = [CTerm.from_kast(printer.kore_to_kast(s.kore)) for s in execute_result.next_states] |
139 |
| - for i, s in enumerate(next_states): |
140 |
| - output_buffer.append(f'Next state #{i}:') |
141 |
| - output_buffer.append(printer.pretty_print(s.kast, sort_collections=True)) |
142 |
| - return output_buffer |
143 |
| - |
144 |
| - try: |
145 |
| - if 'method' in input_dict: |
146 |
| - output_buffer.append('JSON RPC request') |
147 |
| - output_buffer.append(f'id: {input_dict["id"]}') |
148 |
| - output_buffer.append(f'Method: {input_dict["method"]}') |
149 |
| - try: |
150 |
| - if 'state' in input_dict['params']: |
151 |
| - output_buffer += pretty_print_request(input_dict['params']) |
152 |
| - else: # this is an "add-module" request, skip trying to print state |
153 |
| - for key in input_dict['params'].keys(): |
154 |
| - output_buffer.append(f'{key}: {input_dict["params"][key]}') |
155 |
| - except KeyError as e: |
156 |
| - _LOGGER.critical(f'Could not find key {str(e)} in input JSON file') |
157 |
| - exit(1) |
158 |
| - else: |
159 |
| - if not 'result' in input_dict: |
160 |
| - _LOGGER.critical('The input is neither a request not a resonse') |
161 |
| - exit(1) |
162 |
| - output_buffer.append('JSON RPC Response') |
163 |
| - output_buffer.append(f'id: {input_dict["id"]}') |
164 |
| - if list(input_dict['result'].keys()) == ['state']: # this is a "simplify" response |
165 |
| - output_buffer.append('Method: simplify') |
166 |
| - state = CTerm.from_kast(printer.kore_to_kast(kore_term(input_dict['result']['state']))) |
167 |
| - output_buffer.append('State:') |
168 |
| - output_buffer.append(printer.pretty_print(state.kast, sort_collections=True)) |
169 |
| - elif list(input_dict['result'].keys()) == ['module']: # this is an "add-module" response |
170 |
| - output_buffer.append('Method: add-module') |
171 |
| - output_buffer.append('Module:') |
172 |
| - output_buffer.append(input_dict['result']['module']) |
173 |
| - else: |
174 |
| - try: # assume it is an "execute" response |
175 |
| - output_buffer.append('Method: execute') |
176 |
| - execute_result = ExecuteResult.from_dict(input_dict['result']) |
177 |
| - output_buffer += pretty_print_execute_response(execute_result) |
178 |
| - except KeyError as e: |
179 |
| - _LOGGER.critical(f'Could not find key {str(e)} in input JSON file') |
180 |
| - exit(1) |
181 |
| - if args.output_file is not None: |
182 |
| - args.output_file.write('\n'.join(output_buffer)) |
183 |
| - else: |
184 |
| - print('\n'.join(output_buffer)) |
185 |
| - except ValueError as e: |
186 |
| - # shorten and print the error message in case kore_to_kast throws ValueError |
187 |
| - _LOGGER.critical(str(e)[:200]) |
188 |
| - exit(1) |
189 |
| - |
190 |
| - |
191 |
| -def exec_rpc_kast(args: Namespace) -> None: |
192 |
| - """ |
193 |
| - Convert an 'execute' JSON RPC response to a new 'execute' or 'simplify' request, |
194 |
| - copying parameters from a reference request. |
195 |
| - """ |
196 |
| - reference_request = json.loads(args.reference_request_file.read()) |
197 |
| - input_dict = json.loads(args.response_file.read()) |
198 |
| - execute_result = ExecuteResult.from_dict(input_dict['result']) |
199 |
| - non_state_keys = set(reference_request['params'].keys()).difference(['state']) |
200 |
| - request_params = {} |
201 |
| - for key in non_state_keys: |
202 |
| - request_params[key] = reference_request['params'][key] |
203 |
| - request_params['state'] = {'format': 'KORE', 'version': 1, 'term': execute_result.state.kore.dict} |
204 |
| - request = { |
205 |
| - 'jsonrpc': reference_request['jsonrpc'], |
206 |
| - 'id': reference_request['id'], |
207 |
| - 'method': reference_request['method'], |
208 |
| - 'params': request_params, |
209 |
| - } |
210 |
| - args.output_file.write(json.dumps(request)) |
211 |
| - |
212 |
| - |
213 |
| -def exec_prove(args: Namespace) -> None: |
214 |
| - kompiled_dir: Path = args.definition_dir |
215 |
| - kprover = KProve(kompiled_dir, args.main_file) |
216 |
| - final_state = kprover.prove(Path(args.spec_file), spec_module_name=args.spec_module, args=args.kArgs) |
217 |
| - args.output_file.write(json.dumps(mlOr([state.kast for state in final_state]).to_dict())) |
218 |
| - _LOGGER.info(f'Wrote file: {args.output_file.name}') |
219 |
| - |
220 |
| - |
221 |
| -def exec_graph_imports(args: Namespace) -> None: |
222 |
| - kompiled_dir: Path = args.definition_dir |
223 |
| - kprinter = KPrint(kompiled_dir) |
224 |
| - definition = kprinter.definition |
225 |
| - import_graph = Digraph() |
226 |
| - graph_file = kompiled_dir / 'import-graph' |
227 |
| - for module in definition.modules: |
228 |
| - module_name = module.name |
229 |
| - import_graph.node(module_name) |
230 |
| - for module_import in module.imports: |
231 |
| - import_graph.edge(module_name, module_import.name) |
232 |
| - import_graph.render(graph_file) |
233 |
| - _LOGGER.info(f'Wrote file: {graph_file}') |
234 |
| - |
235 |
| - |
236 |
| -def exec_coverage(args: Namespace) -> None: |
237 |
| - kompiled_dir: Path = args.definition_dir |
238 |
| - definition = remove_source_map(read_kast_definition(kompiled_dir / 'compiled.json')) |
239 |
| - pretty_printer = PrettyPrinter(definition) |
240 |
| - for rid in args.coverage_file: |
241 |
| - rule = minimize_rule(strip_coverage_logger(get_rule_by_id(definition, rid.strip()))) |
242 |
| - args.output.write('\n\n') |
243 |
| - args.output.write('Rule: ' + rid.strip()) |
244 |
| - args.output.write('\nUnparsed:\n') |
245 |
| - args.output.write(pretty_printer.print(rule)) |
246 |
| - _LOGGER.info(f'Wrote file: {args.output.name}') |
247 |
| - |
248 |
| - |
249 |
| -def exec_kore_to_json(args: Namespace) -> None: |
250 |
| - text = sys.stdin.read() |
251 |
| - kore = KoreParser(text).pattern() |
252 |
| - print(kore.json) |
253 |
| - |
254 |
| - |
255 |
| -def exec_json_to_kore(args: dict[str, Any]) -> None: |
256 |
| - text = sys.stdin.read() |
257 |
| - kore = Pattern.from_json(text) |
258 |
| - kore.write(sys.stdout) |
259 |
| - sys.stdout.write('\n') |
260 |
| - |
261 |
| - |
262 |
| -def create_argument_parser() -> ArgumentParser: |
263 |
| - k_cli_args = KCLIArgs() |
264 |
| - |
265 |
| - definition_args = ArgumentParser(add_help=False) |
266 |
| - definition_args.add_argument('definition_dir', type=dir_path, help='Path to definition directory.') |
267 |
| - |
268 |
| - pyk_args = ArgumentParser() |
269 |
| - pyk_args_command = pyk_args.add_subparsers(dest='command', required=True) |
270 |
| - |
271 |
| - print_args = pyk_args_command.add_parser( |
272 |
| - 'print', |
273 |
| - help='Pretty print a term.', |
274 |
| - parents=[k_cli_args.logging_args, definition_args, k_cli_args.display_args], |
275 |
| - ) |
276 |
| - print_args.add_argument('term', type=FileType('r'), help='Input term (in format specified with --input).') |
277 |
| - print_args.add_argument('--input', default=PrintInput.KAST_JSON, type=PrintInput, choices=list(PrintInput)) |
278 |
| - print_args.add_argument('--omit-labels', default='', nargs='?', help='List of labels to omit from output.') |
279 |
| - print_args.add_argument( |
280 |
| - '--keep-cells', default='', nargs='?', help='List of cells with primitive values to keep in output.' |
281 |
| - ) |
282 |
| - print_args.add_argument('--output-file', type=FileType('w'), default='-') |
283 |
| - |
284 |
| - rpc_print_args = pyk_args_command.add_parser( |
285 |
| - 'rpc-print', |
286 |
| - help='Pretty-print an RPC request/response', |
287 |
| - parents=[k_cli_args.logging_args, definition_args], |
288 |
| - ) |
289 |
| - rpc_print_args.add_argument( |
290 |
| - 'input_file', |
291 |
| - type=FileType('r'), |
292 |
| - help='An input file containing the JSON RPC request or response with KoreJSON payload.', |
293 |
| - ) |
294 |
| - rpc_print_args.add_argument('--output-file', type=FileType('w'), default='-') |
295 |
| - |
296 |
| - rpc_kast_args = pyk_args_command.add_parser( |
297 |
| - 'rpc-kast', |
298 |
| - help='Convert an "execute" JSON RPC response to a new "execute" or "simplify" request, copying parameters from a reference request.', |
299 |
| - parents=[k_cli_args.logging_args], |
300 |
| - ) |
301 |
| - rpc_kast_args.add_argument( |
302 |
| - 'reference_request_file', |
303 |
| - type=FileType('r'), |
304 |
| - help='An input file containing a JSON RPC request to server as a reference for the new request.', |
305 |
| - ) |
306 |
| - rpc_kast_args.add_argument( |
307 |
| - 'response_file', |
308 |
| - type=FileType('r'), |
309 |
| - help='An input file containing a JSON RPC response with KoreJSON payload.', |
310 |
| - ) |
311 |
| - rpc_kast_args.add_argument('--output-file', type=FileType('w'), default='-') |
312 |
| - |
313 |
| - prove_args = pyk_args_command.add_parser( |
314 |
| - 'prove', |
315 |
| - help='Prove an input specification (using kprovex).', |
316 |
| - parents=[k_cli_args.logging_args, definition_args], |
317 |
| - ) |
318 |
| - prove_args.add_argument('main_file', type=str, help='Main file used for kompilation.') |
319 |
| - prove_args.add_argument('spec_file', type=str, help='File with the specification module.') |
320 |
| - prove_args.add_argument('spec_module', type=str, help='Module with claims to be proven.') |
321 |
| - prove_args.add_argument('--output-file', type=FileType('w'), default='-') |
322 |
| - prove_args.add_argument('kArgs', nargs='*', help='Arguments to pass through to K invocation.') |
323 |
| - |
324 |
| - pyk_args_command.add_parser( |
325 |
| - 'graph-imports', |
326 |
| - help='Graph the imports of a given definition.', |
327 |
| - parents=[k_cli_args.logging_args, definition_args], |
328 |
| - ) |
329 |
| - |
330 |
| - coverage_args = pyk_args_command.add_parser( |
331 |
| - 'coverage', |
332 |
| - help='Convert coverage file to human readable log.', |
333 |
| - parents=[k_cli_args.logging_args, definition_args], |
334 |
| - ) |
335 |
| - coverage_args.add_argument('coverage_file', type=FileType('r'), help='Coverage file to build log for.') |
336 |
| - coverage_args.add_argument('-o', '--output', type=FileType('w'), default='-') |
337 |
| - |
338 |
| - pyk_args_command.add_parser('kore-to-json', help='Convert textual KORE to JSON', parents=[k_cli_args.logging_args]) |
339 |
| - |
340 |
| - pyk_args_command.add_parser('json-to-kore', help='Convert JSON to textual KORE', parents=[k_cli_args.logging_args]) |
341 |
| - |
342 |
| - return pyk_args |
| 34 | + cli = CLI( |
| 35 | + [ |
| 36 | + CoverageCommand, |
| 37 | + GraphImportsCommand, |
| 38 | + JsonToKoreCommand, |
| 39 | + KoreToJsonCommand, |
| 40 | + PrintCommand, |
| 41 | + ProveCommand, |
| 42 | + RPCKastCommand, |
| 43 | + RPCPrintCommand, |
| 44 | + ] |
| 45 | + ) |
| 46 | + command = cli.get_command() |
| 47 | + assert isinstance(command, LoggingOptions) |
| 48 | + logging.basicConfig(level=loglevel(command), format=LOG_FORMAT) |
| 49 | + command.exec() |
343 | 50 |
|
344 | 51 |
|
345 | 52 | if __name__ == '__main__':
|
|
0 commit comments