|
83 | 83 | _INTEROP_CALL = sym.symbol(".")
|
84 | 84 | _INTEROP_PROP = sym.symbol(".-")
|
85 | 85 | _LET = sym.symbol("let*")
|
| 86 | +_LOOP = sym.symbol("loop*") |
86 | 87 | _QUOTE = sym.symbol("quote")
|
87 | 88 | _RECUR = sym.symbol("recur")
|
88 | 89 | _THROW = sym.symbol("throw")
|
|
97 | 98 | _INTEROP_CALL,
|
98 | 99 | _INTEROP_PROP,
|
99 | 100 | _LET,
|
| 101 | + _LOOP, |
100 | 102 | _QUOTE,
|
101 | 103 | _RECUR,
|
102 | 104 | _THROW,
|
@@ -864,7 +866,7 @@ def _assert_recur_is_tail(ctx: CompilerContext, form: lseq.Seq) -> None: # noqa
|
864 | 866 | _assert_recur_is_tail(ctx, lseq.sequence([runtime.nth(child, 3)]))
|
865 | 867 | except IndexError:
|
866 | 868 | pass
|
867 |
| - elif child.first == _LET: |
| 869 | + elif child.first in {_LET, _LOOP}: |
868 | 870 | for binding, val in partition(runtime.nth(child, 1), 2):
|
869 | 871 | _assert_no_recur(ctx, lseq.sequence([binding]))
|
870 | 872 | _assert_no_recur(ctx, lseq.sequence([val]))
|
@@ -1468,6 +1470,110 @@ def let_32(a_33, b_34, c_35):
|
1468 | 1470 | yield _node(ast.Call(func=_load_attr(outer_letname), args=[], keywords=[]))
|
1469 | 1471 |
|
1470 | 1472 |
|
| 1473 | +def _loop_ast( # pylint:disable=too-many-locals |
| 1474 | + ctx: CompilerContext, form: llist.List |
| 1475 | +) -> ASTStream: |
| 1476 | + """Generate a Python AST node for a loop special form. |
| 1477 | +
|
| 1478 | + Python code for a `loop*` binding like this: |
| 1479 | + (loop [s "abc" |
| 1480 | + len 0] |
| 1481 | + (if (seq s) |
| 1482 | + (recur (rest s) |
| 1483 | + (inc len)) |
| 1484 | + len)) |
| 1485 | +
|
| 1486 | + Should look roughly like this: |
| 1487 | + def loop_14(): |
| 1488 | +
|
| 1489 | + def loop_15(s_16, len__17): |
| 1490 | +
|
| 1491 | + def lisp_if_19(): |
| 1492 | + if_test_18 = basilisp.core.seq(s_16) |
| 1493 | + if None is if_test_18 or False is if_test_18: |
| 1494 | + return len__17 |
| 1495 | + else: |
| 1496 | + return runtime_5._TrampolineArgs(False, basilisp.core.rest( |
| 1497 | + s_16), basilisp.core.inc(len__17)) |
| 1498 | + return lisp_if_19() |
| 1499 | + s_12 = 'abc' |
| 1500 | + len__13 = 0 |
| 1501 | + return runtime_5._trampoline(loop_15)(s_12, len__13)""" |
| 1502 | + assert form.first == _LOOP |
| 1503 | + assert isinstance(form[1], vec.Vector) |
| 1504 | + assert len(form) >= 3 |
| 1505 | + |
| 1506 | + # For a better description of what's going on below, peek up at _let_ast. |
| 1507 | + with ctx.new_symbol_table(genname("loop_st")) as st: |
| 1508 | + bindings = list(partition(form[1], 2)) |
| 1509 | + |
| 1510 | + arg_syms: Dict[ |
| 1511 | + sym.Symbol, str |
| 1512 | + ] = OrderedDict() # Mapping of binding symbols (turned into function parameter names) to munged name # noqa: E501 |
| 1513 | + var_names = ( |
| 1514 | + [] |
| 1515 | + ) # Names of local Python variables bound to computed expressions prior to the function call |
| 1516 | + arg_deps = [] # Argument expression dependency nodes |
| 1517 | + arg_exprs = [] # Bound expressions are the expressions a name is bound to |
| 1518 | + for s, expr in bindings: |
| 1519 | + # Keep track of only the newest symbol and munged name in arg_syms, that way |
| 1520 | + # we are only calling the loop binding below with the most recent entry. |
| 1521 | + munged = genname(munge(s.name)) |
| 1522 | + arg_syms[s] = munged |
| 1523 | + var_names.append(munged) |
| 1524 | + |
| 1525 | + expr_deps, expr_node = _nodes_and_expr(_to_ast(ctx, expr)) |
| 1526 | + |
| 1527 | + # Don't add the new symbol until after we've processed the expression |
| 1528 | + _new_symbol(ctx, s, munged, _SYM_CTX_LOCAL, st=st) |
| 1529 | + |
| 1530 | + arg_deps.append(expr_deps) |
| 1531 | + arg_exprs.append(_unwrap_node(expr_node)) |
| 1532 | + |
| 1533 | + # Generate an outer function to hold the entire loop expression (including bindings). |
| 1534 | + # We need to do this to guarantee that no binding expressions are executed as part of |
| 1535 | + # an assignment as a dependency node. This eager evaluation could leak out as part of |
| 1536 | + # (at least) if statements dependency nodes. |
| 1537 | + outer_loopname = genname("loop") |
| 1538 | + loop_fn_body: List[ast.AST] = [] |
| 1539 | + |
| 1540 | + # Generate a function to hold the body of the loop expression |
| 1541 | + loopname = genname("loop") |
| 1542 | + |
| 1543 | + with ctx.new_recur_point("loop", vec.vector(arg_syms.keys())): |
| 1544 | + # Suppress shadowing warnings below since the shadow warnings will be |
| 1545 | + # emitted by calling _new_symbol in the loop above |
| 1546 | + args, body, vargs = _fn_args_body( |
| 1547 | + ctx, |
| 1548 | + vec.vector(arg_syms.keys()), |
| 1549 | + runtime.nthrest(form, 2), |
| 1550 | + warn_on_shadowed_var=False, |
| 1551 | + warn_on_shadowed_name=False, |
| 1552 | + ) |
| 1553 | + loop_fn_body.append(_expressionize(body, loopname, args=args, vargs=vargs)) |
| 1554 | + |
| 1555 | + # Generate local variable assignments for processing loop bindings |
| 1556 | + var_names = seq(var_names).map(lambda n: ast.Name(id=n, ctx=ast.Store())) |
| 1557 | + for name, deps, expr in zip(var_names, arg_deps, arg_exprs): |
| 1558 | + loop_fn_body.extend(_unwrap_nodes(deps)) |
| 1559 | + loop_fn_body.append(ast.Assign(targets=[name], value=expr)) |
| 1560 | + |
| 1561 | + loop_fn_body.append( |
| 1562 | + ast.Call( |
| 1563 | + func=ast.Call( |
| 1564 | + func=_TRAMPOLINE_FN_NAME, args=[_load_attr(loopname)], keywords=[] |
| 1565 | + ), |
| 1566 | + args=seq(arg_syms.values()) |
| 1567 | + .map(lambda n: ast.Name(id=n, ctx=ast.Load())) |
| 1568 | + .to_list(), |
| 1569 | + keywords=[], |
| 1570 | + ) |
| 1571 | + ) |
| 1572 | + |
| 1573 | + yield _dependency(_expressionize(loop_fn_body, outer_loopname)) |
| 1574 | + yield _node(ast.Call(func=_load_attr(outer_loopname), args=[], keywords=[])) |
| 1575 | + |
| 1576 | + |
1471 | 1577 | def _quote_ast(ctx: CompilerContext, form: llist.List) -> ASTStream:
|
1472 | 1578 | """Generate a Python AST Node for quoted forms.
|
1473 | 1579 |
|
@@ -1676,49 +1782,32 @@ def _var_ast(_: CompilerContext, form: llist.List) -> ASTStream:
|
1676 | 1782 | yield _node(ast.Call(func=_FIND_VAR_FN_NAME, args=[base_sym], keywords=[]))
|
1677 | 1783 |
|
1678 | 1784 |
|
| 1785 | +_SPECIAL_FORM_HANDLERS: Dict[ |
| 1786 | + sym.Symbol, Callable[[CompilerContext, llist.List], ASTStream] |
| 1787 | +] = { |
| 1788 | + _DEF: _def_ast, |
| 1789 | + _FN: _fn_ast, |
| 1790 | + _IF: _if_ast, |
| 1791 | + _IMPORT: _import_ast, |
| 1792 | + _INTEROP_CALL: _interop_call_ast, |
| 1793 | + _INTEROP_PROP: _interop_prop_ast, |
| 1794 | + _DO: _do_ast, |
| 1795 | + _LET: _let_ast, |
| 1796 | + _LOOP: _loop_ast, |
| 1797 | + _QUOTE: _quote_ast, |
| 1798 | + _RECUR: _recur_ast, |
| 1799 | + _THROW: _throw_ast, |
| 1800 | + _TRY: _try_ast, |
| 1801 | + _VAR: _var_ast, |
| 1802 | +} |
| 1803 | + |
| 1804 | + |
1679 | 1805 | def _special_form_ast(ctx: CompilerContext, form: llist.List) -> ASTStream:
|
1680 | 1806 | """Generate a Python AST Node for any Lisp special forms."""
|
1681 | 1807 | assert form.first in _SPECIAL_FORMS
|
1682 |
| - which = form.first |
1683 |
| - if which == _DEF: |
1684 |
| - yield from _def_ast(ctx, form) |
1685 |
| - return |
1686 |
| - elif which == _FN: |
1687 |
| - yield from _fn_ast(ctx, form) |
1688 |
| - return |
1689 |
| - elif which == _IF: |
1690 |
| - yield from _if_ast(ctx, form) |
1691 |
| - return |
1692 |
| - elif which == _IMPORT: |
1693 |
| - yield from _import_ast(ctx, form) # type: ignore |
1694 |
| - return |
1695 |
| - elif which == _INTEROP_CALL: |
1696 |
| - yield from _interop_call_ast(ctx, form) |
1697 |
| - return |
1698 |
| - elif which == _INTEROP_PROP: |
1699 |
| - yield from _interop_prop_ast(ctx, form) |
1700 |
| - return |
1701 |
| - elif which == _DO: |
1702 |
| - yield from _do_ast(ctx, form) |
1703 |
| - return |
1704 |
| - elif which == _LET: |
1705 |
| - yield from _let_ast(ctx, form) |
1706 |
| - return |
1707 |
| - elif which == _QUOTE: |
1708 |
| - yield from _quote_ast(ctx, form) |
1709 |
| - return |
1710 |
| - elif which == _RECUR: |
1711 |
| - yield from _recur_ast(ctx, form) |
1712 |
| - return |
1713 |
| - elif which == _THROW: |
1714 |
| - yield from _throw_ast(ctx, form) |
1715 |
| - return |
1716 |
| - elif which == _TRY: |
1717 |
| - yield from _try_ast(ctx, form) |
1718 |
| - return |
1719 |
| - elif which == _VAR: |
1720 |
| - yield from _var_ast(ctx, form) |
1721 |
| - return |
| 1808 | + handle_special_form = _SPECIAL_FORM_HANDLERS.get(form.first, None) |
| 1809 | + if handle_special_form: |
| 1810 | + return handle_special_form(ctx, form) |
1722 | 1811 | raise CompilerException("Special form identified, but not handled") from None
|
1723 | 1812 |
|
1724 | 1813 |
|
|
0 commit comments