From Lisp Parsers to SwiftUI: Unlocking Single Source of Truth and Language Fundamentals
This article explores the concept of Single Source of Truth in SwiftUI, explains the nature of computer programs, compares compiled and interpreted languages, discusses Turing completeness and formal language history, demonstrates a simple Lisp interpreter, and examines how Xcode leverages ASTs for live SwiftUI previews, highlighting the benefits of a code‑out approach.
Background 1: Define SSOT
Q: What is the meaning of Single Source of Truth (SSOT) in the context of SwiftUI? A: With SwiftUI you can edit the UI either programmatically or with a design tool, both producing SwiftUI code, so there is only one source – the code – eliminating the risk of UI and code getting out of sync.
Reference: https://stackoverflow.com/questions/58398373/what-is-the-meaning-of-single-source-of-truth-ssot-in-the-context-of-swiftui
Background 2: What is a program and language
What is a computer program
A computer program is a sequence or set of instructions in a programming language for a computer to execute.
What is a compiled language
A compiled language is a programming language implemented via a compiler that translates code into machine code before execution, unlike interpreted languages which run code line‑by‑line.
What is an interpreted language
An interpreted language executes code directly line‑by‑line using an interpreter, without prior compilation to machine code.
Mathematical model and history of programming languages
Every language should be Turing‑complete. Formal languages have been studied since before computers; the first formal language was Gottlob Frege’s Begriffsschrift (1879). Languages evolved from punched cards to machine code, assembly, Lisp, C, Java, and modern languages like Swift, Go, Rust, each adding new abstractions.
The first use of formal language is thought to be Gottlob Frege’s 1879 Begriffsschrift.
Simple languages can be Turing‑complete (e.g., SNUSP). Some widely used languages lack full Turing completeness due to missing minimalization operations.
Language development progressed from simple to complex, from easy to learn to expressive, from non‑practical to practical, and from easy to parse to hard to parse.
Each language has trade‑offs, often described by a “three‑way paradox”.
Examples: JavaScript offers strong expressiveness but lower performance; Rust provides ownership and higher performance at the cost of learning complexity.
Background 3: A simple interpreted language implementation
Using Lisp as an example, a self‑recursive parser tokenizes source code, builds an AST, and evaluates it recursively.
// semantics: (60 * 9 / 5) + 32
(+ (* (/ 9 5) 60) 32)Tokenization result:
[
"(", "+", "(", "*", "(", "/", "9", "5", ")", "60", ")", "32", ")"
]Parsing produces an AST:
[
"+",
[
"*",
[
"/",
9,
5
],
60
],
32
]Evaluation proceeds by recursively reducing the AST to the final value 140.
// evaluation steps...
140A full demo is available at https://gist.github.com/Enichan/4a9fa87aef6405e13e1c072baa117beb
function interp(x, env) {
env = env || g;
if (typeof x === "string") {
return env.find(x)[x];
} else if (!Array.isArray(x)) {
return x;
} else if (x[0] === "quote") {
let exp = x[1];
return exp;
} else if (x[0] === "if") {
let test = x[1], conseq = x[2], alt = x[3];
let exp = interp(test, env) ? conseq : alt;
return interp(exp, env);
} else if (x[0] === "define") {
let symbol = x[1], exp = x[2];
env[symbol] = interp(exp, env);
} else if (x[0] === "set!") {
let symbol = x[1], exp = x[2];
return (env.find(symbol)[symbol] = interp(exp, env));
} else if (x[0] === "eval") {
let exp = interp(x[1], env);
return interp(exp, env);
} else if (x[0] === "lambda") {
let parms = x[1], body = x[2];
return makeProc(parms, body, env);
} else {
let proc = interp(x[0], env);
let args = x.slice(1).map(exp => interp(exp, env));
if (typeof proc !== "function") {
throw new Error("Expected function, got " + (proc || "").toString());
}
return proc.apply(proc, args);
}
}Adding a wrapper can capture a call stack for debugging.
let stack = [];
function wrap(func) {
return function(...args) {
stack.push(args[0]);
let resp = func(...args);
stack.pop();
return resp;
};
}
interp = wrap(interp);This illustrates how stack overflow occurs when recursion depth exceeds the call stack.
Similar self‑recursive parsing is used in kunlun‑fe’s parseComponentMeta.ts to build UI meta ASTs.
export function parseComponentMeta(meta, components = {}, ...) {
const { name, type, children, events, selectors: selectorMeta } = meta;
// ... processing logic ...
}UI Meta or UIDL can be seen as an AST that a parser interprets.
XCode + SwiftUI UI Design Experience
Experience
Principle
XCode compiles the View struct while providing a live preview, exposing the AST and runtime stack.
Clicking code highlights the corresponding AST node, revealing the UI instance’s type and value.
Clicking a UI component reflects back to the AST node.
Modifying a value updates the AST, which is then regenerated into code and refreshed.
Each change triggers a hot compile and preview, offering fast feedback.
Benefits
Code‑Out: The output is real code that goes through the compiler, yielding performance comparable to LLVM‑based languages.
Swift as SSOT: Developers can always fall back to hand‑written code; SwiftUI unifies design and implementation.
Ecosystem integration: Tools like Copilot, dependency analyzers, and language servers can extend the workflow, similar to plugin architectures in game engines.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
