Skip to content

feat(parser,desugar): extern abi blocks (clean — replaces #47/#53)#54

Merged
hyperpolymath merged 1 commit into
mainfrom
feat/extern-blocks-fresh
May 15, 2026
Merged

feat(parser,desugar): extern abi blocks (clean — replaces #47/#53)#54
hyperpolymath merged 1 commit into
mainfrom
feat/extern-blocks-fresh

Conversation

@hyperpolymath
Copy link
Copy Markdown
Owner

Clean replacement for #47/#53 (which were blocked by sha-different slash-paths commit on main). Cherry-picked f9f8657 onto a fresh branch off current main — single-commit PR, no force-push needed.

Close #53 alongside merging this.

Closes part of #43 (v2 grammar phase 2A).

Second slice of #43. After the slash-paths PR (#46), the next concrete
parse error on `hyperpolymath/hypatia`'s `bridge.eph` is the
`extern "gossamer" { type Window; fn window_open(…): Window; … }`
block. This commit makes that parse.

What lands:

* **Pest grammar** (`ephapax-parser/src/ephapax.pest`): new
  `extern_block` / `extern_item` / `extern_type_item` /
  `extern_fn_item` rules. Top-level `declaration` rule extended to
  admit `extern_block`. `"extern"` added to `keyword` and
  `keyword_boundary` so identifiers can't shadow it.

* **Core AST** (`ephapax-syntax`): new `Decl::Extern { abi, items }`
  variant plus `ExternItem { Type { name } | Fn { name, params,
  ret_ty } }` enum. Item params/ret carry core `Ty`.

* **Surface AST** (`ephapax-surface`): mirror
  `SurfaceDecl::Extern { abi, items, span }` and
  `SurfaceExternItem` with the same shape but `SurfaceTy`. The span
  lives on the block, not on individual items, matching how
  `DataDecl` carries one block-level span.

* **Core parser** (`lib.rs`): `parse_declaration` routes
  `Rule::extern_block` to a new `parse_extern_block` →
  `parse_extern_item` pair. The ABI string is unquoted and
  unescaped via a minimal helper that handles `\"` and `\\`
  (sufficient for ABI tags; extend if richer escapes appear).

* **Surface parser** (`surface.rs`): same plumbing against
  `SurfaceExternItem`, types staying in `SurfaceTy` until the
  desugar pass.

* **Desugar** (`ephapax-desugar`): `SurfaceDecl::Extern` lowers to
  `Decl::Extern` by walking each item and mapping its surface
  types through `desugar_ty`. Extern types pass through as-is
  (just the name). The block carries no body so there's no
  expression-level desugaring.

* **Downstream stubs** so the workspace builds: every exhaustive
  match on `Decl` now handles `Decl::Extern` — typecheck
  (no-op for now, registered as TODO), affine/linear discipline
  (no body → nothing to check), wasm codegen (skipped — wasm
  imports are phase 2B), IR s-expr printer (renders the block
  with `extern-type` / `extern-fn` tagged sub-forms), LSP
  symbol extractor (`filter_map` to drop extern blocks from the
  outline — phase 2B will expose them as navigable symbols).

Tests:

* `parse_empty_extern_block` — `extern "gossamer" { }` round-trips
  with empty items.
* `parse_extern_block_with_type_and_fn_items` — full hypatia
  shape: `type Window` + `fn window_open(title, body): Window`.
* `parse_bridge_eph_shaped_prelude` — slash-pathed module +
  extern block + regular fn all in one file (the canonical
  bridge.eph prelude).
* `test_parse_extern_block_core` — same shape through the core
  parser, asserting `ExternItem::Type` / `ExternItem::Fn` and
  param arity.
* `desugar_extern_block` — `SurfaceDecl::Extern` → `Decl::Extern`,
  with `SurfaceTy::String(region)` → `Ty::String(region)` and
  `SurfaceTy::Base(I32)` → `Ty::Base(I32)` covered.

`cargo test --workspace` passes. `cargo check --workspace` clean.

Out of scope (phase 2B follow-up, tracked in #43):

* **Typechecker**: register extern types as opaque nominal types
  and extern fns as ambient bindings in the module env so other
  decls can call them.
* **Wasm codegen**: emit `(import "<abi>" "<name>" (func ...))`
  for fn items, treat type items as opaque (i32) externs. This
  is what actually unblocks `bridge.eph → bridge.wasm`.
* **LSP**: surface extern items as navigable symbols.

After phase 2B, the `compile-eph` / `compile-affine` clap aliases
land as trivial 3-line additions (the original #36 ask), and
hypatia's `build-gossamer-gui.yml` workflow flips from
`::warning::` to an actual wasm artifact.

Stacked on #46. Rebase target switches to `main` when #46 merges.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@hyperpolymath hyperpolymath merged commit a0820ba into main May 15, 2026
10 checks passed
@hyperpolymath hyperpolymath deleted the feat/extern-blocks-fresh branch May 15, 2026 00:11
hyperpolymath added a commit that referenced this pull request May 15, 2026
#57)

Clean cherry-pick of b32887e onto a fresh branch off current main.
Replaces the cascade-closed #55. Single-commit, no conflict.

Depends on #54 (extern abi blocks) which is already on main.

Closes part of #43 (v2 grammar phase 2B-i).

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant