BigMarc 3ea090ae4b Embed the real Harper grammar core via Rust C-FFI
Replace the NSSpellChecker + regex-stub engine with harper-core 2.5.0:
real subject-verb agreement, verb forms, punctuation, repetition and
spelling. New harper-ffi crate (staticlib) exposes harper_check over a C
ABI; the CHarper module bridges it to Swift; HarperEngine maps Harper's
char spans onto UTF-16 ranges and de-dups overlapping lints. NSSpellChecker
stays only as a case-preserving spelling supplement, which fixes the
problemm->Problem mis-casing. Bulk apply now skips no-op suggestions.

build.sh / scripts/build.sh build the universal static lib; target/ and
lib/ are gitignored (regenerate via harper-ffi/build.sh).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 06:43:11 +02:00
..

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).