Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions docs/QUIRKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,22 +91,29 @@ The compiler now flattens these so they have no practical length limit:
- **`Effect` and `ST`** blocks are lowered to a plain statement sequence
(magic-do).
- **Any other monad** — `Maybe`, `Either`, `State`, a custom parser/decoder —
has its `bind`/`>>=` chain **lambda-lifted**: long chains are split into
segments and each segment's continuation becomes a small named helper, so the
generated nesting stays flat regardless of chain length
([#104](https://github.com/purescript-lua/purescript-lua/issues/104)).
has its continuation chain **lambda-lifted**: long chains of any
`f action (\x -> …)` shape (`bind`/`>>=`, non-`bind` CPS, `bracket`/`with`
combinators) are split into segments and each segment's continuation becomes a
small named helper, so the generated nesting stays flat regardless of chain
length ([#104](https://github.com/purescript-lua/purescript-lua/issues/104)).
- **Applicative / flipped-bind chains** whose depth lives in a value-argument
position — `ado`/`apply` (`<*>`), `bindFlipped` (`=<<`), deep left-associated
`<>`, or any other nested call spine — are **sequentialised**: the deepest
application path is rebuilt into a flat sequence of `local` statements, sealing
a `$tmp` every ~40 applications (segmented, so each `local` stays shallow *and*
the local count stays well under Lua's 200-per-function cap)
([#108](https://github.com/purescript-lua/purescript-lua/issues/108)).

A few related shapes are **not** flattened yet and can still hit the limit at
~200+ levels in a single expression:

- **Applicative chains** built with `ado`/`apply` (`<*>`) or `bindFlipped`
(`=<<`), rather than `do`/`bind`.
- A `bind` chain that forwards more than ~15 distinct earlier-bound variables
through a single cut: the lambda-lifter **bails** (a segment's helpers carry
those forwarded variables *plus* the segment's own binders as upvalues, which
would approach Lua 5.1's 60-upvalue cap, see below) and leaves it nested.
- Deep nesting from non-`bind` constructs: a giant `case` tree, a long string
`<>` concatenation, or a very wide array/record literal.
- Deep nesting from branch constructs: a giant `case` tree (nested
`if`/`then`/`else`), whose hoisting must stay branch- and laziness-aware, or a
very wide array/record literal.

When one of these would overflow, the compiler now **rejects it with a clear
error** (`Expression nests too deeply for Lua 5.1 …`) instead of emitting a Lua
Expand Down
9 changes: 5 additions & 4 deletions exe/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,11 @@ handleLuaError =
[ "Expression nests too deeply for Lua 5.1 ("
<> show depth
<> " syntax levels; the parser caps at ~200)."
, "A long do/>>= chain in a non-Effect/ST monad is the usual cause, but"
, "applicative (ado/apply) chains, large case trees, and very wide"
, "literals can hit it too. Split the expression into smaller named"
, "pieces. See"
, "Deep do/>>=, ado/apply and =<< chains are flattened automatically;"
, "the remaining causes are large case trees, very wide literals, or a"
, "single bind chain forwarding too many variables (which makes the"
, "lambda-lifter bail on Lua's upvalue cap). Split the expression into"
, "smaller named pieces. See"
, "https://github.com/purescript-lua/purescript-lua/issues/104 and"
, "https://github.com/purescript-lua/purescript-lua/issues/108"
]
460 changes: 215 additions & 245 deletions lib/Language/PureScript/Backend/IR/FlattenDeepBinds.hs

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions lib/Language/PureScript/Backend/IR/Optimizer.hs
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,12 @@ optimizedUberModule =
-- elimination so the statements it introduces for `discard` are not
-- dropped as dead. See Language.PureScript.Backend.IR.MagicDo.
>>> magicDo
-- Flatten the remaining deeply-nested bind chains (issue #104). Runs after
-- magicDo (which consumes Effect/ST chains, leaving only non-Effect/ST
-- ones) and likewise consumes and preserves the unique naming. See
-- Language.PureScript.Backend.IR.FlattenDeepBinds.
-- Flatten the remaining deeply-nested expression trees (issues #104, #108):
-- continuation/bind chains of any monad (lambda-lifted into $kont helpers)
-- and applicative/flipped-bind application spines (A-normalised into $tmp
-- locals). Runs after magicDo (which consumes Effect/ST chains, leaving only
-- non-Effect/ST ones) and likewise consumes and preserves the unique naming.
-- See Language.PureScript.Backend.IR.FlattenDeepBinds.
>>> flattenDeepBinds

mergeForeignsIntoBindings ∷ UberModule → UberModule
Expand Down
Loading
Loading