Skip to content

Commit fb8456b

Browse files
authored
Add async/await support to Basilisp (#342)
* Add async/await support to Basilisp * Parsing and generating * Testssss * Bust that cache * I dunno man whatever * Specify cache location specifically * Ugh * Ok go back to the old version * Small formatting change * Remove function meta code * Formatting :shame: * Keep runtime & compiler special forms in sync * Interop test
1 parent 02e2c9a commit fb8456b

File tree

12 files changed

+514
-275
lines changed

12 files changed

+514
-275
lines changed

.circleci/config.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ jobs:
77
steps:
88
- checkout
99
- restore_cache:
10-
key: deps9-{{ .Branch }}-{{ checksum "Pipfile.lock" }}
10+
key: deps-{{ .Branch }}-{{ checksum "Pipfile.lock" }}
1111
- run:
1212
name: Install tox
1313
shell: /bin/bash -leo pipefail
@@ -24,7 +24,7 @@ jobs:
2424
- save_cache:
2525
key: deps9-{{ .Branch }}-{{ checksum "Pipfile.lock" }}
2626
paths:
27-
- ".tox"
27+
- "/home/pyenv/.tox"
2828
- "/usr/local/bin"
2929
- "/usr/local/lib/python3.6/site-packages"
3030
- store_artifacts:
@@ -38,7 +38,7 @@ jobs:
3838
steps:
3939
- checkout
4040
- restore_cache:
41-
key: deps9-{{ .Branch }}-{{ checksum "Pipfile.lock" }}
41+
key: deps-{{ .Branch }}-{{ checksum "Pipfile.lock" }}
4242
- run:
4343
name: Install tox
4444
command: |
@@ -50,7 +50,7 @@ jobs:
5050
- save_cache:
5151
key: deps9-{{ .Branch }}-{{ checksum "Pipfile.lock" }}
5252
paths:
53-
- ".tox"
53+
- "/home/pyenv/.tox"
5454
- "/usr/local/bin"
5555
- "/usr/local/lib/python3.6/site-packages"
5656
- store_artifacts:

Pipfile.lock

Lines changed: 136 additions & 101 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/basilisp/cli.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def bootstrap_repl(which_ns: str) -> types.ModuleType:
7777
@cli.command(short_help="start the Basilisp REPL")
7878
@click.option(
7979
"--default-ns",
80-
default=runtime._REPL_DEFAULT_NS,
80+
default=runtime.REPL_DEFAULT_NS,
8181
help="default namespace to use for the REPL",
8282
)
8383
@click.option(
@@ -167,7 +167,7 @@ def repl(
167167
"-c", "--code", is_flag=True, help="if provided, treat argument as a string of code"
168168
)
169169
@click.option(
170-
"--in-ns", default=runtime._REPL_DEFAULT_NS, help="namespace to use for the code"
170+
"--in-ns", default=runtime.REPL_DEFAULT_NS, help="namespace to use for the code"
171171
)
172172
@click.option(
173173
"--use-var-indirection",

src/basilisp/lang/compiler/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44

55
class SpecialForm:
6+
AWAIT = sym.symbol("await")
67
CATCH = sym.symbol("catch")
78
DEF = sym.symbol("def")
89
DO = sym.symbol("do")
@@ -26,6 +27,7 @@ class SpecialForm:
2627

2728
DEFAULT_COMPILER_FILE_PATH = "NO_SOURCE_PATH"
2829

30+
SYM_ASYNC_META_KEY = kw.keyword("async")
2931
SYM_DYNAMIC_META_KEY = kw.keyword("dynamic")
3032
SYM_MACRO_META_KEY = kw.keyword("macro")
3133
SYM_NO_WARN_ON_REDEF_META_KEY = kw.keyword("no-warn-on-redef")

src/basilisp/lang/compiler/generator.py

Lines changed: 56 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,20 @@
1010
from decimal import Decimal
1111
from enum import Enum
1212
from fractions import Fraction
13-
from functools import wraps, partial
13+
from functools import partial, wraps
1414
from itertools import chain
1515
from typing import (
16-
Iterable,
17-
Pattern,
18-
Optional,
19-
List,
20-
Union,
16+
Callable,
17+
Collection,
2118
Deque,
2219
Dict,
23-
Callable,
20+
Iterable,
21+
List,
22+
Optional,
23+
Pattern,
2424
Tuple,
2525
Type,
26-
Collection,
26+
Union,
2727
)
2828

2929
import attr
@@ -46,45 +46,46 @@
4646
)
4747
from basilisp.lang.compiler.exception import CompilerException, CompilerPhase
4848
from basilisp.lang.compiler.nodes import (
49-
Node,
50-
NodeOp,
51-
ConstType,
49+
Await,
50+
Binding,
51+
Catch,
5252
Const,
53-
WithMeta,
53+
ConstType,
5454
Def,
5555
Do,
56-
If,
57-
VarRef,
56+
Fn,
57+
FnMethod,
5858
HostCall,
5959
HostField,
60-
MaybeClass,
61-
MaybeHostForm,
62-
Map as MapNode,
63-
Set as SetNode,
64-
Vector as VectorNode,
65-
Quote,
66-
ReaderLispForm,
60+
If,
61+
Import,
6762
Invoke,
68-
Throw,
69-
Try,
70-
LocalType,
71-
SetBang,
72-
Local,
7363
Let,
64+
Local,
65+
LocalType,
7466
Loop,
75-
Recur,
76-
Fn,
77-
Import,
78-
FnMethod,
79-
Binding,
67+
Map as MapNode,
68+
MaybeClass,
69+
MaybeHostForm,
70+
Node,
8071
NodeEnv,
81-
Catch,
72+
NodeOp,
73+
PyDict,
8274
PyList,
8375
PySet,
8476
PyTuple,
85-
PyDict,
77+
Quote,
78+
ReaderLispForm,
79+
Recur,
80+
Set as SetNode,
81+
SetBang,
82+
Throw,
83+
Try,
84+
VarRef,
85+
Vector as VectorNode,
86+
WithMeta,
8687
)
87-
from basilisp.lang.runtime import Var
88+
from basilisp.lang.runtime import CORE_NS, NS_VAR_NAME as LISP_NS_VAR, Var
8889
from basilisp.lang.typing import LispForm
8990
from basilisp.lang.util import count, genname, munge
9091
from basilisp.util import Maybe
@@ -96,12 +97,8 @@
9697
USE_VAR_INDIRECTION = "use_var_indirection"
9798
WARN_ON_VAR_INDIRECTION = "warn_on_var_indirection"
9899

99-
# Lisp AST node keywords
100-
INIT = kw.keyword("init")
101-
102100
# String constants used in generating code
103101
_BUILTINS_NS = "builtins"
104-
_CORE_NS = "basilisp.core"
105102
_DEFAULT_FN = "__lisp_expr__"
106103
_DO_PREFIX = "lisp_do"
107104
_FN_PREFIX = "lisp_fn"
@@ -112,7 +109,6 @@
112109
_THROW_PREFIX = "lisp_throw"
113110
_TRY_PREFIX = "lisp_try"
114111
_NS_VAR = "__NS"
115-
_LISP_NS_VAR = "*ns*"
116112

117113

118114
GeneratorException = partial(CompilerException, phase=CompilerPhase.CODE_GENERATION)
@@ -520,6 +516,15 @@ def expressionize(
520516
#################
521517

522518

519+
@_with_ast_loc_deps
520+
def _await_to_py_ast(ctx: GeneratorContext, node: Await) -> GeneratedPyAST:
521+
assert node.op == NodeOp.AWAIT
522+
expr_ast = gen_py_ast(ctx, node.expr)
523+
return GeneratedPyAST(
524+
node=ast.Await(value=expr_ast.node), dependencies=expr_ast.dependencies
525+
)
526+
527+
523528
def __should_warn_on_redef(
524529
ctx: GeneratorContext, defsym: sym.Symbol, safe_name: str, def_meta: lmap.Map
525530
) -> bool:
@@ -553,13 +558,12 @@ def _def_to_py_ast( # pylint: disable=too-many-branches
553558
defsym = node.name
554559
is_defn = False
555560

556-
if INIT in node.children:
561+
if node.init is not None:
557562
# Since Python function definitions always take the form `def name(...):`,
558563
# it is redundant to assign them to the their final name after they have
559564
# been defined under a private alias. This codepath generates `defn`
560565
# declarations by directly generating the Python `def` with the correct
561566
# function name and short-circuiting the default double-declaration.
562-
assert node.init is not None, "Def init must be defined"
563567
if node.init.op == NodeOp.FN:
564568
assert isinstance(node.init, Fn)
565569
def_ast = _fn_to_py_ast(ctx, node.init, def_name=defsym.name)
@@ -736,6 +740,7 @@ def __single_arity_fn_to_py_ast(
736740

737741
lisp_fn_name = node.local.name if node.local is not None else None
738742
py_fn_name = __fn_name(lisp_fn_name) if def_name is None else munge(def_name)
743+
py_fn_node = ast.AsyncFunctionDef if node.is_async else ast.FunctionDef
739744
with ctx.new_symbol_table(py_fn_name), ctx.new_recur_point(
740745
method.loop_id, RecurType.FN, is_variadic=node.is_variadic
741746
):
@@ -751,7 +756,7 @@ def __single_arity_fn_to_py_ast(
751756
return GeneratedPyAST(
752757
node=ast.Name(id=py_fn_name, ctx=ast.Load()),
753758
dependencies=[
754-
ast.FunctionDef(
759+
py_fn_node(
755760
name=py_fn_name,
756761
args=ast.arguments(
757762
args=fn_args,
@@ -776,6 +781,7 @@ def __multi_arity_dispatch_fn(
776781
arity_map: Dict[int, str],
777782
default_name: Optional[str] = None,
778783
max_fixed_arity: Optional[int] = None,
784+
is_async: bool = False,
779785
) -> GeneratedPyAST:
780786
"""Return the Python AST nodes for a argument-length dispatch function
781787
for multi-arity functions.
@@ -884,14 +890,15 @@ def fn(*args):
884890
),
885891
]
886892

893+
py_fn_node = ast.AsyncFunctionDef if is_async else ast.FunctionDef
887894
return GeneratedPyAST(
888895
node=ast.Name(id=name, ctx=ast.Load()),
889896
dependencies=[
890897
ast.Assign(
891898
targets=[ast.Name(id=dispatch_map_name, ctx=ast.Store())],
892899
value=ast.Dict(keys=dispatch_keys, values=dispatch_vals),
893900
),
894-
ast.FunctionDef(
901+
py_fn_node(
895902
name=name,
896903
args=ast.arguments(
897904
args=[],
@@ -910,7 +917,7 @@ def fn(*args):
910917

911918

912919
@_with_ast_loc_deps
913-
def __multi_arity_fn_to_py_ast(
920+
def __multi_arity_fn_to_py_ast( # pylint: disable=too-many-locals
914921
ctx: GeneratorContext,
915922
node: Fn,
916923
methods: Collection[FnMethod],
@@ -923,6 +930,8 @@ def __multi_arity_fn_to_py_ast(
923930
lisp_fn_name = node.local.name if node.local is not None else None
924931
py_fn_name = __fn_name(lisp_fn_name) if def_name is None else munge(def_name)
925932

933+
py_fn_node = ast.AsyncFunctionDef if node.is_async else ast.FunctionDef
934+
926935
arity_to_name = {}
927936
rest_arity_name: Optional[str] = None
928937
fn_defs = []
@@ -946,7 +955,7 @@ def __multi_arity_fn_to_py_ast(
946955
ctx, method.params, method.body
947956
)
948957
fn_defs.append(
949-
ast.FunctionDef(
958+
py_fn_node(
950959
name=arity_name,
951960
args=ast.arguments(
952961
args=fn_args,
@@ -2113,6 +2122,7 @@ def _const_node_to_py_ast(ctx: GeneratorContext, lisp_ast: Const) -> GeneratedPy
21132122

21142123

21152124
_NODE_HANDLERS: Dict[NodeOp, PyASTGenerator] = { # type: ignore
2125+
NodeOp.AWAIT: _await_to_py_ast,
21162126
NodeOp.CONST: _const_node_to_py_ast,
21172127
NodeOp.DEF: _def_to_py_ast,
21182128
NodeOp.DO: _do_to_py_ast,
@@ -2203,9 +2213,7 @@ def _from_module_import() -> ast.ImportFrom:
22032213

22042214

22052215
def _ns_var(
2206-
py_ns_var: str = _NS_VAR,
2207-
lisp_ns_var: str = _LISP_NS_VAR,
2208-
lisp_ns_ns: str = _CORE_NS,
2216+
py_ns_var: str = _NS_VAR, lisp_ns_var: str = LISP_NS_VAR, lisp_ns_ns: str = CORE_NS
22092217
) -> ast.Assign:
22102218
"""Assign a Python variable named `ns_var` to the value of the current
22112219
namespace."""

src/basilisp/lang/compiler/nodes.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
from abc import ABC, abstractmethod
22
from enum import Enum
33
from typing import (
4+
Callable,
45
Collection,
5-
Union,
6-
Optional,
7-
Iterable,
6+
Dict,
87
Generic,
9-
TypeVar,
10-
Callable,
8+
Iterable,
9+
Optional,
1110
Tuple,
12-
Dict,
11+
TypeVar,
12+
Union,
1313
)
1414

1515
import attr
@@ -48,6 +48,7 @@
4848

4949

5050
class NodeOp(Enum):
51+
AWAIT = kw.keyword("await")
5152
BINDING = kw.keyword("binding")
5253
CATCH = kw.keyword("catch")
5354
CONST = kw.keyword("const")
@@ -237,6 +238,17 @@ class NodeEnv:
237238
col: Optional[int] = None
238239

239240

241+
@attr.s(auto_attribs=True, frozen=True, slots=True)
242+
class Await(Node[ReaderLispForm]):
243+
form: ReaderLispForm
244+
expr: Node
245+
env: NodeEnv
246+
children: Collection[kw.Keyword] = vec.v(EXPR)
247+
op: NodeOp = NodeOp.AWAIT
248+
top_level: bool = False
249+
raw_forms: Collection[LispForm] = vec.Vector.empty()
250+
251+
240252
@attr.s(auto_attribs=True, frozen=True, slots=True)
241253
class Binding(Node[sym.Symbol]):
242254
form: sym.Symbol
@@ -316,6 +328,7 @@ class Fn(Node[SpecialForm]):
316328
env: NodeEnv
317329
local: Optional[Binding] = None
318330
is_variadic: bool = False
331+
is_async: bool = False
319332
children: Collection[kw.Keyword] = vec.v(METHODS)
320333
op: NodeOp = NodeOp.FN
321334
top_level: bool = False
@@ -654,6 +667,7 @@ class WithMeta(Node[LispForm]):
654667

655668

656669
ParentNode = Union[
670+
Await,
657671
Const,
658672
Def,
659673
Do,
@@ -685,6 +699,7 @@ class WithMeta(Node[LispForm]):
685699
ChildOnlyNode = Union[Binding, Catch, FnMethod, ImportAlias, Local, Recur]
686700
AnyNode = Union[ParentNode, ChildOnlyNode]
687701
SpecialFormNode = Union[
702+
Await,
688703
Def,
689704
Do,
690705
Fn,

0 commit comments

Comments
 (0)