BigMarc e5b0977abf f
2026-06-18 16:49:59 +02:00

1.7 KiB

harper-ffi

A thin Rust static library that exposes the Harper grammar core (Apache-2.0) to Swift over a C-FFI. This directory is a placeholder for the real integration (spec §1, §2, §8).

The Swift side talks to this through the GrammarEngine protocol; today HarperEngine.swift ships a small pure-Swift rule stub so the system loop + tests run with zero native deps.

Planned shape

harper-ffi/
├─ Cargo.toml          # crate-type = ["staticlib"]; depends on `harper-core`
├─ src/lib.rs          # #[no_mangle] extern "C" fns over harper-core
└─ include/harper.h    # C header consumed by Swift (module map / bridging)

Sketch of the FFI surface:

// src/lib.rs
#[no_mangle]
pub extern "C" fn harper_check(text: *const c_char,
                               out_json: *mut *mut c_char) -> usize { /* … */ }

#[no_mangle]
pub extern "C" fn harper_free(ptr: *mut c_char) { /* … */ }

Wiring into SwiftPM

  1. Build the static lib: cargo build --release → libharper_ffi.a.
  2. Add a systemLibrary/binary target (or linkerSettings/unsafeFlags) in Package.swift pointing at the .a + the header's module map.
  3. Replace the rule set in HarperEngine.check(_:) with calls to harper_check, decoding the returned span JSON into [Suggestion].

Alternative considered: bundle harper-ls and talk to it as a subprocess over LSP. The FFI route is leaner (no process, no JSON-RPC) and is preferred.

Why Harper (not LanguageTool)

Harper is ~1/50th the memory of LanguageTool and needs no multi-GB n-gram data, so it's the right embeddable core. LanguageTool stays an optional power-user backend, never the default (spec §2).