Commit 08354552 authored by Christophe Benz's avatar Christophe Benz

Add transpilation script

parent 526f40a4
# Calculateur Impôts en Python
Ce projet utilise le langage Python 3.
## Utilisation
Exemple :
......@@ -7,3 +9,13 @@ Exemple :
```
$ python3 calculateur_impots/scripts/calculate.py PPE_SALAVDEFV
```
## Transpilation de l'AST JSON en Python
> Transpiler signifie compiler vers un autre langage de programmation.
Cette étape n'est utile que lorsque les fichiers JSON définissant l'AST changent.
```
$ python3 calculateur_impots/scripts/json_ast_to_python.py /path/to/m-language-parser/json
```
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Convert a JSON AST file to Python source code.
"""
import argparse
import itertools
import json
import logging
import os
import pprint
import sys
import textwrap
# Globals
args = None
script_name = os.path.splitext(os.path.basename(__file__))[0]
log = logging.getLogger(script_name)
script_dir_path = os.path.dirname(os.path.abspath(__file__))
generated_dir_path = os.path.abspath(os.path.join(script_dir_path, '..', 'generated'))
# Source code helper functions
def read_ast_json_file(json_file_path):
with open(json_file_path) as json_file:
json_str = json_file.read()
nodes = json.loads(json_str)
return nodes
def value_to_python_source(value):
return pprint.pformat(value, indent=4, width=120)
def write_source(file_name, source):
header = """\
# -*- coding: utf-8 -*-
# WARNING: This file is automatically generated by a script. No not modify it by hand!
"""
global args
file_path = os.path.join(generated_dir_path, file_name)
with open(file_path, 'w') as output_file:
output_file.write(header + source)
log.info('Output file "{}" was written with success.'.format(file_path))
# Iterators
def merge(*iterables):
for values in itertools.zip_longest(*iterables, fillvalue=UnboundLocalError):
for index, value in enumerate(values):
if value != UnboundLocalError:
yield index, value
# AST navigation helpers
def find_node(node, type, out):
assert isinstance(out, list), out
if isinstance(node, dict):
if node['type'] == type:
out.append(node)
else:
find_node(list(node.values()), type, out)
elif isinstance(node, list):
for child_node in node:
if isinstance(child_node, (list, dict)):
find_node(child_node, type, out)
# Generic transpilation function
def infix_expression_to_python_source(node, operators={}):
tokens = (
node_to_python_source(operand_or_operator)
if index == 0
else operators.get(operand_or_operator, operand_or_operator)
for index, operand_or_operator in merge(node['operands'], node['operators'])
)
return ' '.join(map(str, tokens))
deep_level = 0
def node_to_python_source(node):
global deep_level
transpilation_function_name = node['type'] + '_to_python_source'
if transpilation_function_name not in globals():
error_message = '"def {}(node):" is not defined, node = {}'.format(
transpilation_function_name,
pprint.pformat(node),
)
raise NotImplementedError(error_message)
node_str = textwrap.indent(
pprint.pformat(node),
prefix='>' + ' ' * (deep_level * 4 + len('DEBUG:' + script_name) + len(transpilation_function_name) + 1),
)[1:].lstrip()
log.debug('{}{}({})'.format(' ' * deep_level * 4, transpilation_function_name, node_str))
transpilation_function = globals()[transpilation_function_name]
deep_level += 1
source = transpilation_function(node)
source = str(source)
deep_level -= 1
log.debug('{}=> {}'.format(' ' * deep_level * 4, textwrap.indent(source, prefix='> ')[1:].lstrip()))
assert deep_level >= 0, deep_level
return source
# Specific transpilation functions
def boolean_expression_to_python_source(node):
return infix_expression_to_python_source(node, operators={'et': 'and', 'ou': 'or'})
def comparaison_to_python_source(node):
return '{} {} {}'.format(
node_to_python_source(node['left_operand']),
{'=': '=='}.get(node['operator'], node['operator']),
node_to_python_source(node['right_operand']),
)
def expression_to_python_source(node):
return node_to_python_source(node)
def float_to_python_source(node):
return node['value']
def function_call_to_python_source(node):
return '{name}({parameters})'.format(
name=node['name'],
parameters=', '.join(map(node_to_python_source, node['parameters'])),
)
def integer_to_python_source(node):
return node['value']
def loop_expression_to_python_source(node):
# TODO
return node_to_python_source(node['expression'])
def product_expression_to_python_source(node):
return infix_expression_to_python_source(node)
def regle_to_python_source(node):
def iter_variable_definitions():
for variable_definition_node in node['variable_definitions']:
assert variable_definition_node['type'] == 'variable_definition', variable_definition_node
expression_node = variable_definition_node['expression']
expression_source = node_to_python_source(expression_node)
arguments = []
find_node(expression_node, out=arguments, type='symbol')
yield 'def {}({}):\n return {}\n\n'.format(
variable_definition_node['name'],
', '.join(argument['value'] for argument in arguments),
expression_source,
)
return '\n'.join(iter_variable_definitions())
def sum_expression_to_python_source(node):
return infix_expression_to_python_source(node)
def symbol_to_python_source(node):
return node['value']
def ternary_operator_to_python_source(node):
return '({}) if ({}) else ({})'.format(
node_to_python_source(node['value_if_true']),
node_to_python_source(node['condition']),
node_to_python_source(node['value_if_false']),
)
# File transpilation functions
def chap_1_to_python_source():
nodes = read_ast_json_file(os.path.join(args.json_dir, 'chap-1.json'))
source = '\n'.join(map(node_to_python_source, nodes))
return source
def tgvH_json_to_python_source():
nodes = read_ast_json_file(os.path.join(args.json_dir, 'tgvH.json'))
variable_definition_by_name = {
node['name']: node
for node in nodes
if node['type'] in ('variable_calculee', 'variable_saisie')
}
return variable_definition_by_name
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('-d', '--debug', action='store_true', default=False, help='Debug Arpeggio parser')
parser.add_argument('-v', '--verbose', action='store_true', default=False, help='Increase output verbosity')
parser.add_argument('json_dir', help='Directory containing the JSON AST files')
global args
args = parser.parse_args()
logging.basicConfig(
level=logging.INFO if args.verbose else (logging.DEBUG if args.debug else logging.WARNING),
stream=sys.stdout,
)
# variable_definition_by_name = tgvH_json_to_python_source()
# write_source(
# file_name='definitions.py',
# source='variable_definition_by_name = ' + value_to_python_source(variable_definition_by_name),
# )
chap_1_source = chap_1_to_python_source()
write_source(
file_name='chap_1.py',
source=chap_1_source,
)
return 0
if __name__ == '__main__':
sys.exit(main())
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment