public final class Reader { private let chars: [Character] private var pos: Int public init(_ source: String) { self.chars = Array(source) self.pos = 0 } public func readProgram() throws -> [Value] { var forms: [Value] = [] while true { skipAtmosphere() if pos >= chars.count { break } forms.append(try readDatum()) } return forms } public func readOne() throws -> Value? { skipAtmosphere() if pos >= chars.count { return nil } return try readDatum() } private func peek() -> Character? { pos < chars.count ? chars[pos] : nil } private func advance() -> Character { let c = chars[pos] pos += 1 return c } private func isDelimiter(_ c: Character) -> Bool { c == "(" || c == ")" || c == "\"" || c == ";" || c == "'" || c == "`" || c == "," || c.isWhitespace } private func skipAtmosphere() { while pos < chars.count { let c = chars[pos] if c.isWhitespace { pos += 1 } else if c == ";" { while pos < chars.count && chars[pos] != "\n" { pos += 1 } } else if c == "#" && pos + 1 < chars.count && chars[pos + 1] == "|" { skipBlockComment() } else if c == "#" && pos + 1 < chars.count && chars[pos + 1] == ";" { pos += 2 skipAtmosphere() _ = try? readDatum() } else { break } } } private func skipBlockComment() { pos += 2 var depth = 1 while pos < chars.count && depth > 0 { if chars[pos] == "#" && pos + 1 < chars.count && chars[pos + 1] == "|" { depth += 1 pos += 2 } else if chars[pos] == "|" && pos + 1 < chars.count && chars[pos + 1] == "#" { depth -= 1 pos += 2 } else { pos += 1 } } } private func readDatum() throws -> Value { skipAtmosphere() guard let c = peek() else { throw schemeError("unexpected end of input") } switch c { case "(", "[": return try readList(close: c == "(" ? ")" : "]") case ")", "]": throw schemeError("unexpected )") case "\"": return try readString() case "'": pos += 1 return listOf("quote", try readDatum()) case "`": pos += 1 return listOf("quasiquote", try readDatum()) case ",": pos += 1 if peek() == "@" { pos += 1 return listOf("unquote-splicing", try readDatum()) } return listOf("unquote", try readDatum()) case "#": return try readHash() default: return try readAtom() } } private func listOf(_ head: String, _ rest: Value) -> Value { cons(.symbol(head), cons(rest, .null)) } private func readList(close: Character) throws -> Value { pos += 1 var elements: [Value] = [] var tail: Value = .null while true { skipAtmosphere() guard let c = peek() else { throw schemeError("unexpected end of input in list") } if c == close || c == ")" || c == "]" { pos += 1 break } if c == "." && isDotSeparator() { pos += 1 tail = try readDatum() skipAtmosphere() guard let cc = peek(), cc == close || cc == ")" || cc == "]" else { throw schemeError("malformed dotted list") } pos += 1 break } elements.append(try readDatum()) } return arrayToList(elements, tail: tail) } private func isDotSeparator() -> Bool { guard chars[pos] == "." else { return false } let next = pos + 1 if next >= chars.count { return true } return isDelimiter(chars[next]) } private func readString() throws -> Value { pos += 1 var out = "" while pos < chars.count { let c = advance() if c == "\"" { return .string(out) } if c == "\\" { guard pos < chars.count else { break } let e = advance() switch e { case "n": out.append("\n") case "t": out.append("\t") case "r": out.append("\r") case "\\": out.append("\\") case "\"": out.append("\"") case "0": out.append("\u{0}") default: out.append(e) } } else { out.append(c) } } throw schemeError("unterminated string") } private func readHash() throws -> Value { pos += 1 guard let c = peek() else { throw schemeError("bad # token") } if c == "t" { consumeWord() return .bool(true) } if c == "f" { consumeWord() return .bool(false) } if c == "\\" { pos += 1 return try readChar() } if c == "(" { let list = try readList(close: ")") return list } throw schemeError("unsupported # syntax: #\(c)") } private func consumeWord() { while pos < chars.count && !isDelimiter(chars[pos]) { pos += 1 } } private func readChar() throws -> Value { guard pos < chars.count else { throw schemeError("bad char literal") } var name = "" name.append(advance()) while pos < chars.count && !isDelimiter(chars[pos]) { name.append(advance()) } if name.count == 1 { return .char(name.first!) } switch name.lowercased() { case "space": return .char(" ") case "newline": return .char("\n") case "tab": return .char("\t") case "return": return .char("\r") case "null", "nul": return .char("\u{0}") case "delete", "rubout": return .char("\u{7f}") default: return .char(name.first!) } } private func readAtom() throws -> Value { var token = "" while pos < chars.count && !isDelimiter(chars[pos]) { token.append(advance()) } return classifyAtom(token) } private func classifyAtom(_ token: String) -> Value { if let n = parseInteger(token) { return .integer(n) } if let rat = parseRational(token) { return rat } if let d = parseReal(token) { return .real(d) } return .symbol(token) } } func parseInteger(_ token: String) -> Int? { if token.isEmpty { return nil } if token == "+" || token == "-" || token == "..." || token == "." { return nil } return Int(token) } func parseRational(_ token: String) -> Value? { guard let slash = token.firstIndex(of: "/") else { return nil } let numPart = String(token[.. Double? { if token.isEmpty { return nil } let first = token.first! let looksNumeric = first.isNumber || first == "+" || first == "-" || first == "." guard looksNumeric else { return nil } let hasDigit = token.contains(where: { $0.isNumber }) guard hasDigit else { return nil } return Double(token) }