hy/hy/compiler.py

1568 lines
52 KiB
Python
Raw Normal View History

# -*- encoding: utf-8 -*-
#
2013-03-18 15:27:14 +01:00
# Copyright (c) 2013 Paul Tagliamonte <paultag@debian.org>
# Copyright (c) 2013 Julien Danjou <julien@danjou.info>
# Copyright (c) 2013 Nicolas Dandrimont <nicolas.dandrimont@crans.org>
2013-03-03 22:26:17 +01:00
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
2013-03-05 02:40:23 +01:00
from hy.errors import HyError
2013-03-05 01:12:57 +01:00
2013-04-23 03:47:43 +02:00
from hy.models.lambdalist import HyLambdaListKeyword
2013-03-05 02:40:23 +01:00
from hy.models.expression import HyExpression
2013-04-23 03:47:43 +02:00
from hy.models.keyword import HyKeyword
2013-03-06 00:39:34 +01:00
from hy.models.integer import HyInteger
from hy.models.complex import HyComplex
2013-03-05 02:40:23 +01:00
from hy.models.string import HyString
2013-03-06 04:08:53 +01:00
from hy.models.symbol import HySymbol
2013-04-23 03:47:43 +02:00
from hy.models.float import HyFloat
2013-03-06 04:08:53 +01:00
from hy.models.list import HyList
2013-03-09 06:55:27 +01:00
from hy.models.dict import HyDict
2013-03-05 02:40:23 +01:00
from hy.core import process
from hy.util import str_type
2013-04-05 01:32:56 +02:00
2013-04-07 04:49:48 +02:00
import codecs
import traceback
2013-03-05 02:40:23 +01:00
import ast
2013-03-12 01:17:27 +01:00
import sys
2013-03-05 02:40:23 +01:00
from collections import defaultdict
2013-03-05 02:40:23 +01:00
class HyCompileError(HyError):
def __init__(self, exception, traceback=None):
self.exception = exception
self.traceback = traceback
def __str__(self):
if isinstance(self.exception, HyTypeError):
return str(self.exception)
if self.traceback:
tb = "".join(traceback.format_tb(self.traceback)).strip()
else:
tb = "No traceback available. 😟"
return("Internal Compiler Bug 😱\n%s: %s\nCompilation traceback:\n%s"
% (self.exception.__class__.__name__,
self.exception, tb))
2013-03-05 02:40:23 +01:00
class HyTypeError(TypeError):
def __init__(self, expression, message):
super(HyTypeError, self).__init__(message)
self.expression = expression
2013-03-05 02:40:23 +01:00
def __str__(self):
return (super(HyTypeError, self).__str__() + " (line %s, column %d)"
% (self.expression.start_line,
self.expression.start_column))
2013-03-05 02:40:23 +01:00
2013-03-05 02:40:23 +01:00
_compile_table = {}
def ast_str(foobar):
if sys.version_info[0] >= 3:
return str(foobar)
2013-04-07 04:49:48 +02:00
try:
return str(foobar)
except UnicodeEncodeError:
pass
enc = codecs.getencoder('punycode')
foobar, _ = enc(foobar)
return "hy_%s" % (str(foobar).replace("-", "_"))
2013-03-05 02:40:23 +01:00
def builds(_type):
def _dec(fn):
_compile_table[_type] = fn
return fn
2013-03-05 02:40:23 +01:00
return _dec
class Result(object):
"""
Smart representation of the result of a hy->AST compilation
This object tries to reconcile the hy world, where everything can be used
as an expression, with the Python world, where statements and expressions
need to coexist.
To do so, we represent a compiler result as a list of statements `stmts`,
terminated by an expression context `expr`. The expression context is used
when the compiler needs to use the result as an expression.
Results are chained by addition: adding two results together returns a
Result representing the succession of the two Results' statements, with
the second Result's expression context.
We make sure that a non-empty expression context does not get clobbered by
adding more results, by checking accesses to the expression context. We
assume that the context has been used, or deliberately ignored, if it has
been accessed.
The Result object is interoperable with python AST objects: when an AST
object gets added to a Result object, it gets converted on-the-fly.
"""
__slots__ = ("imports", "stmts", "temp_variables", "_expr", "__used_expr")
def __init__(self, *args, **kwargs):
if args:
# emulate kw-only args for future bits.
raise TypeError("Yo: Hacker: don't pass me real args, dingus")
self.imports = defaultdict(set)
self.stmts = []
self.temp_variables = []
self._expr = None
self.__used_expr = False
# XXX: Make sure we only have AST where we should.
for kwarg in kwargs:
if kwarg not in ["imports", "stmts", "expr", "temp_variables"]:
raise TypeError(
"%s() got an unexpected keyword argument '%s'" % (
self.__class__.__name__, kwarg))
setattr(self, kwarg, kwargs[kwarg])
@property
def expr(self):
self.__used_expr = True
return self._expr
@expr.setter
def expr(self, value):
self.__used_expr = False
self._expr = value
def add_imports(self, mod, imports):
"""Autoimport `imports` from `mod`"""
self.imports[mod].update(imports)
def is_expr(self):
"""Check whether I am a pure expression"""
return self._expr and not (self.imports or self.stmts)
@property
def force_expr(self):
"""Force the expression context of the Result.
If there is no expression context, we return a "None" expression.
"""
if not self.expr:
# Spoof the position of the last statement for our generated None
lineno = 0
col_offset = 0
if self.stmts:
lineno = self.stmts[-1].lineno
col_offset = self.stmts[-1].col_offset
return ast.Name(id=ast_str("None"),
arg=ast_str("None"),
ctx=ast.Load(),
lineno=lineno,
col_offset=col_offset)
# XXX: Likely raise Exception here - this will assertionfail
# pypy since the ast will be out of numerical order.
else:
return self.expr
def expr_as_stmt(self):
"""Convert the Result's expression context to a statement
This is useful when we want to use the stored expression in a
statement context (for instance in a code branch).
If there is no expression context, return an empty result.
"""
if self.expr:
return Result() + ast.Expr(lineno=self.expr.lineno,
col_offset=self.expr.col_offset,
value=self.expr)
return Result()
def rename(self, new_name):
"""Rename the Result's temporary variables to a `new_name`.
We know how to handle ast.Names and ast.FunctionDefs.
"""
new_name = ast_str(new_name)
for var in self.temp_variables:
if isinstance(var, ast.Name):
var.id = new_name
var.arg = new_name
elif isinstance(var, ast.FunctionDef):
var.name = new_name
else:
raise TypeError("Don't know how to rename a %s!" % (
var.__class__.__name__))
self.temp_variables = []
def __add__(self, other):
# If we add an ast statement, convert it first
if isinstance(other, ast.stmt):
return self + Result(stmts=[other])
# If we add an ast expression, clobber the expression context
if isinstance(other, ast.expr):
return self + Result(expr=other)
if isinstance(other, ast.excepthandler):
return self + Result(stmts=[other])
if not isinstance(other, Result):
raise TypeError("Can't add %r with non-compiler result %r" % (
self, other))
# Check for expression context clobbering
if self.expr and not self.__used_expr:
traceback.print_stack()
print("Bad boy clobbered expr %s with %s" % (
ast.dump(self.expr),
ast.dump(other.expr)))
# Fairly obvious addition
result = Result()
result.imports = other.imports
result.stmts = self.stmts + other.stmts
result.expr = other.expr
result.temp_variables = other.temp_variables
return result
def __str__(self):
return "Result(imports=[%s], stmts=[%s], expr=%s)" % (
", ".join(ast.dump(x) for x in self.imports),
", ".join(ast.dump(x) for x in self.stmts),
ast.dump(self.expr) if self.expr else None,
)
def _collect(results):
"""Collect the expression contexts from a list of results
This returns a list of the expression contexts, and the sum of the Result
objects passed as arguments.
"""
compiled_exprs = []
ret = Result()
for result in results:
ret += result
compiled_exprs.append(ret.force_expr)
return compiled_exprs, ret
def _branch(results):
"""Make a branch out of a list of Result objects
This generates a Result from the given sequence of Results, forcing each
expression context as a statement before the next result is used.
We keep the expression context of the last argument for the returned Result
"""
results = list(results)
ret = Result()
for result in results[:-1]:
ret += result
ret += result.expr_as_stmt()
for result in results[-1:]:
ret += result
return ret
def _raise_wrong_args_number(expression, error):
raise HyTypeError(expression,
error % (expression.pop(0),
len(expression)))
def checkargs(exact=None, min=None, max=None):
def _dec(fn):
def checker(self, expression):
if exact is not None and (len(expression) - 1) != exact:
2013-04-10 02:44:05 +02:00
_raise_wrong_args_number(
expression, "`%%s' needs %d arguments, got %%d" % exact)
if min is not None and (len(expression) - 1) < min:
2013-04-10 02:44:52 +02:00
_raise_wrong_args_number(
expression,
2013-04-06 19:15:32 +02:00
"`%%s' needs at least %d arguments, got %%d" % (min))
if max is not None and (len(expression) - 1) > max:
2013-04-10 02:44:52 +02:00
_raise_wrong_args_number(
expression,
2013-04-06 19:15:32 +02:00
"`%%s' needs at most %d arguments, got %%d" % (max))
return fn(self, expression)
return checker
return _dec
2013-03-05 02:40:23 +01:00
class HyASTCompiler(object):
def __init__(self):
2013-03-05 04:35:07 +01:00
self.anon_fn_count = 0
self.anon_var_count = 0
self.imports = defaultdict(set)
2013-03-05 02:40:23 +01:00
def get_anon_var(self):
self.anon_var_count += 1
return "_hy_anon_var_%s" % self.anon_var_count
def get_anon_fn(self):
self.anon_fn_count += 1
return "_hy_anon_fn_%d" % self.anon_fn_count
def update_imports(self, result):
"""Retrieve the imports from the result object"""
for mod in result.imports:
self.imports[mod].update(result.imports[mod])
def imports_as_stmts(self, expr):
"""Convert the Result's imports to statements"""
ret = Result()
for module, names in self.imports.items():
ret += self.compile([
HyExpression([
HySymbol("import"),
HyList([
HySymbol(module),
HyList([HySymbol(name) for name in sorted(names)])
])
]).replace(expr)
])
self.imports = defaultdict(set)
return ret.stmts
def compile_atom(self, atom_type, atom):
if atom_type in _compile_table:
atom = process(atom)
ret = _compile_table[atom_type](self, atom)
if not isinstance(ret, Result):
ret = Result() + ret
return ret
else:
return None
2013-03-05 02:40:23 +01:00
def compile(self, tree):
try:
_type = type(tree)
ret = self.compile_atom(_type, tree)
if ret:
self.update_imports(ret)
return ret
except HyCompileError:
# compile calls compile, so we're going to have multiple raise
# nested; so let's re-raise this exception, let's not wrap it in
# another HyCompileError!
raise
except Exception as e:
raise HyCompileError(e, sys.exc_info()[2])
2013-03-05 02:40:23 +01:00
raise HyCompileError(Exception("Unknown type: `%s'" % _type))
2013-04-04 02:18:56 +02:00
def _compile_collect(self, exprs):
return _collect(self.compile(expr) for expr in exprs)
2013-03-05 02:40:23 +01:00
def _compile_branch(self, exprs):
return _branch(self.compile(expr) for expr in exprs)
2013-03-05 02:40:23 +01:00
def _parse_lambda_list(self, exprs):
2013-04-19 04:27:38 +02:00
""" Return FunctionDef parameter values from lambda list."""
ret = Result()
args = []
2013-04-19 04:27:38 +02:00
defaults = []
varargs = None
2013-04-19 04:27:38 +02:00
kwargs = None
lambda_keyword = None
2013-04-19 16:34:17 +02:00
for expr in exprs:
if isinstance(expr, HyLambdaListKeyword):
if expr not in expr._valid_types:
raise HyCompileError("{0} is not a valid "
"lambda-keyword.".format(repr(expr)))
if expr == "&rest" and lambda_keyword is None:
lambda_keyword = expr
2013-04-18 23:47:08 +02:00
elif expr == "&optional":
lambda_keyword = expr
2013-04-18 23:47:08 +02:00
elif expr == "&key":
lambda_keyword = expr
elif expr == "&kwargs":
lambda_keyword = expr
else:
raise HyCompileError("{0} is in an invalid "
"position.".format(repr(expr)))
# we don't actually care about this token, so we set
# our state and continue to the next token...
continue
if lambda_keyword is None:
args.append(expr)
elif lambda_keyword == "&rest":
if varargs:
raise HyCompileError("There can only be one "
"&rest argument")
varargs = str(expr)
2013-04-18 23:47:08 +02:00
elif lambda_keyword == "&key":
if type(expr) != HyDict:
2013-04-19 04:27:38 +02:00
raise TypeError("There can only be one &key "
"argument")
2013-04-18 23:47:08 +02:00
else:
2013-04-19 04:27:38 +02:00
if len(defaults) > 0:
2013-04-19 00:39:49 +02:00
raise HyCompileError("There can only be "
"one &key argument")
2013-04-19 04:27:38 +02:00
# As you can see, Python has a funny way of
# defining keyword arguments.
2013-04-19 16:34:17 +02:00
for k, v in expr.items():
2013-04-19 04:27:38 +02:00
args.append(k)
ret += self.compile(v)
defaults.append(ret.force_expr)
elif lambda_keyword == "&optional":
2013-04-18 23:47:08 +02:00
# not implemented yet.
pass
2013-04-18 23:47:08 +02:00
elif lambda_keyword == "&kwargs":
2013-04-19 04:27:38 +02:00
if kwargs:
raise HyCompileError("There can only be one "
"&kwargs argument")
2013-04-18 23:47:08 +02:00
kwargs = str(expr)
return ret, args, defaults, varargs, kwargs
def _storeize(self, name):
"""Return a new `name` object with an ast.Store() context"""
if isinstance(name, Result):
if not name.is_expr():
raise TypeError("Can't assign to a non-expr")
name = name.expr
if isinstance(name, (ast.Tuple, ast.List)):
typ = type(name)
new_elts = []
for x in name.elts:
new_elts.append(self._storeize(x))
new_name = typ(elts=new_elts)
elif isinstance(name, ast.Name):
new_name = ast.Name(id=name.id, arg=name.arg)
elif isinstance(name, ast.Subscript):
new_name = ast.Subscript(value=name.value, slice=name.slice)
elif isinstance(name, ast.Attribute):
new_name = ast.Attribute(value=name.value, attr=name.attr)
else:
raise TypeError("Can't assign to a %s object" % type(name))
new_name.ctx = ast.Store()
ast.copy_location(new_name, name)
return new_name
2013-03-05 02:40:23 +01:00
@builds(list)
def compile_raw_list(self, entries):
ret = self._compile_branch(entries)
ret += ret.expr_as_stmt()
return ret
2013-03-05 02:40:23 +01:00
def _render_quoted_form(self, form):
name = form.__class__.__name__
imports = [name]
2013-04-11 04:51:58 +02:00
if isinstance(form, HyList):
contents = []
for x in form:
form_imports, form_contents = self._render_quoted_form(x)
imports += form_imports
contents.append(form_contents)
return imports, HyExpression(
[HySymbol(name),
HyList(contents)]
).replace(form)
elif isinstance(form, HySymbol):
return imports, HyExpression([HySymbol(name),
HyString(form)]).replace(form)
return imports, HyExpression([HySymbol(name), form]).replace(form)
@builds("quote")
2013-04-10 02:34:46 +02:00
@checkargs(exact=1)
def compile_quote(self, entries):
imports, stmts = self._render_quoted_form(entries[1])
ret = self.compile(stmts)
ret.add_imports("hy", imports)
return ret
2013-04-10 03:33:09 +02:00
@builds("eval")
@checkargs(exact=1)
def compile_eval(self, expr):
expr.pop(0)
2013-04-11 04:51:58 +02:00
ret = self.compile(HyExpression([
HySymbol("hy_eval")] + expr + [
HyExpression([HySymbol("locals")])]).replace(expr))
2013-04-10 03:33:09 +02:00
ret.add_imports("hy.importer", ["hy_eval"])
return ret
@builds("do")
@builds("progn")
def compile_progn(self, expression):
expression.pop(0)
return self._compile_branch(expression)
2013-03-09 05:41:04 +01:00
@builds("throw")
@builds("raise")
@checkargs(max=1)
def compile_throw_expression(self, expr):
expr.pop(0)
ret = Result()
if expr:
ret += self.compile(expr.pop(0))
# Use ret.expr to get a literal `None`
ret += ast.Raise(
lineno=expr.start_line,
col_offset=expr.start_column,
type=ret.expr,
exc=ret.expr,
inst=None,
tback=None,
cause=None)
return ret
@builds("try")
def compile_try_expression(self, expr):
expr.pop(0) # try
2013-03-12 01:17:27 +01:00
try:
body = expr.pop(0)
except IndexError:
body = []
# (try something…)
body = self.compile(body)
# XXX we will likely want to make this a tempvar
body += body.expr_as_stmt()
body = body.stmts
if not body:
body = [ast.Pass(lineno=expr.start_line,
col_offset=expr.start_column)]
orelse = []
finalbody = []
handlers = []
handler_results = Result()
for e in expr:
if not len(e):
raise HyTypeError(e, "Empty list not allowed in `try'")
if e[0] in (HySymbol("except"), HySymbol("catch")):
handler_results += self.compile(e)
handlers.append(handler_results.stmts.pop())
elif e[0] == HySymbol("else"):
if orelse:
raise HyTypeError(
e,
"`try' cannot have more than one `else'")
else:
orelse = self._compile_branch(e[1:])
# XXX tempvar magic
orelse += orelse.expr_as_stmt()
orelse = orelse.stmts
elif e[0] == HySymbol("finally"):
if finalbody:
raise HyTypeError(
e,
"`try' cannot have more than one `finally'")
else:
finalbody = self._compile_branch(e[1:])
# XXX tempvar magic
finalbody += finalbody.expr_as_stmt()
finalbody = finalbody.stmts
else:
raise HyTypeError(e, "Unknown expression in `try'")
# Using (else) without (except) is verboten!
if orelse and not handlers:
raise HyTypeError(
e,
"`try' cannot have `else' without `except'")
# (try) or (try BODY)
# Generate a default handler for Python >= 3.3 and pypy
if not handlers and not finalbody and not orelse:
handlers = [ast.ExceptHandler(
lineno=expr.start_line,
col_offset=expr.start_column,
type=None,
name=None,
body=[ast.Pass(lineno=expr.start_line,
col_offset=expr.start_column)])]
ret = handler_results
if sys.version_info[0] >= 3 and sys.version_info[1] >= 3:
# Python 3.3 features a merge of TryExcept+TryFinally into Try.
return ret + ast.Try(
lineno=expr.start_line,
col_offset=expr.start_column,
body=body,
handlers=handlers,
orelse=orelse,
finalbody=finalbody)
if finalbody:
if handlers:
return ret + ast.TryFinally(
lineno=expr.start_line,
col_offset=expr.start_column,
body=[ast.TryExcept(
lineno=expr.start_line,
col_offset=expr.start_column,
handlers=handlers,
body=body,
orelse=orelse)],
finalbody=finalbody)
return ret + ast.TryFinally(
lineno=expr.start_line,
col_offset=expr.start_column,
body=body,
finalbody=finalbody)
return ret + ast.TryExcept(
lineno=expr.start_line,
col_offset=expr.start_column,
handlers=handlers,
body=body,
orelse=orelse)
@builds("catch")
@builds("except")
def compile_catch_expression(self, expr):
catch = expr.pop(0) # catch
try:
exceptions = expr.pop(0)
except IndexError:
exceptions = HyList()
# exceptions catch should be either:
# [[list of exceptions]]
# or
# [variable [list of exceptions]]
# or
# [variable exception]
# or
# [exception]
# or
# []
if not isinstance(exceptions, HyList):
raise HyTypeError(exceptions,
"`%s' exceptions list is not a list" % catch)
if len(exceptions) > 2:
raise HyTypeError(exceptions,
"`%s' exceptions list is too long" % catch)
# [variable [list of exceptions]]
# let's pop variable and use it as name
if len(exceptions) == 2:
name = exceptions.pop(0)
if sys.version_info[0] >= 3:
# Python3 features a change where the Exception handler
# moved the name from a Name() to a pure Python String type.
#
# We'll just make sure it's a pure "string", and let it work
# it's magic.
name = ast_str(name)
else:
# Python2 requires an ast.Name, set to ctx Store.
name = self._storeize(self.compile(name))
else:
name = None
try:
exceptions_list = exceptions.pop(0)
except IndexError:
exceptions_list = []
if isinstance(exceptions_list, list):
if len(exceptions_list):
# [FooBar BarFoo] → catch Foobar and BarFoo exceptions
elts, _type = self._compile_collect(exceptions_list)
_type += ast.Tuple(elts=elts,
lineno=expr.start_line,
col_offset=expr.start_column,
ctx=ast.Load())
else:
# [] → all exceptions catched
_type = Result()
elif isinstance(exceptions_list, HySymbol):
_type = self.compile(exceptions_list)
else:
raise HyTypeError(exceptions,
"`%s' needs a valid exception list" % catch)
body = self._compile_branch(expr)
# XXX tempvar handling magic
body += body.expr_as_stmt()
body = body.stmts
if not body:
body = [ast.Pass(lineno=expr.start_line,
col_offset=expr.start_column)]
# use _type.expr to get a literal `None`
return _type + ast.ExceptHandler(
lineno=expr.start_line,
col_offset=expr.start_column,
type=_type.expr,
name=name,
body=body)
@builds("if")
@checkargs(min=2, max=3)
def compile_if(self, expression):
expression.pop(0)
cond = self.compile(expression.pop(0))
body = self.compile(expression.pop(0))
orel = Result()
if expression:
orel = self.compile(expression.pop(0))
# We want to hoist the statements from the condition
ret = cond
if body.stmts or orel.stmts:
# We have statements in our bodies
# Get a temporary variable for the result storage
var = self.get_anon_var()
name = ast.Name(id=ast_str(var), arg=ast_str(var),
ctx=ast.Store(),
lineno=expression.start_line,
col_offset=expression.start_column)
# Store the result of the body
body += ast.Assign(targets=[name],
value=body.force_expr,
lineno=expression.start_line,
col_offset=expression.start_column)
# and of the else clause
orel += ast.Assign(targets=[name],
value=orel.force_expr,
lineno=expression.start_line,
col_offset=expression.start_column)
# Then build the if
ret += ast.If(test=ret.force_expr,
body=body.stmts,
orelse=orel.stmts,
lineno=expression.start_line,
col_offset=expression.start_column)
# And make our expression context our temp variable
expr_name = ast.Name(id=ast_str(var), arg=ast_str(var),
ctx=ast.Load(),
lineno=expression.start_line,
col_offset=expression.start_column)
ret += Result(expr=expr_name, temp_variables=[expr_name, name])
else:
# Just make that an if expression
ret += ast.IfExp(test=ret.force_expr,
body=body.force_expr,
orelse=orel.force_expr,
lineno=expression.start_line,
col_offset=expression.start_column)
return ret
2013-03-09 05:07:21 +01:00
@builds("print")
2013-03-10 20:39:27 +01:00
def compile_print_expression(self, expr):
call = expr.pop(0) # print
values, ret = self._compile_collect(expr)
2013-04-02 01:51:21 +02:00
if sys.version_info[0] >= 3:
call = self.compile(call)
ret += call
ret += ast.Call(func=call.expr,
args=values,
keywords=[],
starargs=None,
kwargs=None,
lineno=expr.start_line,
col_offset=expr.start_column)
else:
ret += ast.Print(
2013-04-02 01:51:21 +02:00
lineno=expr.start_line,
col_offset=expr.start_column,
dest=None,
values=values,
nl=True)
2013-04-02 01:51:21 +02:00
return ret
2013-03-10 20:39:27 +01:00
@builds("assert")
@checkargs(1)
2013-03-06 03:42:54 +01:00
def compile_assert_expression(self, expr):
expr.pop(0) # assert
e = expr.pop(0)
ret = self.compile(e)
ret += ast.Assert(test=ret.force_expr,
2013-03-06 03:42:54 +01:00
msg=None,
lineno=e.start_line,
col_offset=e.start_column)
return ret
@builds("global")
@checkargs(1)
2013-04-24 01:25:02 +02:00
def compile_global_expression(self, expr):
expr.pop(0) # global
e = expr.pop(0)
return ast.Global(names=[ast_str(e)],
lineno=e.start_line,
col_offset=e.start_column)
@builds("lambda")
@checkargs(2)
2013-03-09 21:57:13 +01:00
def compile_lambda_expression(self, expr):
expr.pop(0)
sig = expr.pop(0)
body = self.compile(expr.pop(0))
body += ast.Lambda(
2013-03-09 21:57:13 +01:00
lineno=expr.start_line,
col_offset=expr.start_column,
args=ast.arguments(args=[
ast.Name(arg=ast_str(x), id=ast_str(x),
2013-03-09 21:57:13 +01:00
ctx=ast.Param(),
lineno=x.start_line,
col_offset=x.start_column)
for x in sig],
vararg=None,
kwarg=None,
defaults=[],
kwonlyargs=[],
kw_defaults=[]),
body=body.force_expr)
return body
2013-03-09 21:57:13 +01:00
@builds("yield")
2013-04-13 05:48:58 +02:00
@checkargs(max=1)
2013-03-15 01:55:11 +01:00
def compile_yield_expression(self, expr):
expr.pop(0)
ret = Result()
2013-04-13 05:48:58 +02:00
value = None
if expr != []:
ret += self.compile(expr.pop(0))
value = ret.force_expr
ret += ast.Yield(
2013-04-13 05:48:58 +02:00
value=value,
2013-03-15 01:55:11 +01:00
lineno=expr.start_line,
col_offset=expr.start_column)
return ret
@builds("import")
2013-03-10 01:46:32 +01:00
def compile_import_expression(self, expr):
2013-04-15 03:54:15 +02:00
def _compile_import(expr, module, names=None, importer=ast.Import):
if not names:
names = [ast.alias(name=ast_str(module), asname=None)]
ret = importer(lineno=expr.start_line,
col_offset=expr.start_column,
module=ast_str(module),
names=names,
level=0)
return Result() + ret
2013-03-10 01:46:32 +01:00
expr.pop(0) # index
rimports = Result()
while len(expr) > 0:
iexpr = expr.pop(0)
2013-04-15 03:54:15 +02:00
if isinstance(iexpr, HySymbol):
rimports += _compile_import(expr, iexpr)
2013-04-15 03:54:15 +02:00
continue
if isinstance(iexpr, HyList) and len(iexpr) == 1:
rimports += _compile_import(expr, iexpr.pop(0))
2013-04-15 03:54:15 +02:00
continue
if isinstance(iexpr, HyList) and iexpr:
module = iexpr.pop(0)
2013-04-15 03:54:15 +02:00
entry = iexpr[0]
if isinstance(entry, HyKeyword) and entry == HyKeyword(":as"):
if not len(iexpr) == 2:
raise HyTypeError(iexpr,
"garbage after aliased import")
2013-04-15 03:54:15 +02:00
iexpr.pop(0) # :as
alias = iexpr.pop(0)
names = [ast.alias(name=ast_str(module),
asname=ast_str(alias))]
rimports += _compile_import(expr, ast_str(module), names)
2013-04-15 03:54:15 +02:00
continue
if isinstance(entry, HyList):
names = []
2013-04-15 03:54:15 +02:00
while entry:
sym = entry.pop(0)
if entry and isinstance(entry[0], HyKeyword):
entry.pop(0)
alias = ast_str(entry.pop(0))
else:
alias = None
names.append(ast.alias(name=ast_str(sym),
asname=alias))
2013-04-15 03:54:15 +02:00
rimports += _compile_import(expr, module,
names, ast.ImportFrom)
continue
raise HyTypeError(
entry,
"Unknown entry (`%s`) in the HyList" % (entry)
)
2013-04-15 03:54:15 +02:00
return rimports
2013-03-10 01:46:32 +01:00
@builds("get")
@checkargs(2)
2013-03-09 06:55:27 +01:00
def compile_index_expression(self, expr):
expr.pop(0) # index
val = self.compile(expr.pop(0)) # target
sli = self.compile(expr.pop(0)) # slice
return val + sli + ast.Subscript(
2013-03-09 06:55:27 +01:00
lineno=expr.start_line,
col_offset=expr.start_column,
value=val.force_expr,
slice=ast.Index(value=sli.force_expr),
2013-03-09 06:55:27 +01:00
ctx=ast.Load())
2013-03-19 00:47:48 +01:00
@builds("slice")
@checkargs(min=1, max=4)
2013-03-19 00:47:48 +01:00
def compile_slice_expression(self, expr):
expr.pop(0) # index
val = self.compile(expr.pop(0)) # target
low = Result()
2013-03-19 00:47:48 +01:00
if expr != []:
low = self.compile(expr.pop(0))
high = Result()
2013-03-19 00:47:48 +01:00
if expr != []:
high = self.compile(expr.pop(0))
step = Result()
if expr != []:
step = self.compile(expr.pop(0))
# use low.expr, high.expr and step.expr to use a literal `None`.
return val + low + high + step + ast.Subscript(
2013-03-19 00:47:48 +01:00
lineno=expr.start_line,
col_offset=expr.start_column,
value=val.force_expr,
slice=ast.Slice(lower=low.expr,
upper=high.expr,
step=step.expr),
2013-03-19 00:47:48 +01:00
ctx=ast.Load())
2013-03-09 06:55:27 +01:00
@builds("assoc")
@checkargs(3)
2013-03-10 20:39:27 +01:00
def compile_assoc_expression(self, expr):
2013-03-10 17:59:16 +01:00
expr.pop(0) # assoc
# (assoc foo bar baz) => foo[bar] = baz
target = self.compile(expr.pop(0))
key = self.compile(expr.pop(0))
val = self.compile(expr.pop(0))
2013-03-10 17:59:16 +01:00
return target + key + val + ast.Assign(
2013-03-10 17:59:16 +01:00
lineno=expr.start_line,
col_offset=expr.start_column,
targets=[
ast.Subscript(
lineno=expr.start_line,
col_offset=expr.start_column,
value=target.force_expr,
slice=ast.Index(value=key.force_expr),
2013-03-10 17:59:16 +01:00
ctx=ast.Store())],
value=val.force_expr)
2013-03-10 17:59:16 +01:00
@builds("with_decorator")
@checkargs(min=1)
2013-03-10 03:01:59 +01:00
def compile_decorate_expression(self, expr):
expr.pop(0) # with-decorator
2013-03-10 03:01:59 +01:00
fn = self.compile(expr.pop(-1))
if not fn.stmts or not isinstance(fn.stmts[-1], ast.FunctionDef):
raise HyTypeError(expr, "Decorated a non-function")
decorators, ret = self._compile_collect(expr)
fn.stmts[-1].decorator_list = decorators
return ret + fn
2013-03-10 03:01:59 +01:00
@builds("with")
@checkargs(min=2)
def compile_with_expression(self, expr):
expr.pop(0) # with
args = expr.pop(0)
if len(args) > 2 or len(args) < 1:
raise HyTypeError(expr, "with needs [arg (expr)] or [(expr)]")
args.reverse()
ctx = self.compile(args.pop(0))
thing = None
if args != []:
thing = self._storeize(self.compile(args.pop(0)))
2013-03-24 07:04:44 +01:00
body = self._compile_branch(expr)
body += body.expr_as_stmt()
if not body.stmts:
body += ast.Pass(lineno=expr.start_line,
col_offset=expr.start_column)
the_with = ast.With(context_expr=ctx.force_expr,
lineno=expr.start_line,
col_offset=expr.start_column,
optional_vars=thing,
body=body.stmts)
2013-03-24 15:00:07 +01:00
if sys.version_info[0] >= 3 and sys.version_info[1] >= 3:
the_with.items = [ast.withitem(context_expr=ctx.force_expr,
optional_vars=thing)]
2013-03-24 07:04:44 +01:00
return ctx + the_with
2013-03-24 07:04:44 +01:00
@builds(",")
def compile_tuple(self, expr):
expr.pop(0)
elts, ret = self._compile_collect(expr)
ret += ast.Tuple(elts=elts,
lineno=expr.start_line,
col_offset=expr.start_column,
ctx=ast.Load())
return ret
@builds("list_comp")
@checkargs(min=2, max=3)
def compile_list_comprehension(self, expr):
2013-04-03 02:46:32 +02:00
# (list-comp expr (target iter) cond?)
expr.pop(0)
2013-04-03 02:46:32 +02:00
expression = expr.pop(0)
tar_it = iter(expr.pop(0))
targets = zip(tar_it, tar_it)
cond = self.compile(expr.pop(0)) if expr != [] else Result()
2013-04-03 02:46:32 +02:00
generator_res = Result()
generators = []
2013-04-03 02:46:32 +02:00
for target, iterable in targets:
comp_target = self.compile(target)
target = self._storeize(comp_target)
generator_res += self.compile(iterable)
generators.append(ast.comprehension(
target=target,
iter=generator_res.force_expr,
2013-04-03 02:46:32 +02:00
ifs=[]))
if cond.expr:
generators[-1].ifs.append(cond.expr)
2013-04-03 02:46:32 +02:00
compiled_expression = self.compile(expression)
ret = compiled_expression + generator_res + cond
ret += ast.ListComp(
lineno=expr.start_line,
col_offset=expr.start_column,
elt=compiled_expression.force_expr,
generators=generators)
2013-04-03 02:46:32 +02:00
return ret
@builds("kwapply")
@checkargs(2)
2013-03-10 03:14:30 +01:00
def compile_kwapply_expression(self, expr):
expr.pop(0) # kwapply
call = self.compile(expr.pop(0))
kwargs = expr.pop(0)
if type(call.expr) != ast.Call:
raise HyTypeError(expr, "kwapplying a non-call")
2013-03-10 03:14:30 +01:00
2013-04-11 03:44:23 +02:00
if type(kwargs) != HyDict:
raise TypeError("kwapplying with a non-dict")
keywords = []
ret = Result()
for x in kwargs:
ret += self.compile(kwargs[x])
keywords.append(ast.keyword(arg=ast_str(x),
value=ret.force_expr))
2013-03-10 03:14:30 +01:00
call.expr.keywords = keywords
return ret + call
2013-03-10 03:14:30 +01:00
@builds("not")
@builds("~")
@checkargs(1)
def compile_unary_operator(self, expression):
ops = {"not": ast.Not,
"~": ast.Invert}
operator = expression.pop(0)
operand = self.compile(expression.pop(0))
operand += ast.UnaryOp(op=ops[operator](),
operand=operand.expr,
lineno=operator.start_line,
col_offset=operator.start_column)
return operand
@builds("and")
@builds("or")
@checkargs(min=2)
def compile_logical_or_and_and_operator(self, expression):
ops = {"and": ast.And,
"or": ast.Or}
operator = expression.pop(0)
values, ret = self._compile_collect(expression)
ret += ast.BoolOp(op=ops[operator](),
lineno=operator.start_line,
col_offset=operator.start_column,
values=values)
return ret
@builds("=")
@builds("!=")
@builds("<")
@builds("<=")
@builds(">")
@builds(">=")
@builds("is")
@builds("in")
@builds("is_not")
@builds("not_in")
@checkargs(min=2)
2013-03-06 03:42:54 +01:00
def compile_compare_op_expression(self, expression):
2013-03-10 03:01:59 +01:00
ops = {"=": ast.Eq, "!=": ast.NotEq,
"<": ast.Lt, "<=": ast.LtE,
">": ast.Gt, ">=": ast.GtE,
"is": ast.Is, "is_not": ast.IsNot,
"in": ast.In, "not_in": ast.NotIn}
2013-03-06 03:42:54 +01:00
inv = expression.pop(0)
op = ops[inv]
ops = [op() for x in range(1, len(expression))]
e = expression[0]
exprs, ret = self._compile_collect(expression)
return ret + ast.Compare(left=exprs[0],
ops=ops,
comparators=exprs[1:],
lineno=e.start_line,
col_offset=e.start_column)
2013-03-06 03:42:54 +01:00
@builds("+")
@builds("%")
@builds("/")
@builds("//")
@builds("*")
@builds("**")
@builds("<<")
@builds(">>")
@builds("|")
@builds("^")
@builds("&")
@checkargs(min=2)
2013-03-06 00:28:27 +01:00
def compile_maths_expression(self, expression):
ops = {"+": ast.Add,
"/": ast.Div,
"//": ast.FloorDiv,
2013-03-06 00:28:27 +01:00
"*": ast.Mult,
2013-03-19 02:46:58 +01:00
"-": ast.Sub,
"%": ast.Mod,
"**": ast.Pow,
"<<": ast.LShift,
">>": ast.RShift,
"|": ast.BitOr,
"^": ast.BitXor,
"&": ast.BitAnd}
2013-03-06 00:28:27 +01:00
inv = expression.pop(0)
op = ops[inv]
ret = self.compile(expression.pop(0))
2013-03-06 00:28:27 +01:00
for child in expression:
left_expr = ret.force_expr
ret += self.compile(child)
right_expr = ret.force_expr
ret += ast.BinOp(left=left_expr,
2013-03-06 00:28:27 +01:00
op=op(),
right=right_expr,
2013-03-06 00:28:27 +01:00
lineno=child.start_line,
col_offset=child.start_column)
return ret
2013-03-06 00:28:27 +01:00
2013-04-16 17:43:40 +02:00
@builds("-")
@checkargs(min=1)
def compile_maths_expression_sub(self, expression):
if len(expression) > 2:
return self.compile_maths_expression(expression)
else:
arg = expression[1]
ret = self.compile(arg)
ret += ast.UnaryOp(op=ast.USub(),
operand=ret.force_expr,
2013-04-16 17:43:40 +02:00
lineno=arg.start_line,
col_offset=arg.start_column)
return ret
2013-04-16 17:43:40 +02:00
@builds("+=")
@builds("/=")
@builds("//=")
@builds("*=")
@builds("_=")
@builds("%=")
@builds("**=")
@builds("<<=")
@builds(">>=")
@builds("|=")
@builds("^=")
@builds("&=")
@checkargs(2)
def compile_augassign_expression(self, expression):
ops = {"+=": ast.Add,
"/=": ast.Div,
"//=": ast.FloorDiv,
"*=": ast.Mult,
"_=": ast.Sub,
"%=": ast.Mod,
"**=": ast.Pow,
"<<=": ast.LShift,
">>=": ast.RShift,
"|=": ast.BitOr,
"^=": ast.BitXor,
"&=": ast.BitAnd}
op = ops[expression[0]]
target = self._storeize(self.compile(expression[1]))
ret = self.compile(expression[2])
ret += ast.AugAssign(
target=target,
value=ret.force_expr,
op=op(),
lineno=expression.start_line,
col_offset=expression.start_column)
return ret
2013-03-10 04:04:38 +01:00
2013-03-05 02:40:23 +01:00
@builds(HyExpression)
def compile_expression(self, expression):
2013-03-05 04:35:07 +01:00
fn = expression[0]
func = None
2013-03-09 22:42:07 +01:00
if isinstance(fn, HyString):
ret = self.compile_atom(fn, expression)
if ret:
return ret
if fn.startswith("."):
# (.split "test test") -> "test test".split()
# Get the attribute name
ofn = fn
fn = HySymbol(ofn[1:])
fn.replace(ofn)
# Get the object we want to take an attribute from
func = self.compile(expression.pop(1))
# And get the attribute
func += ast.Attribute(lineno=fn.start_line,
col_offset=fn.start_column,
value=func.force_expr,
attr=ast_str(fn),
ctx=ast.Load())
if not func:
func = self.compile(fn)
args, ret = self._compile_collect(expression[1:])
ret += ast.Call(func=func.expr,
args=args,
2013-03-05 02:40:23 +01:00
keywords=[],
starargs=None,
kwargs=None,
lineno=expression.start_line,
col_offset=expression.start_column)
return func + ret
@builds("def")
@builds("setf")
@builds("setv")
@checkargs(2)
2013-03-06 00:16:04 +01:00
def compile_def_expression(self, expression):
expression.pop(0)
2013-03-06 00:16:04 +01:00
name = expression.pop(0)
result = self.compile(expression.pop(0))
2013-03-06 00:16:04 +01:00
if result.temp_variables and isinstance(name, HyString):
result.rename(name)
return result
2013-03-06 00:16:04 +01:00
ld_name = self.compile(name)
st_name = self._storeize(ld_name)
2013-03-06 00:16:04 +01:00
result += ast.Assign(
2013-03-06 00:16:04 +01:00
lineno=expression.start_line,
col_offset=expression.start_column,
targets=[st_name], value=result.force_expr)
result += ld_name
return result
2013-03-06 00:16:04 +01:00
@builds("foreach")
@checkargs(min=1)
2013-03-07 04:09:13 +01:00
def compile_for_expression(self, expression):
expression.pop(0) # for
target_name, iterable = expression.pop(0)
target = self._storeize(self.compile(target_name))
ret = Result()
orel = Result()
# (foreach [] body (else …))
if expression and expression[-1][0] == HySymbol("else"):
else_expr = expression.pop()
if len(else_expr) > 2:
raise HyTypeError(
else_expr,
"`else' statement in `foreach' is too long")
elif len(else_expr) == 2:
orel += self.compile(else_expr[1])
orel += orel.expr_as_stmt()
ret += self.compile(iterable)
body = self._compile_branch(expression)
body += body.expr_as_stmt()
ret += ast.For(lineno=expression.start_line,
col_offset=expression.start_column,
target=target,
iter=ret.force_expr,
body=body.stmts,
orelse=orel.stmts)
2013-03-07 04:13:14 +01:00
return ret
2013-03-07 04:09:13 +01:00
@builds("while")
@checkargs(min=2)
2013-04-03 19:55:09 +02:00
def compile_while_expression(self, expr):
expr.pop(0) # "while"
ret = self.compile(expr.pop(0))
body = self._compile_branch(expr)
body += body.expr_as_stmt()
2013-04-03 19:55:09 +02:00
ret += ast.While(test=ret.force_expr,
body=body.stmts,
2013-04-03 19:55:09 +02:00
orelse=[],
lineno=expr.start_line,
col_offset=expr.start_column)
return ret
2013-03-06 04:08:53 +01:00
@builds(HyList)
def compile_list(self, expression):
elts, ret = self._compile_collect(expression)
ret += ast.List(elts=elts,
ctx=ast.Load(),
lineno=expression.start_line,
col_offset=expression.start_column)
return ret
2013-03-06 04:08:53 +01:00
@builds("fn")
@checkargs(min=1)
def compile_function_def(self, expression):
expression.pop(0)
name = self.get_anon_fn()
arglist = expression.pop(0)
ret, args, defaults, stararg, kwargs = self._parse_lambda_list(arglist)
body = self._compile_branch(expression)
if body.expr:
body += ast.Return(value=body.expr,
lineno=body.expr.lineno,
col_offset=body.expr.col_offset)
if not body.stmts:
body += ast.Pass(lineno=expression.start_line,
col_offset=expression.start_column)
ret += ast.FunctionDef(name=name,
lineno=expression.start_line,
col_offset=expression.start_column,
args=ast.arguments(
args=[
ast.Name(
arg=ast_str(x), id=ast_str(x),
ctx=ast.Param(),
lineno=x.start_line,
col_offset=x.start_column)
for x in args],
vararg=stararg,
kwarg=kwargs,
kwonlyargs=[],
kw_defaults=[],
defaults=defaults),
body=body.stmts,
decorator_list=[])
ast_name = ast.Name(id=name,
arg=name,
ctx=ast.Load(),
lineno=expression.start_line,
col_offset=expression.start_column)
ret += Result(expr=ast_name, temp_variables=[ast_name, ret.stmts[-1]])
2013-03-05 04:35:07 +01:00
return ret
2013-03-06 00:39:34 +01:00
@builds(HyInteger)
def compile_integer(self, number):
return ast.Num(n=int(number),
lineno=number.start_line,
col_offset=number.start_column)
@builds(HyFloat)
2013-04-12 15:11:56 +02:00
def compile_float(self, number):
return ast.Num(n=float(number),
lineno=number.start_line,
col_offset=number.start_column)
@builds(HyComplex)
2013-04-12 15:11:56 +02:00
def compile_complex(self, number):
return ast.Num(n=complex(number),
2013-03-06 00:39:34 +01:00
lineno=number.start_line,
col_offset=number.start_column)
2013-03-05 02:40:23 +01:00
@builds(HySymbol)
def compile_symbol(self, symbol):
2013-03-10 01:46:32 +01:00
if "." in symbol:
glob, local = symbol.rsplit(".", 1)
glob = HySymbol(glob).replace(symbol)
ret = self.compile_symbol(glob)
2013-03-10 01:46:32 +01:00
ret = ast.Attribute(
2013-03-10 01:46:32 +01:00
lineno=symbol.start_line,
col_offset=symbol.start_column,
value=ret,
attr=ast_str(local),
2013-03-10 01:46:32 +01:00
ctx=ast.Load()
)
return ret
2013-03-10 01:46:32 +01:00
return ast.Name(id=ast_str(symbol),
arg=ast_str(symbol),
ctx=ast.Load(),
2013-03-05 02:40:23 +01:00
lineno=symbol.start_line,
col_offset=symbol.start_column)
@builds(HyString)
def compile_string(self, string):
return ast.Str(s=str_type(string),
lineno=string.start_line,
2013-03-05 02:40:23 +01:00
col_offset=string.start_column)
@builds(HyKeyword)
def compile_keyword(self, keyword):
return ast.Str(s=str_type(keyword),
lineno=keyword.start_line,
col_offset=keyword.start_column)
2013-03-09 06:55:27 +01:00
@builds(HyDict)
def compile_dict(self, m):
keyvalues, ret = self._compile_collect(sum(m.items(), ()))
2013-04-11 04:51:58 +02:00
ret += ast.Dict(lineno=m.start_line,
col_offset=m.start_column,
keys=keyvalues[::2],
values=keyvalues[1::2])
return ret
2013-04-11 05:00:42 +02:00
def hy_compile(tree, root=ast.Module, get_expr=False):
"""
Compile a HyObject tree into a Python AST Module.
If `get_expr` is True, return a tuple (module, last_expression), where
`last_expression` is the.
"""
if hasattr(sys, "subversion"):
implementation = sys.subversion[0].lower()
elif hasattr(sys, "implementation"):
implementation = sys.implementation.name.lower()
body = []
expr = None
if tree:
compiler = HyASTCompiler()
result = compiler.compile(tree)
expr = result.force_expr
if not get_expr:
result += result.expr_as_stmt()
if isinstance(tree, list):
spoof_tree = tree[0]
else:
spoof_tree = tree
body = compiler.imports_as_stmts(spoof_tree) + result.stmts
ret = root(body=body)
# PyPy _really_ doesn't like the ast going backwards...
if implementation != "cpython":
for node in ast.walk(ret):
node.lineno = 1
node.col_offset = 1
if get_expr:
expr = ast.Expression(body=expr)
ret = (ret, expr)
2013-04-11 04:51:58 +02:00
2013-03-05 02:40:23 +01:00
return ret