Skip to content

Replace the lexical foreign splitter with a real Lua 5.1 parser producing the Lua AST #173

Description

@Unisay

Problem

Lua.Linker.Foreign does not parse foreign files — it lexically splits them into a header text and (Key, Text) export pairs, extracting each value as a raw text blob by counting balanced parentheses (hence the "every value is wrapped in parens" contract in Note [Foreign module source format]). The blobs are embedded into the Lua AST as opaque verbatim nodes, so every optimization stops at a foreign boundary.

The measurable cost: when the inliner pastes a foreign lambda into an application site, the result is an applied function literal the optimizer cannot see into. Golden.Fibonacci compiles every addition to (function(x) return function(y) return x + y end end)(a)(b) — two closure allocations and two calls per arithmetic op. fib(30) runs 8.3x slower than handwritten Lua under PUC 5.1; under LuaJIT the per-call FNEW aborts every trace recording and gets the loop blacklisted, so the JIT contributes nothing.

Approach

Implement our own Lua 5.1 parser (megaparsec is already a dependency) targeting the existing Lua.Types AST:

  1. Full Lua 5.1 expression/statement grammar (it is small). Each foreign export value parses into an AST expression, the header into statements.
  2. Comment preservation: comments attach to AST nodes via the existing annotation slots, so printed output keeps the load-bearing FFI comments and structural goldens stay close. Formatting/blank-line fidelity is a non-goal.
  3. The balanced-paren scanner and the parens-around-values contract become unnecessary; the overall file format contract (optional header, then return { ... }) stays.
  4. Foreign files get compile-time syntax validation — today a syntax error inside a value blob is caught only by luacheck or at runtime.

Decision recorded: own parser, not hackage language-lua — the values must land in our Lua.Types with comments attached, and converting from a third-party comment-less AST buys nothing. This parser is a standing decision for the series: later issues build on foreign values being real AST.

Prerequisites / Relations

Infrastructure with no hard prerequisite (megaparsec is already a dependency). What it unlocks:

Verification / Measurement

The fib(30) macrobench and FNEW counters from #172 show the win directly: the foreign IIFE ((function(x) return function(y) return x + y end end)(a)(b), two closure allocations + two calls per add) collapses, and the per-call FNEW that aborts LuaJIT trace recording disappears. Separately, a foreign file with a syntax error inside a value now fails at compile time rather than only under luacheck or at runtime.

Metadata

Metadata

Assignees

Labels

OptimisationA Compiler Optimisationarea: codegenLua code generation / printing

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions