hy/hy/compiler.py

1233 lines
41 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>
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.util import flatten_literal_list, str_type, temporary_attribute_value
2013-04-05 01:32:56 +02:00
2013-04-11 04:51:58 +02:00
from collections import defaultdict
2013-04-23 03:47:43 +02:00
import traceback
2013-04-07 04:49:48 +02:00
import codecs
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
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
_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
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):
self.returnable = False
2013-03-05 04:35:07 +01:00
self.anon_fn_count = 0
2013-04-11 04:51:58 +02:00
self.imports = defaultdict(list)
2013-03-05 02:40:23 +01:00
2013-04-21 17:43:33 +02:00
def is_returnable(self, v):
return temporary_attribute_value(self, "returnable", v)
2013-03-05 02:40:23 +01:00
def compile(self, tree):
try:
_type = type(tree)
if _type in _compile_table:
return _compile_table[_type](self, tree)
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
2013-04-10 03:33:09 +02:00
raise HyCompileError(
Exception("Unknown type: `%s'" % (str(type(tree)))))
2013-03-05 02:40:23 +01:00
def _mangle_branch(self, tree, start_line, start_column):
tree = list(flatten_literal_list(tree))
tree = list(filter(bool, tree)) # Remove empty statements
# If tree is empty, just return a pass statement
if tree == []:
2013-04-10 02:44:05 +02:00
return [ast.Pass(lineno=start_line, col_offset=start_column)]
2013-03-05 02:40:23 +01:00
tree.reverse()
ret = []
if self.returnable:
2013-03-06 03:42:54 +01:00
el = tree[0]
2013-04-05 01:32:56 +02:00
if not isinstance(el, ast.stmt):
2013-03-07 04:09:13 +01:00
el = tree.pop(0)
2013-03-05 02:40:23 +01:00
ret.append(ast.Return(value=el,
lineno=el.lineno,
col_offset=el.col_offset))
2013-04-06 01:46:27 +02:00
if isinstance(el, ast.FunctionDef):
ret.append(ast.Return(
value=ast.Name(
2013-04-10 02:44:52 +02:00
arg=el.name, id=el.name, ctx=ast.Load(),
2013-04-06 01:46:27 +02:00
lineno=el.lineno, col_offset=el.col_offset),
lineno=el.lineno, col_offset=el.col_offset))
2013-04-04 02:18:56 +02:00
for el in tree:
if isinstance(el, ast.stmt):
ret.append(el)
continue
2013-04-10 02:44:05 +02:00
ret.append(ast.Expr(
value=el,
lineno=el.lineno,
col_offset=el.col_offset))
2013-03-05 02:40:23 +01:00
ret.reverse()
return ret
def _parse_lambda_list(self, exprs):
2013-04-19 04:27:38 +02:00
""" Return FunctionDef parameter values from lambda list."""
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)
defaults.append(self.compile(v))
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)
2013-04-19 04:27:38 +02:00
return args, defaults, varargs, kwargs
2013-03-05 02:40:23 +01:00
@builds(list)
def compile_raw_list(self, entries):
return [self.compile(x) for x in entries]
def _render_quoted_form(self, form):
name = form.__class__.__name__
2013-04-11 04:51:58 +02:00
self.imports["hy"].append((name, form))
if isinstance(form, HyList):
return HyExpression(
[HySymbol(name),
HyList([self._render_quoted_form(x) for x in form])]
).replace(form)
elif isinstance(form, HySymbol):
return HyExpression([HySymbol(name), HyString(form)]).replace(form)
return HyExpression([HySymbol(name), form]).replace(form)
@builds("quote")
2013-04-10 02:34:46 +02:00
@checkargs(exact=1)
def compile_quote(self, entries):
return self.compile(self._render_quoted_form(entries[1]))
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
self.imports["hy.importer"].append(("hy_eval", expr))
2013-04-10 03:33:09 +02:00
return self.compile(HyExpression([
HySymbol("hy_eval")] + expr + [
HyExpression([HySymbol("locals")])]).replace(expr))
2013-04-10 03:33:09 +02:00
@builds("do")
@builds("progn")
2013-03-09 05:41:04 +01:00
def compile_do_expression(self, expr):
return [self.compile(x) for x in expr[1:]]
@builds("throw")
@builds("raise")
@checkargs(max=1)
def compile_throw_expression(self, expr):
expr.pop(0)
exc = self.compile(expr.pop(0)) if expr else None
return ast.Raise(
lineno=expr.start_line,
col_offset=expr.start_column,
2013-03-12 01:17:27 +01:00
type=exc,
exc=exc,
inst=None,
tback=None)
@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._code_branch(self.compile(body),
expr.start_line,
expr.start_column)
orelse = []
finalbody = []
handlers = []
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")):
handlers.append(self.compile(e))
elif e[0] == HySymbol("else"):
if orelse:
raise HyTypeError(
e,
"`try' cannot have more than one `else'")
else:
orelse = self._code_branch(self.compile(e[1:]),
e.start_line,
e.start_column)
elif e[0] == HySymbol("finally"):
if finalbody:
raise HyTypeError(
e,
"`try' cannot have more than one `finally'")
else:
finalbody = self._code_branch(self.compile(e[1:]),
e.start_line,
e.start_column)
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)])]
if sys.version_info[0] >= 3 and sys.version_info[1] >= 3:
# Python 3.3 features a merge of TryExcept+TryFinally into Try.
return ast.Try(
lineno=expr.start_line,
col_offset=expr.start_column,
body=body,
handlers=handlers,
orelse=orelse,
finalbody=finalbody)
if finalbody:
if handlers:
return 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 ast.TryFinally(
lineno=expr.start_line,
col_offset=expr.start_column,
body=body,
finalbody=finalbody)
return 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
_type = ast.Tuple(elts=[self.compile(x)
for x in exceptions_list],
lineno=expr.start_line,
col_offset=expr.start_column,
ctx=ast.Load())
else:
# [] → all exceptions catched
_type = None
elif isinstance(exceptions_list, HySymbol):
_type = self.compile(exceptions_list)
else:
raise HyTypeError(exceptions,
"`%s' needs a valid exception list" % catch)
body = self._code_branch([self.compile(x) for x in expr],
expr.start_line,
expr.start_column)
return ast.ExceptHandler(
lineno=expr.start_line,
col_offset=expr.start_column,
type=_type,
name=name,
body=body)
def _code_branch(self, branch, start_line, start_column):
return self._mangle_branch((branch
if isinstance(branch, list)
else [branch]),
start_line,
start_column)
2013-03-09 06:01:43 +01:00
@builds("if")
@checkargs(min=2, max=3)
2013-03-09 05:41:04 +01:00
def compile_if_expression(self, expr):
expr.pop(0) # if
test = self.compile(expr.pop(0))
body = self._code_branch(self.compile(expr.pop(0)),
expr.start_line,
expr.start_column)
2013-04-06 14:18:45 +02:00
if len(expr) == 1:
orel = self._code_branch(self.compile(expr.pop(0)),
expr.start_line,
expr.start_column)
else:
orel = []
2013-03-09 06:17:02 +01:00
return ast.If(test=test,
body=body,
orelse=orel,
2013-03-09 05:41:04 +01:00
lineno=expr.start_line,
col_offset=expr.start_column)
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
2013-04-02 01:51:21 +02:00
if sys.version_info[0] >= 3:
call = self.compile(call)
2013-04-02 01:51:21 +02:00
# AST changed with Python 3, we now just call it.
return ast.Call(
keywords=[],
func=call,
2013-04-02 01:51:21 +02:00
args=[self.compile(x) for x in expr],
lineno=expr.start_line,
col_offset=expr.start_column)
2013-03-10 20:39:27 +01:00
return ast.Print(
2013-03-11 14:37:29 +01:00
lineno=expr.start_line,
col_offset=expr.start_column,
2013-03-10 20:39:27 +01:00
dest=None,
values=[self.compile(x) for x in expr],
nl=True)
@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)
return ast.Assert(test=self.compile(e),
msg=None,
lineno=e.start_line,
col_offset=e.start_column)
@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(min=2)
2013-03-09 21:57:13 +01:00
def compile_lambda_expression(self, expr):
expr.pop(0)
sig = expr.pop(0)
body = expr.pop(0)
# assert expr is empty
return ast.Lambda(
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=self.compile(body))
@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)
2013-04-13 05:48:58 +02:00
value = None
if expr != []:
value = self.compile(expr.pop(0))
2013-03-15 01:55:11 +01:00
return 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)
@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):
return [
importer(
lineno=expr.start_line,
col_offset=expr.start_column,
module=ast_str(module),
2013-04-15 03:54:15 +02:00
names=names or [
ast.alias(name=ast_str(module), asname=None)
],
level=0)
]
2013-03-10 01:46:32 +01:00
expr.pop(0) # index
rimports = []
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"):
assert len(iexpr) == 2, "garbage after aliased import"
2013-04-15 03:54:15 +02:00
iexpr.pop(0) # :as
alias = iexpr.pop(0)
rimports += _compile_import(
expr,
ast_str(module),
[
ast.alias(name=ast_str(module),
asname=ast_str(alias))
]
)
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
2013-04-15 03:54:15 +02:00
names += [
ast.alias(name=ast_str(sym),
asname=alias)
]
rimports += _compile_import(expr, module,
names, ast.ImportFrom)
continue
raise TypeError("Unknown entry (`%s`) in the HyList" % (entry))
if len(rimports) == 1:
return rimports[0]
else:
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 ast.Subscript(
lineno=expr.start_line,
col_offset=expr.start_column,
value=val,
slice=ast.Index(value=sli),
ctx=ast.Load())
2013-03-19 00:47:48 +01:00
@builds("slice")
@checkargs(min=1, max=3)
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 = None
if expr != []:
low = self.compile(expr.pop(0))
high = None
if expr != []:
high = self.compile(expr.pop(0))
return ast.Subscript(
lineno=expr.start_line,
col_offset=expr.start_column,
value=val,
slice=ast.Slice(lower=low,
upper=high,
step=None),
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 = expr.pop(0)
key = expr.pop(0)
val = expr.pop(0)
return ast.Assign(
lineno=expr.start_line,
col_offset=expr.start_column,
targets=[
ast.Subscript(
lineno=expr.start_line,
col_offset=expr.start_column,
value=self.compile(target),
slice=ast.Index(value=self.compile(key)),
ctx=ast.Store())],
value=self.compile(val))
@builds("decorate_with")
@checkargs(min=1)
2013-03-10 03:01:59 +01:00
def compile_decorate_expression(self, expr):
expr.pop(0) # decorate-with
fn = self.compile(expr.pop(-1))
if type(fn) != ast.FunctionDef:
raise HyTypeError(expr, "Decorated a non-function")
2013-03-10 03:01:59 +01:00
fn.decorator_list = [self.compile(x) for x in expr]
return fn
@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
2013-03-24 15:00:07 +01:00
ret = ast.With(context_expr=ctx,
lineno=expr.start_line,
col_offset=expr.start_column,
optional_vars=thing,
body=self._code_branch(
[self.compile(x) for x in expr],
expr.start_line,
expr.start_column))
2013-03-24 15:00:07 +01:00
if sys.version_info[0] >= 3 and sys.version_info[1] >= 3:
ret.items = [ast.withitem(context_expr=ctx, optional_vars=thing)]
2013-03-24 07:04:44 +01:00
2013-03-24 15:00:07 +01:00
return ret
2013-03-24 07:04:44 +01:00
@builds(",")
def compile_tuple(self, expr):
expr.pop(0)
return ast.Tuple(elts=[self.compile(x) for x in expr],
lineno=expr.start_line,
col_offset=expr.start_column,
ctx=ast.Load())
@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)
2013-04-03 03:00:50 +02:00
cond = self.compile(expr.pop(0)) if expr != [] else None
2013-04-03 02:46:32 +02:00
ret = ast.ListComp(
lineno=expr.start_line,
col_offset=expr.start_column,
2013-04-03 02:46:32 +02:00
elt=self.compile(expression),
generators=[])
for target, iterable in targets:
ret.generators.append(ast.comprehension(
target=self._storeize(self.compile(target)),
iter=self.compile(iterable),
ifs=[]))
if cond:
ret.generators[-1].ifs.append(cond)
return ret
def _storeize(self, name):
if isinstance(name, ast.Tuple):
for x in name.elts:
x.ctx = ast.Store()
name.ctx = ast.Store()
return name
@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) != 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")
call.keywords = [ast.keyword(arg=ast_str(x),
2013-03-10 03:16:28 +01:00
value=self.compile(kwargs[x])) for x in kwargs]
2013-03-10 03:14:30 +01:00
return call
@builds("not")
@builds("~")
@checkargs(1)
def compile_unary_operator(self, expression):
ops = {"not": ast.Not,
"~": ast.Invert}
operator = expression.pop(0)
operand = expression.pop(0)
return ast.UnaryOp(op=ops[operator](),
operand=self.compile(operand),
lineno=operator.start_line,
col_offset=operator.start_column)
@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 = []
for child in expression:
2013-04-08 00:16:45 +02:00
values.append(self.compile(child))
return ast.BoolOp(op=ops[operator](),
lineno=operator.start_line,
col_offset=operator.start_column,
values=values)
@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.pop(0)
return ast.Compare(left=self.compile(e),
ops=ops,
comparators=[self.compile(x) for x in expression],
lineno=e.start_line,
col_offset=e.start_column)
@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]
left = self.compile(expression.pop(0))
calc = None
for child in expression:
calc = ast.BinOp(left=left,
op=op(),
right=self.compile(child),
lineno=child.start_line,
col_offset=child.start_column)
left = calc
return calc
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]
return ast.UnaryOp(op=ast.USub(),
operand=self.compile(arg),
lineno=arg.start_line,
col_offset=arg.start_column)
@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]))
value = self.compile(expression[2])
return ast.AugAssign(
target=target,
value=value,
op=op(),
lineno=expression.start_line,
col_offset=expression.start_column)
2013-03-10 04:04:38 +01:00
def compile_dotted_expression(self, expr):
ofn = expr.pop(0) # .join
fn = HySymbol(ofn[1:])
fn.replace(ofn)
obj = expr.pop(0) # [1 2 3 4]
return ast.Call(
func=ast.Attribute(
lineno=expr.start_line,
col_offset=expr.start_column,
value=self.compile(obj),
attr=ast_str(fn),
2013-03-10 04:04:38 +01:00
ctx=ast.Load()),
args=[self.compile(x) for x in expr],
keywords=[],
lineno=expr.start_line,
col_offset=expr.start_column,
starargs=None,
kwargs=None)
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]
2013-03-09 22:42:07 +01:00
if isinstance(fn, HyString):
if fn in _compile_table:
return _compile_table[fn](self, expression)
2013-03-05 04:35:07 +01:00
2013-03-10 20:32:27 +01:00
if expression[0].startswith("."):
return self.compile_dotted_expression(expression)
if isinstance(fn, HyKeyword):
new_expr = HyExpression(["get", expression[1], fn])
new_expr.start_line = expression.start_line
new_expr.start_column = expression.start_column
return self.compile_index_expression(new_expr)
2013-03-10 04:04:38 +01:00
2013-03-09 22:42:07 +01:00
return ast.Call(func=self.compile(fn),
2013-03-05 02:40:23 +01:00
args=[self.compile(x) for x in expression[1:]],
keywords=[],
starargs=None,
kwargs=None,
lineno=expression.start_line,
col_offset=expression.start_column)
@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) # "def"
name = expression.pop(0)
what = self.compile(expression.pop(0))
if type(what) == ast.FunctionDef:
# We special case a FunctionDef, since we can define by setting
# FunctionDef's .name attribute, rather then foo == anon_fn. This
# helps keep things clean.
what.name = ast_str(name)
2013-03-06 00:16:04 +01:00
return what
2013-04-03 02:46:32 +02:00
name = self._storeize(self.compile(name))
2013-03-06 00:16:04 +01:00
return ast.Assign(
lineno=expression.start_line,
col_offset=expression.start_column,
targets=[name], value=what)
@builds("foreach")
@checkargs(min=1)
2013-03-07 04:09:13 +01:00
def compile_for_expression(self, expression):
2013-04-21 17:43:33 +02:00
with self.is_returnable(False):
expression.pop(0) # for
name, iterable = expression.pop(0)
target = self._storeize(self.compile_symbol(name))
orelse = []
# (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:
orelse = self._code_branch(
self.compile(else_expr[1]),
else_expr[1].start_line,
else_expr[1].start_column)
ret = ast.For(lineno=expression.start_line,
col_offset=expression.start_column,
target=target,
iter=self.compile(iterable),
body=self._code_branch(
[self.compile(x) for x in expression],
expression.start_line,
expression.start_column),
orelse=orelse)
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"
test = self.compile(expr.pop(0))
2013-04-03 19:55:09 +02:00
return ast.While(test=test,
body=self._code_branch(
[self.compile(x) for x in expr],
expr.start_line,
expr.start_column),
2013-04-03 19:55:09 +02:00
orelse=[],
lineno=expr.start_line,
col_offset=expr.start_column)
2013-03-06 04:08:53 +01:00
@builds(HyList)
def compile_list(self, expr):
return ast.List(
elts=[self.compile(x) for x in expr],
ctx=ast.Load(),
lineno=expr.start_line,
col_offset=expr.start_column)
@builds("fn")
@checkargs(min=1)
2013-03-05 04:35:07 +01:00
def compile_fn_expression(self, expression):
2013-03-06 00:16:04 +01:00
expression.pop(0) # fn
2013-03-05 04:35:07 +01:00
self.anon_fn_count += 1
name = "_hy_anon_fn_%d" % (self.anon_fn_count)
sig = expression.pop(0)
2013-04-08 01:29:45 +02:00
body = []
if expression != []:
2013-04-21 17:43:33 +02:00
with self.is_returnable(True):
tailop = self.compile(expression.pop(-1))
2013-04-21 17:43:33 +02:00
with self.is_returnable(False):
for el in expression:
body.append(self.compile(el))
2013-04-08 01:29:45 +02:00
body.append(tailop)
2013-04-21 17:43:33 +02:00
with self.is_returnable(True):
body = self._code_branch(body,
expression.start_line,
expression.start_column)
args, defaults, stararg, kwargs = self._parse_lambda_list(sig)
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,
decorator_list=[])
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)
glob.replace(symbol)
return ast.Attribute(
lineno=symbol.start_line,
col_offset=symbol.start_column,
value=self.compile_symbol(glob),
attr=ast_str(local),
2013-03-10 01:46:32 +01:00
ctx=ast.Load()
)
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):
2013-04-12 20:41:29 +02:00
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):
2013-04-12 20:39:45 +02:00
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):
keys = []
vals = []
for entry in m:
keys.append(self.compile(entry))
vals.append(self.compile(m[entry]))
return ast.Dict(
lineno=m.start_line,
col_offset=m.start_column,
keys=keys,
values=vals)
2013-03-05 02:40:23 +01:00
2013-03-14 22:36:38 +01:00
def hy_compile(tree, root=None):
2013-03-05 02:40:23 +01:00
" Compile a HyObject tree into a Python AST tree. "
compiler = HyASTCompiler()
2013-03-14 22:36:38 +01:00
tlo = root
if root is None:
tlo = ast.Module
2013-04-10 03:33:09 +02:00
_ast = compiler.compile(tree)
if type(_ast) == list:
_ast = compiler._mangle_branch(_ast, 0, 0)
2013-04-11 05:11:54 +02:00
if hasattr(sys, "subversion"):
implementation = sys.subversion[0].lower()
elif hasattr(sys, "implementation"):
implementation = sys.implementation.name.lower()
2013-04-11 04:51:58 +02:00
imports = []
for package in compiler.imports:
imported = set()
syms = compiler.imports[package]
for entry, form in syms:
if entry in imported:
continue
replace = form
2013-04-11 05:11:54 +02:00
if implementation != "cpython":
# using form causes pypy to blow up; let's conditionally
# add this for cpython, since it won't go through and make
# sure the AST makes sense. Muhahaha. - PRT
replace = tree[0]
2013-04-11 04:51:58 +02:00
imported.add(entry)
imports.append(HyExpression([
HySymbol("import"),
HyList([HySymbol(package), HyList([HySymbol(entry)])])
]).replace(replace))
2013-04-11 05:00:42 +02:00
2013-04-11 04:51:58 +02:00
_ast = compiler.compile(imports) + _ast
2013-04-10 03:33:09 +02:00
ret = tlo(body=_ast)
2013-03-05 02:40:23 +01:00
return ret