Close Menu
  • Home
  • AI
  • Big Data
  • Cloud Computing
  • iOS Development
  • IoT
  • IT/ Cybersecurity
  • Tech
    • Nanotechnology
    • Green Technology
    • Apple
    • Software Development
    • Software Engineering

Subscribe to Updates

Get the latest technology news from Bigteetechhub about IT, Cybersecurity and Big Data.

    What's Hot

    An Interpreter for Swift | Cocoanetics

    May 3, 2026

    Strain engineered single crystal silver films – Physics World

    May 3, 2026

    Modern transfer protocols evolving to protect cloud data

    May 2, 2026
    Facebook X (Twitter) Instagram
    Facebook X (Twitter) Instagram
    Big Tee Tech Hub
    • Home
    • AI
    • Big Data
    • Cloud Computing
    • iOS Development
    • IoT
    • IT/ Cybersecurity
    • Tech
      • Nanotechnology
      • Green Technology
      • Apple
      • Software Development
      • Software Engineering
    Big Tee Tech Hub
    Home»iOS Development»An Interpreter for Swift | Cocoanetics
    iOS Development

    An Interpreter for Swift | Cocoanetics

    big tee tech hubBy big tee tech hubMay 3, 20260016 Mins Read
    Share Facebook Twitter Pinterest Copy Link LinkedIn Tumblr Email Telegram WhatsApp
    Follow Us
    Google News Flipboard
    An Interpreter for Swift | Cocoanetics
    Share
    Facebook Twitter LinkedIn Pinterest Email Copy Link


    SwiftScript

    A few days ago I introduced SwiftBash — a sandboxed bash interpreter written in pure Swift. At the end of the four-green-checkmarks post I promised the next instalment would be about something else: SwiftScript, the same idea but for Swift itself.

    It’s exactly that. Real Swift syntax, walked by a tree-walking interpreter, no LLVM, no codegen, no Process/fork/exec — meant for the places where Swift as a compiled binary isn’t an option.

    After this success with an AST for bash, I figured, let’s up the game and try the same with Swift Syntax. My Claude Opus has proven time again that it has the necessary tenacity to make any stupid idea come true.

    Let me just say it outright: I get very sad every time somebody insists that TypeScript is the future of agentic coding. So I kept sending my wish to the universe that Swift should be also in the race for that. Now – finally – I was able to manifest the missing piece: an interpreter for the language I love.

    Why a Swift interpreter

    Swift can already be a scripting language. #!/usr/bin/env swift works today, and the toolchain even reads command-line arguments and links dynamic modules along the way. That covers the case where you have the toolchain installed and you’re allowed to compile-and-run.

    What it doesn’t cover:

    • iOS apps. App Sandbox forbids spawning processes and forbids running JIT-compiled code.
    • macOS sandboxes — same constraint.
    • Server-side hosts where you don’t ship the compiler for size or security reasons.

    Swift is too beautiful a language to leave unavailable everywhere a compile-and-exec pipeline isn’t. The same intuition that drove SwiftBash applies here: take the language an LLM (or a human) already wants to write, and make it run inside the host process, in a deterministic sandbox, with no shell-out.

    What it’s built on

    The foundation is swift-syntax — Apple’s official, source-of-truth Swift parser, which also underpins the modern Swift compiler frontend. I’d already been using it for two earlier projects:

    • SwiftButler was an early experiment with reading Swift source and reasoning about it.
    • SwiftMCP‘s macros lean heavily on AST-walking to expose Swift functions to MCP clients.

    Once you trust swift-syntax to give you the AST, “interpret it” stops sounding ridiculous and starts sounding like an afternoon project. SwiftScript has matured well past proof-of-concept since then: an interpreter walks the AST, evaluates expressions to real Swift values, and at the leaves — function calls, property accesses, initialisers — actually invokes the real system functions.

    The repo is about 30,000 lines of Swift today. Almost half of that is auto-generated bridge code. More on that in a second.

    Boxing and unboxing

    The hard part is the seam between interpreted Swift values and real Swift values. Inside the interpreter, every value is a Value — a single enum that knows how to be every shape Swift cares about:

    public indirect enum Value: @unchecked Sendable {
        case int(Int)
        case double(Double)
        case string(String)
        case bool(Bool)
        case void
        case function(Function)
        case range(lower: Int, upper: Int, closed: Bool)
        case array([Value])
        case optional(Value?)
        case tuple([Value], labels: [String?])
        case dict([DictEntry])
        case set([Value])
        /// Opaque carrier for a host-Swift value (CharacterSet, URL, Date,
        /// Data, …) that we don't model structurally.
        case opaque(typeName: String, value: Any)
        case structValue(typeName: String, fields: [StructField])
        case classInstance(ClassInstance)
        case enumValue(typeName: String, caseName: String, associatedValues: [Value])
    }

    Int, String, Double, Bool get their own cases because they’re so common. Everything Foundation hands us that we can’t decompose — URL, Date, CharacterSet, Data, Calendar, JSONEncoder — lives inside .opaque(typeName:, value: Any). The typeName is a string we use for runtime type checks; the value is the actual host instance held as Any.

    Calling URL.absoluteString from inside a script means crossing that seam:

    "var URL.absoluteString: String": .computed { receiver in
        let recv: URL = try unboxOpaque(receiver, as: URL.self, typeName: "URL")
        return .string(recv.absoluteString)
    },

    Three things to notice. First, the bridge is keyed by a string that’s a one-line summary of the Swift declaration — "var URL.absoluteString: String". The same shape works for init, func, static let, etc. It’s greppable, it’s declarative, and it matches the Swift you’d write by hand. Second, the closure is the only piece of executable logic — receive a Value, return a Value. Third, the two helpers — unboxOpaque and boxOpaque — carry the entire seam between interpreter values and host values:

    /// Wrap a host-Swift value of type `T` as a `Value.opaque`.
    func boxOpaque(_ value: T, typeName: String) -> Value {
        return .opaque(typeName: typeName, value: value)
    }
    
    /// Recover a host-Swift value from a `Value.opaque`. Verifies the boxed
    /// `typeName` matches — a type-name mismatch throws rather than risking
    /// a bad downcast.
    func unboxOpaque(_ value: Value, as: T.Type, typeName expectedName: String) throws -> T {
        guard case .opaque(let actualName, let any) = value else {
            throw RuntimeError.invalid("expected \(expectedName), got \(typeName(value))")
        }
        guard actualName == expectedName else {
            throw RuntimeError.invalid("expected \(expectedName), got \(actualName)")
        }
        guard let cast = any as? T else {
            throw RuntimeError.invalid("opaque value of type \(actualName) failed to cast")
        }
        return cast
    }

    A method with arguments looks the same in both directions:

    "func URL.appendingPathComponent()": .method { receiver, args in
        let recv: URL = try unboxOpaque(receiver, as: URL.self, typeName: "URL")
        return boxOpaque(recv.appendingPathComponent(try unboxString(args[0])),
                         typeName: "URL")
    },

    unboxString(args[0]) pulls a String back out of Value.string; the call returns a real URL; boxOpaque packs it back as Value.opaque(typeName: "URL", …). The script never sees the URL instance directly, but every operation on it is the actual Foundation method, with all its real behaviour — its NSURL bridging, its .fileURL quirks, its percent-encoding rules. We’re not reimplementing Foundation; we’re routing through it.

    The bridge generator

    You don’t write 13,000 lines of those entries by hand. Or at least: I didn’t, after writing the first two by hand, swearing audibly, and writing BridgeGeneratorTool/main.swift instead.

    It’s a 2,200-line command-line tool that:

    1. Takes one or more symbol graphs — Apple’s machine-readable JSON dump of a module’s public surface, which Xcode emits during DocC builds. Stdlib’s symbol graph and Foundation’s symbol graph give us every public type, every public function, every initialiser, every protocol conformance, every generic constraint.
    2. Walks those graphs, classifies each symbol by its shape (computed property, instance method, static method, init, failable init, throwing init, throwing async method with generics, …), and emits the appropriate .method / .computed / .staticMethod bridge entry for each.
    3. Writes two output files — one for stdlib, one for Foundation — each containing tens of thousands of entries.

    The generator handles a long tail of cases that would otherwise have eaten a month of debugging:

    • Optional-returning functions unbox their arguments, call, then box the result as .optional(boxOpaque(...)) if non-nil, .optional(nil) otherwise.
    • Throwing initialisers (init?(string:)) get an explicit failure-path bridge — return .optional(nil) when the host call returns nil.
    • Generic functions with type constraints emit a generic check at call time. The interpreter has a built-in protocol-predicate table that decides whether a Value “is” Encodable/Comparable/Sequence/etc. without trying to actually conform it:
    "Encodable":  { _ in true },          // ScriptCodable wraps any Value
    "Hashable":   { _ in true },
    "Comparable": { v in
        switch v { case .int, .double, .string: return true; default: return false }
    },
    "Sequence": { v in
        switch v {
        case .array, .set, .range, .string, .dict: return true
        default: return false
        }
    },

    Encodable returning true for everything sounds like a cheat. It isn’t — the next paragraph explains.

    Codable round-trips through real Foundation

    Script code does this all the time:

    let user = User(name: "Bob", age: 42)
    let data = try JSONEncoder().encode(user)
    print(String(data: data, encoding: .utf8)!)

    User is a struct defined inside the script. The interpreter has a Value.structValue(typeName: "User", fields: [...]) for it. JSONEncoder().encode(user) is a host call into real Foundation — and Foundation has no idea how to encode Value.

    The trick is a thin Codable adapter: a ScriptCodable wrapper that conforms to Codable and walks the Value tree itself, asking the encoder for the right container kind at each step (single-value for primitives, keyed for structs/dicts, unkeyed for arrays). Encoding is symmetric and needs no type context. Decoding is the harder direction — JSON’s {} could be any struct; null could be any optional — so the decoder reads the script type name and a back-reference to the interpreter from decoder.userInfo:

    public init(from decoder: Decoder) throws {
        guard let interp = decoder.userInfo[.scriptInterpreter] as? Interpreter,
              let typeName = decoder.userInfo[.scriptTargetType] as? String
        else { … }
        self.value = try Self.decodeValue(
            from: decoder, typeName: typeName, interp: interp
        )
    }

    What this buys: the script is using the real JSONEncoder with the real strategies (.iso8601, .convertToSnakeCase, …). We don’t reimplement the format. We don’t need to. Every Date/URL/Data quirk is Foundation’s quirk, not ours.

    The same wrapper handles PropertyListEncoder, custom Encoders the user writes, JSONDecoder from a network response — anything in the Codable ecosystem. One adapter, ~340 lines, extends to the entire serialisation surface of the standard library.

    Mirror works too

    Swift’s Mirror(reflecting: x) walks the structural shape of any value. Script code can do that on its own values:

    "init Mirror(reflecting:)": .`init` { args in
        let box = MirrorBox(reflected: args[0])
        return .opaque(typeName: "Mirror", value: box)
    },
    "var Mirror.children": .computed { recv in
        guard case .opaque(_, let any) = recv,
              let box = any as? MirrorBox else { … }
        return .array(MirrorModule.childrenOf(box.reflected))
    },

    MirrorModule.childrenOf switches on the Value enum and returns a [Value] of (label: String?, value: Value) tuples — .struct returns its fields, .classInstance walks its property cells, .array enumerates its elements with nil labels, .dict returns key/value pairs. So generic dump helpers, debug printers, and data-driven serialisers — all the patterns that lean on Mirror.children — port directly into script code with the same surface.

    KeyPaths are synthesised closures

    people.map(\.age) in real Swift uses a KeyPath. We don’t model that. Instead:

    func evaluate(keyPath: KeyPathExprSyntax) throws -> Value {
        var steps: [String] = []
        for component in keyPath.components {
            switch component.component {
            case .property(let prop):
                steps.append(prop.declName.baseName.text)
            ...
            }
        }
        // Synthesise `{ $0.steps[0].steps[1]... }` as a closure
        ...
    }

    \.age becomes a one-arg closure { $0.age }. \.address.city becomes { $0.address.city }. people.map(\.age) is then just people.map { $0.age }. The host signature map(_: (Element) -> T) accepts a Function value, and we run it under the interpreter the same way as any user-written closure. Subscript- and optional-chaining components in keypaths are surfaced as runtime-unsupported errors rather than being silently mistranslated — they’re rare in script code and faking them would be worse than rejecting them.

    Iteration, both directions

    Two adapters bridge iteration between host-Swift and script-Swift:

    ScriptSequence is a Sequence over any iterable Value — array, set, dict, string, range. Wrapping a script value gives host-Swift code something to pass to Array(_:), Set(_:), zip, prefix, and the rest of the stdlib’s algorithm surface. Bridge code that needs to walk a Value no longer has to switch on every shape.

    AsyncStreamBox goes the other way — a reference-typed carrier for an asynchronous element source, surfaced into the interpreter as .opaque("AsyncStream", box). A registered builtin captures a host AsyncIterator‘s .next() in a closure; the for-loop adapter in the interpreter drives it via try await stream.next(). That’s how URLSession.bytes(for:) becomes a script-side for await byte in stream { … } without any per-Foundation-API glue.

    Concurrency, with one honest cheat

    The interpreter’s evaluation graph is fully async throws from top to bottom — every evaluate(...) call signature suspends. So await in script code lands on Swift’s real concurrency runtime: bridged async leaves (URLSession.shared.data(...), the sleep builtin) genuinely suspend and resume.

    The cheat is Task { … }. In real Swift, Task { closure } spawns a new concurrent task and returns a handle. In SwiftScript, Task { closure } runs the closure body inline and returns .void. Why: the interpreter mutates shared state (scopes, classDefs, the bridge table, …) that isn’t Sendable. Spawning real concurrent Swift tasks would race. Inline execution + real await on leaves is the best of both worlds — script code calls bridged async APIs and gets real suspension, but the interpreter keeps its single-threaded mutation guarantee.

    actor Foo { … } declarations are registered as classes for the same reason. The single-threaded runtime has nothing to isolate, so the await keyword on actor methods is a no-op at the expression level and method dispatch goes through the same path as classes.

    Cross-platform classification, automatically

    This one is my favourite engineering touch in the project, and the reason five checkmarks light up rather than three.

    The Apple symbol graph for Foundation is huge. It includes a lot of Apple-only stuff — NSCoding, AppKit-bridged classes, things that simply don’t ship in swift-corelibs-foundation. A naive bridge generator would emit entries for those and the Linux/Windows/Android builds would fail to link.

    Hand-curating an “Apple-only” list would be tedious and inevitably stale. Instead there’s a tiny companion tool, SCLSymbolExtractor, which parses the actual source tree of swift-corelibs-foundation with swift-syntax and emits a flat list of every public type member it declares:

    Type.memberName                       (cross-platform member)
    Type.memberName  UNAVAILABLE          (declared but @available(*, unavailable))
    Type.            (cross-platform type marker; member name empty)
    .topLevelFunc                         (cross-platform free function)

    The resulting file (Resources/foundation-symbols-scl.txt, ~8000 lines) is consumed by the bridge generator, which then automatically wraps every Apple-only entry in #if canImport(Darwin). No hand-curated +Apple.swift companions; no merge conflicts when Linux’s Foundation gets a new method; the whole policy is a regenerate-from-source step.

    The companion handles the gnarly cases too: @available(*, unavailable) declarations stay marked Apple-only because Linux declares the symbol but throws at runtime; entries the generator can’t classify (no owning type, no signature) are conservatively wrapped.

    What it’s good for

    The natural niche is the place where bash starts to creak — anything that wants real numbers, structured data, or a local function with named parameters:

    struct Sample { let label: String; let values: [Double] }
    
    func mean(_ xs: [Double]) -> Double {
        xs.reduce(0, +) / Double(xs.count)
    }
    
    let samples = [
        Sample(label: "alpha", values: [12.1, 13.4, 11.9]),
        Sample(label: "beta",  values: [9.5, 8.7, 10.2]),
    ]
    
    for s in samples {
        print("\(s.label): \(mean(s.values))")
    }

    That’s a swift-script one-liner away from running. No compile step, no toolchain on the runtime host, no shell-out. The same source loaded by an iOS app, evaluated in-process, sandboxed.

    The Examples/llm_probes/ folder in the repo is a set of ten small programs an LLM might typically write — mean/stddev, primes, quadratic formula, Fibonacci, Simpson’s rule numerical integration, compound interest. They all run unmodified.

    The shebang case works:

    #!/usr/bin/env swift-script
    import Foundation
    
    let nums = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
    let sorted = nums.sorted()
    print("sorted:", sorted)
    print("mean:  ", String(format: "%.2f", Double(nums.reduce(0, +)) / Double(nums.count)))

    chmod +x it, run it directly, the binary on $PATH is swift-script instead of swift. The script never gets compiled. It runs entirely inside swift-script‘s process.

    Where it falls short

    Two big honest caveats up front:

    1. No type checking ahead of time. SwiftScript evaluates types when the call happens — when unboxOpaque(receiver, as: URL.self, typeName: "URL") runs and either gets back a URL or throws. There’s no compile pass to tell you “that’s an Int, you can’t pass it to URL.appendingPathComponent” before the script starts running. swift-syntax already gives us the structural information; the interpreter just doesn’t take advantage of it for type analysis yet. That’s the next obvious frontier.
    2. Class inheritance is approximated rather than walked through a real vtable. A subclass is registered with a recorded superclass name; method dispatch walks the chain at call time; override is checked structurally (declaring override on a method that doesn’t actually shadow a superclass method is a compile-error-equivalent runtime error). It supports the everyday patterns — override, store fields, share state via reference semantics — and even allows inheriting from bridged parents (a script class inheriting from URL or Date wraps a real native instance and falls through to the bridged surface for unmodelled members). What it doesn’t perfectly mirror is every corner of Swift’s class semantics: super chains across multiple levels behave correctly for normal calls, but exotic patterns (initialiser inheritance with required, dynamic dispatch through Self-typed return) are best-effort.

    Neither limitation is fundamental — both are work, not impossibilities.

    A few numbers

    For anyone curious about the shape of the code:

    LOC
    Total Swift in repo ~30,000
    Auto-generated stdlib bridges ~1,100
    Auto-generated Foundation bridges ~13,500
    Bridge generator tool ~2,200
    Interpreter (everything else) ~13,000
    Test suite 69 test files

    The bridge generator is the single highest-leverage piece of code in the project. Every new Foundation type Apple ships becomes available to script code by re-running the generator against the updated symbol graph; no per-type human work.

    The Resources/ directory holds three lists that drive the generation: a 126-line allowlist of types we always include, a 200-line blocklist of types/members the auto-bridge can’t handle (and where a hand-rolled bridge in Modules/ takes over), and the 8000-line foundation-symbols-scl.txt cross-platform classifier described above.

    Bitrig’s Compiler

    I would be amiss if I didn’t tip my hat to Bitrig who tackled this problem slightly differently. Instead of walking the AST tree they invented a compiler that compiles the Swift code into a form of byte code first. Then the second step executes those commands in something similar to a virtual machine. But at the end it still needs to transition to the binary world. This approach optimizes for performance because it avoids having to navigate around the tree and boxing and unboxing values.

    But premature optimization is the death of many a project, so I concentrated on making it work first. We can still worry about performance later. Bitrig is focussing on SwiftUI code that gets written on-device. My primary goal is to use Swift as first class scripting language, so performance is a lesser concern.

    What I’d love to see

    It would be wonderful if the official Swift project leaned into safe, embedded scripting as a first-class use case — a sanctioned interpreter mode, blessed bridges over Foundation and the standard library, and a clear answer to “I want to ship a Swift script that runs inside an iOS app’s sandbox without compiling code.”

    Until then, SwiftScript is what I have. The repo is over here, the README has the install line, and the same five-checkmark CI as SwiftBash now keeps it honest on macOS, iOS, Linux, Windows, and Android. As usual, I am very much interested in your thoughts in this and any of my other OSS projects.

    Like this:

    Like Loading…

    Related


    Categories: Updates



    Source link

    Cocoanetics Interpreter Swift
    Follow on Google News Follow on Flipboard
    Share. Facebook Twitter Pinterest LinkedIn Tumblr Email Copy Link
    tonirufai
    big tee tech hub
    • Website

    Related Posts

    ios – App crashing in Apple Review and they’re not sending a crash log?

    May 2, 2026

    Four Green Checkmarks: GitHub CI for macOS, iOS, Linux, and Windows

    May 1, 2026

    ios – AppShortcut is shown in Shortcuts app but not in Spotlight

    April 30, 2026
    Add A Comment
    Leave A Reply Cancel Reply

    Editors Picks

    An Interpreter for Swift | Cocoanetics

    May 3, 2026

    Strain engineered single crystal silver films – Physics World

    May 3, 2026

    Modern transfer protocols evolving to protect cloud data

    May 2, 2026

    Apple reports second quarter results

    May 2, 2026
    Timer Code
    15 Second Timer for Articles
    20
    About Us
    About Us

    Welcome To big tee tech hub. Big tee tech hub is a Professional seo tools Platform. Here we will provide you only interesting content, which you will like very much. We’re dedicated to providing you the best of seo tools, with a focus on dependability and tools. We’re working to turn our passion for seo tools into a booming online website. We hope you enjoy our seo tools as much as we enjoy offering them to you.

    Don't Miss!

    An Interpreter for Swift | Cocoanetics

    May 3, 2026

    Strain engineered single crystal silver films – Physics World

    May 3, 2026

    Subscribe to Updates

    Get the latest technology news from Bigteetechhub about IT, Cybersecurity and Big Data.

      • About Us
      • Contact Us
      • Disclaimer
      • Privacy Policy
      • Terms and Conditions
      © 2026 bigteetechhub.All Right Reserved

      Type above and press Enter to search. Press Esc to cancel.