Skip to main content

Phase 12. FFI (file I/O)

FieldValue
MEPMEP-49 §Phases · Phase 12
StatusLANDED
Started2026-05-28 13:40 (GMT+7)
Landed2026-05-28 13:40 (GMT+7)
Tracking issue—
Tracking PR—

Gate​

TestPhase12FFI: 5 fixtures green on Swift 6.0+, macOS 15. Gate builds each fixture and compares stdout to .expected.

Goal-alignment audit​

Phase 12 ships the file I/O primitives that are the simplest and most universally needed FFI operations. mochiReadFile, mochiWriteFile, mochiAppendFile, and mochiLines cover the fixture suite. These are implemented as plain synchronous Foundation wrappers, keeping the generated code simple while matching the behaviour of the BEAM and C backends. C FFI (module maps, @_silgen_name, unsafe pointers) and Swift library FFI are deferred.

Sub-phases​

#ScopeStatusCommit
12.0ReadFileExpr → mochiReadFile(path)LANDEDmep/0049-phase-12
12.1WriteFileStmt → mochiWriteFile(path, content)LANDEDmep/0049-phase-12
12.2AppendFileStmt → mochiAppendFile(path, content)LANDEDmep/0049-phase-12
12.3LinesExpr → mochiLines(path)LANDEDmep/0049-phase-12
12.4C FFI: module.modulemap, @_silgen_name, ClangImporterDEFERRED—
12.5Swift library FFI: external module → SwiftPM .package dependencyDEFERRED—
12.6Export to C: @export fun foo() → @_cdecl("foo")DEFERRED—

Sub-phase 12.0 -- Read file​

Decisions made (12.0)​

mochiReadFile(_ path: String) -> String: reads the entire file at path, returning its content as a UTF-8 string. Returns "" on error.

public func mochiReadFile(_ path: String) -> String {
(try? String(contentsOfFile: path, encoding: .utf8)) ?? ""
}

The lowerer emits mochiReadFile(pathExpr) as a RawSwiftExpr.

Sub-phase 12.1 -- Write file​

Decisions made (12.1)​

mochiWriteFile(_ path: String, _ content: String): writes content atomically to path, overwriting any existing file.

public func mochiWriteFile(_ path: String, _ content: String) {
try? content.write(toFile: path, atomically: true, encoding: .utf8)
}

The lowerer emits mochiWriteFile(path, content) as a RawSwiftStmt.

Sub-phase 12.2 -- Append file​

Decisions made (12.2)​

mochiAppendFile(_ path: String, _ content: String): opens the file for appending (creates it if it does not exist).

public func mochiAppendFile(_ path: String, _ content: String) {
if let data = content.data(using: .utf8) {
if FileManager.default.fileExists(atPath: path) {
if let fh = FileHandle(forWritingAtPath: path) {
fh.seekToEndOfFile()
fh.write(data)
fh.closeFile()
}
} else {
try? content.write(toFile: path, atomically: true, encoding: .utf8)
}
}
}

Sub-phase 12.3 -- Lines​

Decisions made (12.3)​

mochiLines(_ path: String) -> [String]: reads the file and splits by \n. Strips the trailing empty element left by files ending with a newline.

public func mochiLines(_ path: String) -> [String] {
guard let content = try? String(contentsOfFile: path, encoding: .utf8) else { return [] }
let lines = content.components(separatedBy: "\n")
if lines.last == "" { return Array(lines.dropLast()) }
return lines
}

Files changed​

FilePurpose
transpiler3/swift/lower/lower.goReadFileExpr, WriteFileStmt, AppendFileStmt, LinesExpr lowering
transpiler3/swift/runtime/Sources/MochiRuntime/FileIO.swiftmochiReadFile, mochiWriteFile, mochiAppendFile, mochiLines
transpiler3/swift/build/phase12_test.goTestPhase12FFI: 5 fixtures
tests/transpiler3/swift/fixtures/phase12-ffi/5 fixture directories

Test set​

  • TestPhase12FFI -- 5 fixtures: ffi_append, ffi_lines, ffi_newlines, ffi_overwrite, ffi_write_read.

Deferred work​

  • C FFI: module.modulemap, @_silgen_name, ClangImporter, unsafe pointer bridging. Deferred to Phase 12.4.
  • Swift library FFI: external module "url" → SwiftPM .package + .product dependency. Deferred to Phase 12.5.
  • Export to C: @export fun foo() → @_cdecl("mochi_foo") + generated MochiExports.h. Deferred to Phase 12.6.
  • Objective-C bridging header. Deferred.
  • UnsafePointer<T>, UnsafeMutablePointer<T>, UnsafeRawPointer. Deferred.