Skip to content

Instantly share code, notes, and snippets.

@spookylukey
Created January 25, 2019 13:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save spookylukey/e79cec2684a2f6c8f5c3578b76eac29e to your computer and use it in GitHub Desktop.
Save spookylukey/e79cec2684a2f6c8f5c3578b76eac29e to your computer and use it in GitHub Desktop.
Beginnings of a patch to use Python AST for compiling
diff --git a/fluent.runtime/fluent/runtime/codegen.py b/fluent.runtime/fluent/runtime/codegen.py
index f00f750..b76b147 100644
--- a/fluent.runtime/fluent/runtime/codegen.py
+++ b/fluent.runtime/fluent/runtime/codegen.py
@@ -3,6 +3,7 @@ Utilities for doing Python code generation
"""
from __future__ import absolute_import, unicode_literals
+import ast
import keyword
import re
import sys
@@ -52,6 +53,10 @@ UNKNOWN_TYPE = object
class PythonAst(object):
+ """
+ Base class representing a simplified Python AST (not the real one).
+ Generates real `ast.*` nodes via `as_ast()` method.
+ """
def simplify(self, changes):
"""
Simplify the statement/expression, returning either a modified
@@ -64,6 +69,9 @@ class PythonAst(object):
"""
return self
+ def as_ast(self):
+ raise NotImplementedError("{!r}.as_ast()".format(self.__class__))
+
class Scope(PythonAst):
def __init__(self, parent_scope=None):
@@ -205,7 +213,8 @@ def cleanup_name(name):
class Module(Scope):
- pass
+ def as_ast(self):
+ return ast.Module([s.as_ast() for s in self.statements])
class Statement(PythonAst):
@@ -253,6 +262,20 @@ class Function(Block, Statement):
self.args = args
self.kwargs = kwargs
+ def as_ast(self):
+ return ast.FunctionDef(name=self.func_name,
+ args=ast.arguments(args=[ast.arg(arg=arg_name, annotation=None,
+ lineno=1, col_offset=1)
+ for arg_name in self.args],
+ vararg=None,
+ kwonlyargs=[],
+ kw_defaults=[],
+ kwarg=None,
+ defaults=[]),
+ body=[statement.as_ast() for statement in self.statements],
+ decorator_list=[],
+ lineno=1, col_offset=1)
+
def as_source_code(self):
if not allowable_name(self.func_name):
raise AssertionError("Expected '{0}' to be a valid Python identifier".format(self.func_name))
@@ -281,6 +304,9 @@ class Return(Statement):
def __init__(self, value):
self.value = value
+ def as_ast(self):
+ return ast.Return(self.value.as_ast(), lineno=1, col_offset=1)
+
def as_source_code(self):
return 'return {0}'.format(self.value.as_source_code())
@@ -371,6 +397,9 @@ class String(Expression):
def __init__(self, string_value):
self.string_value = string_value
+ def as_ast(self):
+ return ast.Str(self.string_value, lineno=1, col_offset=1)
+
def as_source_code(self):
retval = repr(self.string_value)
# We eventually call 'exec' in a module that has 'from __future__ import
diff --git a/fluent.runtime/fluent/runtime/compiler.py b/fluent.runtime/fluent/runtime/compiler.py
index 85255df..49b0c28 100644
--- a/fluent.runtime/fluent/runtime/compiler.py
+++ b/fluent.runtime/fluent/runtime/compiler.py
@@ -126,7 +126,9 @@ def compile_messages(messages, locale, use_isolating=True, functions=None, debug
# TODO - it would be nice to be able to get back to FTL source file lines,
# if were knew what they were, and pass absolute filename that to 'compile'
# builtin as the filepath. Instead of that just use 'exec' for now.
- exec(module.as_source_code(), module_globals)
+
+ code_obj = compile(module.as_ast(), '<string>', 'exec')
+ exec(code_obj, module_globals)
retval = {}
for key, val in message_mapping.items():
if key.startswith('-'):
diff --git a/fluent.runtime/tests/test_compiler.py b/fluent.runtime/tests/test_compiler.py
index 6690eac..3d96cdc 100644
--- a/fluent.runtime/tests/test_compiler.py
+++ b/fluent.runtime/tests/test_compiler.py
@@ -5,6 +5,8 @@ from collections import OrderedDict
import babel
+from ast_decompiler import decompile
+
from fluent.runtime.builtins import BUILTINS
from fluent.runtime.compiler import messages_to_module
from fluent.runtime.errors import FluentCyclicReferenceError, FluentError, FluentReferenceError
@@ -40,7 +42,7 @@ def compile_messages_to_python(source, locale, use_isolating=False, functions=No
messages, locale,
use_isolating=use_isolating,
functions=_functions)
- return module.as_source_code(), errors
+ return decompile(module.as_ast()), errors
class TestCompiler(unittest.TestCase):
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment