replace RustBridge stubs with real FFI

This commit is contained in:
jess 2026-04-04 22:07:37 -07:00
parent fe3a5d60d1
commit c7b42d2f15
1 changed files with 80 additions and 140 deletions

View File

@ -9,200 +9,140 @@ struct NoteInfo: Identifiable {
class RustBridge { class RustBridge {
static let shared = RustBridge() static let shared = RustBridge()
private var notes: [UUID: String] = [:] private var docs: [UUID: OpaquePointer] = [:]
private var noteMeta: [UUID: NoteInfo] = [:]
private init() {} private init() {}
// MARK: - Document operations (stubbed)
func newDocument() -> UUID { func newDocument() -> UUID {
let id = UUID() let ptr = swiftly_doc_new()!
notes[id] = "" let uuidStr = cacheSaveRaw(ptr)
let info = NoteInfo(id: id, title: "Untitled", lastModified: Date()) let id = UUID(uuidString: uuidStr) ?? UUID()
noteMeta[id] = info docs[id] = ptr
return id return id
} }
func freeDocument(_ id: UUID) { func freeDocument(_ id: UUID) {
notes.removeValue(forKey: id) guard let ptr = docs.removeValue(forKey: id) else { return }
noteMeta.removeValue(forKey: id) swiftly_doc_free(ptr)
} }
func setText(_ id: UUID, text: String) { func setText(_ id: UUID, text: String) {
notes[id] = text guard let ptr = docs[id] else { return }
if var info = noteMeta[id] { text.withCString { cstr in
info.title = titleFromText(text) swiftly_doc_set_text(ptr, cstr)
info.lastModified = Date()
noteMeta[id] = info
} }
} }
func getText(_ id: UUID) -> String { func getText(_ id: UUID) -> String {
return notes[id] ?? "" guard let ptr = docs[id] else { return "" }
guard let cstr = swiftly_doc_get_text(ptr) else { return "" }
let str = String(cString: cstr)
swiftly_free_string(cstr)
return str
} }
func evaluate(_ id: UUID) -> [Int: String] { func evaluate(_ id: UUID) -> [Int: String] {
// Stub: parse lines for /= prefix, return placeholder results guard let ptr = docs[id] else { return [:] }
guard let text = notes[id] else { return [:] } guard let cstr = swiftly_doc_evaluate(ptr) else { return [:] }
var results: [Int: String] = [:] let json = String(cString: cstr)
let lines = text.components(separatedBy: "\n") swiftly_free_string(cstr)
for (i, line) in lines.enumerated() { return parseEvalJSON(json)
let trimmed = line.trimmingCharacters(in: .whitespaces)
if trimmed.hasPrefix("/=") {
let expr = String(trimmed.dropFirst(2)).trimmingCharacters(in: .whitespaces)
results[i] = stubbedEval(expr)
}
}
return results
} }
func evaluateLine(_ line: String) -> String { func evaluateLine(_ line: String) -> String {
let trimmed = line.trimmingCharacters(in: .whitespaces) guard let cstr = line.withCString({ swiftly_eval_line($0) }) else { return "" }
if trimmed.hasPrefix("/=") { let str = String(cString: cstr)
let expr = String(trimmed.dropFirst(2)).trimmingCharacters(in: .whitespaces) swiftly_free_string(cstr)
return stubbedEval(expr) return str
} }
return ""
}
// MARK: - File I/O (stubbed with UserDefaults)
func saveNote(_ id: UUID, path: String) -> Bool { func saveNote(_ id: UUID, path: String) -> Bool {
guard let text = notes[id] else { return false } guard let ptr = docs[id] else { return false }
do { return path.withCString { cstr in
try text.write(toFile: path, atomically: true, encoding: .utf8) swiftly_doc_save(ptr, cstr)
return true
} catch {
return false
} }
} }
func loadNote(path: String) -> (UUID, String)? { func loadNote(path: String) -> (UUID, String)? {
guard let text = try? String(contentsOfFile: path, encoding: .utf8) else { guard let ptr = path.withCString({ swiftly_doc_load($0) }) else { return nil }
let uuidStr = cacheSaveRaw(ptr)
guard let id = UUID(uuidString: uuidStr) else {
swiftly_doc_free(ptr)
return nil return nil
} }
let id = UUID() if let old = docs[id] { swiftly_doc_free(old) }
notes[id] = text docs[id] = ptr
let info = NoteInfo(id: id, title: titleFromText(text), lastModified: Date())
noteMeta[id] = info guard let cstr = swiftly_doc_get_text(ptr) else { return (id, "") }
let text = String(cString: cstr)
swiftly_free_string(cstr)
return (id, text) return (id, text)
} }
// MARK: - Cache (stubbed with local storage)
func cacheDir() -> URL {
let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
let dir = appSupport.appendingPathComponent("Swiftly", isDirectory: true)
try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
return dir
}
func cacheSave(_ id: UUID) -> Bool { func cacheSave(_ id: UUID) -> Bool {
guard let text = notes[id], let info = noteMeta[id] else { return false } guard let ptr = docs[id] else { return false }
let dir = cacheDir().appendingPathComponent(id.uuidString, isDirectory: true) guard let cstr = swiftly_cache_save(ptr) else { return false }
try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) swiftly_free_string(cstr)
let textFile = dir.appendingPathComponent("content.txt")
let metaFile = dir.appendingPathComponent("meta.json")
do {
try text.write(to: textFile, atomically: true, encoding: .utf8)
let meta: [String: String] = [
"title": info.title,
"lastModified": ISO8601DateFormatter().string(from: info.lastModified)
]
let data = try JSONSerialization.data(withJSONObject: meta)
try data.write(to: metaFile, options: .atomic)
return true return true
} catch {
return false
}
} }
func cacheLoad(_ id: UUID) -> Bool { func cacheLoad(_ id: UUID) -> Bool {
let dir = cacheDir().appendingPathComponent(id.uuidString, isDirectory: true) let uuidStr = id.uuidString.lowercased()
let textFile = dir.appendingPathComponent("content.txt") guard let ptr = uuidStr.withCString({ swiftly_cache_load($0) }) else { return false }
let metaFile = dir.appendingPathComponent("meta.json") if let old = docs[id] { swiftly_doc_free(old) }
guard let text = try? String(contentsOf: textFile, encoding: .utf8), docs[id] = ptr
let metaData = try? Data(contentsOf: metaFile),
let meta = try? JSONSerialization.jsonObject(with: metaData) as? [String: String] else {
return false
}
notes[id] = text
let formatter = ISO8601DateFormatter()
let date = formatter.date(from: meta["lastModified"] ?? "") ?? Date()
noteMeta[id] = NoteInfo(id: id, title: meta["title"] ?? "Untitled", lastModified: date)
return true return true
} }
func listNotes() -> [NoteInfo] { func listNotes() -> [NoteInfo] {
let dir = cacheDir() guard let cstr = swiftly_list_notes() else { return [] }
guard let entries = try? FileManager.default.contentsOfDirectory(at: dir, includingPropertiesForKeys: nil) else { let json = String(cString: cstr)
return [] swiftly_free_string(cstr)
} return parseNoteListJSON(json)
var result: [NoteInfo] = []
let formatter = ISO8601DateFormatter()
for entry in entries {
guard entry.hasDirectoryPath,
let uuid = UUID(uuidString: entry.lastPathComponent) else { continue }
let metaFile = entry.appendingPathComponent("meta.json")
guard let data = try? Data(contentsOf: metaFile),
let meta = try? JSONSerialization.jsonObject(with: data) as? [String: String] else { continue }
let date = formatter.date(from: meta["lastModified"] ?? "") ?? Date()
let info = NoteInfo(id: uuid, title: meta["title"] ?? "Untitled", lastModified: date)
result.append(info)
if noteMeta[uuid] == nil {
noteMeta[uuid] = info
}
}
return result.sorted { $0.lastModified > $1.lastModified }
} }
func deleteNote(_ id: UUID) { func deleteNote(_ id: UUID) {
notes.removeValue(forKey: id) freeDocument(id)
noteMeta.removeValue(forKey: id) let cacheDir = FileManager.default.homeDirectoryForCurrentUser
let dir = cacheDir().appendingPathComponent(id.uuidString, isDirectory: true) .appendingPathComponent(".swiftly/cache")
try? FileManager.default.removeItem(at: dir) let cacheFile = cacheDir.appendingPathComponent("\(id.uuidString.lowercased()).sw")
try? FileManager.default.removeItem(at: cacheFile)
} }
// MARK: - Internal // MARK: - Internal
private func titleFromText(_ text: String) -> String { private func cacheSaveRaw(_ ptr: OpaquePointer) -> String {
let firstLine = text.components(separatedBy: "\n").first ?? "" guard let cstr = swiftly_cache_save(ptr) else { return UUID().uuidString }
let trimmed = firstLine.trimmingCharacters(in: .whitespaces) let str = String(cString: cstr)
if trimmed.isEmpty { return "Untitled" } swiftly_free_string(cstr)
let clean = trimmed.replacingOccurrences(of: "^#+\\s*", with: "", options: .regularExpression) return str
let maxLen = 60
if clean.count > maxLen {
return String(clean.prefix(maxLen)) + "..."
}
return clean
} }
private func stubbedEval(_ expr: String) -> String { private func parseEvalJSON(_ json: String) -> [Int: String] {
// Stub: attempt basic arithmetic, otherwise return placeholder guard let data = json.data(using: .utf8) else { return [:] }
let components = expr.components(separatedBy: .whitespaces).filter { !$0.isEmpty } guard let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return [:] }
if components.count == 3, guard let results = obj["results"] as? [[String: Any]] else { return [:] }
let a = Double(components[0]), var dict: [Int: String] = [:]
let b = Double(components[2]) { for item in results {
let op = components[1] if let line = item["line"] as? Int, let result = item["result"] as? String {
switch op { dict[line] = result
case "+": return formatNumber(a + b)
case "-": return formatNumber(a - b)
case "*": return formatNumber(a * b)
case "/": return b != 0 ? formatNumber(a / b) : "div/0"
default: break
} }
} }
if components.count == 1, let n = Double(components[0]) { return dict
return formatNumber(n)
}
return "..."
} }
private func formatNumber(_ n: Double) -> String { private func parseNoteListJSON(_ json: String) -> [NoteInfo] {
if n == n.rounded() && abs(n) < 1e15 { guard let data = json.data(using: .utf8) else { return [] }
return String(format: "%.0f", n) guard let arr = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] else { return [] }
var notes: [NoteInfo] = []
for item in arr {
guard let uuidStr = item["uuid"] as? String,
let uuid = UUID(uuidString: uuidStr),
let title = item["title"] as? String else { continue }
let modified = item["modified"] as? Double ?? 0
let date = Date(timeIntervalSince1970: modified)
notes.append(NoteInfo(id: uuid, title: title, lastModified: date))
} }
return String(format: "%.6g", n) return notes.sorted { $0.lastModified > $1.lastModified }
} }
} }