Sources/OrgCore/Config/Lisp.swift
224 lines · 8482 bytes
1import Foundation
2
3/// Emacs Lisp read without evaluating: for importing settings from an Emacs configuration.
4public indirect enum Sexp: Equatable, Sendable {
5 case symbol(String)
6 case string(String)
7 case integer(Int)
8 case float(Double)
9 /// `?a`, as its code point.
10 case character(Int)
11 case list([Sexp])
12 /// `(a b . c)`: the elements, then the final cdr.
13 case dotted([Sexp], Sexp)
14 case vector([Sexp])
15
16 public static let `nil` = Sexp.symbol("nil")
17
18 public var symbol: String? { if case .symbol(let s) = self { return s } else { return nil } }
19 public var string: String? { if case .string(let s) = self { return s } else { return nil } }
20 public var integer: Int? { if case .integer(let i) = self { return i } else { return nil } }
21
22 /// A list's elements; `nil` is the empty list.
23 public var elements: [Sexp]? {
24 switch self {
25 case .list(let items): items
26 case .symbol("nil"): []
27 default: nil
28 }
29 }
30
31 /// The symbol a list starts with: `setq` for `(setq ...)`.
32 public var head: String? { elements?.first?.symbol }
33
34 /// Written back as Lisp, for reports.
35 public var description: String {
36 switch self {
37 case .symbol(let s): return s
38 case .string(let s): return "\"" + s.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "\"", with: "\\\"").replacingOccurrences(of: "\n", with: "\\n") + "\""
39 case .integer(let i): return String(i)
40 case .float(let d): return String(d)
41 case .character(let c): return "?" + (Unicode.Scalar(c).map { String(Character($0)) } ?? "")
42 case .list(let items):
43 if items.count == 2, let head = items[0].symbol {
44 switch head {
45 case "quote": return "'" + items[1].description
46 case "function": return "#'" + items[1].description
47 case "\\`": return "`" + items[1].description
48 case ",": return "," + items[1].description
49 case ",@": return ",@" + items[1].description
50 default: break
51 }
52 }
53 return "(" + items.map(\.description).joined(separator: " ") + ")"
54 case .dotted(let items, let last): return "(" + items.map(\.description).joined(separator: " ") + " . " + last.description + ")"
55 case .vector(let items): return "[" + items.map(\.description).joined(separator: " ") + "]"
56 }
57 }
58}
59
60public enum LispReader {
61 public struct Form: Sendable {
62 public let sexp: Sexp
63 /// 1-based line where the form starts.
64 public let line: Int
65 }
66
67 public struct ReadError: Error, Equatable, CustomStringConvertible {
68 public let line: Int
69 public let message: String
70 public var description: String { "line \(line): \(message)" }
71 }
72
73 /// The top-level forms of `text`.
74 public static func read(_ text: String) throws -> [Form] {
75 var reader = Reader(Array(text.unicodeScalars))
76 var forms: [Form] = []
77 while true {
78 reader.skipSpace()
79 guard !reader.atEnd else { break }
80 let line = reader.line
81 forms.append(Form(sexp: try reader.form(), line: line))
82 }
83 return forms
84 }
85
86 struct Reader {
87 let chars: [Unicode.Scalar]
88 var index = 0
89 var line = 1
90
91 init(_ chars: [Unicode.Scalar]) { self.chars = chars }
92
93 var atEnd: Bool { index >= chars.count }
94 var peek: Unicode.Scalar? { index < chars.count ? chars[index] : nil }
95
96 mutating func next() -> Unicode.Scalar? {
97 guard index < chars.count else { return nil }
98 let c = chars[index]
99 index += 1
100 if c == "\n" { line += 1 }
101 return c
102 }
103
104 mutating func skipSpace() {
105 while let c = peek {
106 if c == ";" {
107 while let d = peek, d != "\n" { _ = next() }
108 } else if c.properties.isWhitespace {
109 _ = next()
110 } else {
111 return
112 }
113 }
114 }
115
116 func error(_ message: String) -> ReadError { ReadError(line: line, message: message) }
117
118 static let delimiters: Set<Unicode.Scalar> = ["(", ")", "[", "]", "\"", "'", "`", ",", ";"]
119
120 mutating func form() throws -> Sexp {
121 skipSpace()
122 guard let c = next() else { throw error("unexpected end of file") }
123 switch c {
124 case "(": return try list(close: ")")
125 case "[":
126 guard case .list(let items) = try list(close: "]") else { throw error("bad vector") }
127 return .vector(items)
128 case ")", "]": throw error("unexpected \(c)")
129 case "\"": return .string(try string())
130 case "'": return .list([.symbol("quote"), try form()])
131 case "`": return .list([.symbol("\\`"), try form()])
132 case ",":
133 if peek == "@" {
134 _ = next()
135 return .list([.symbol(",@"), try form()])
136 }
137 return .list([.symbol(","), try form()])
138 case "?": return .character(try character())
139 case "#":
140 if peek == "'" {
141 _ = next()
142 return .list([.symbol("function"), try form()])
143 }
144 return .symbol("#" + atom())
145 default:
146 index -= 1
147 if c == "\n" { line -= 1 }
148 return number(atom())
149 }
150 }
151
152 mutating func list(close: Unicode.Scalar) throws -> Sexp {
153 var items: [Sexp] = []
154 while true {
155 skipSpace()
156 guard let c = peek else { throw error("unclosed list") }
157 if c == close {
158 _ = next()
159 return .list(items)
160 }
161 if c == ".", index + 1 < chars.count, chars[index + 1].properties.isWhitespace || Self.delimiters.contains(chars[index + 1]) {
162 _ = next()
163 let last = try form()
164 skipSpace()
165 guard next() == close else { throw error("bad dotted list") }
166 return .dotted(items, last)
167 }
168 items.append(try form())
169 }
170 }
171
172 mutating func string() throws -> String {
173 var out = ""
174 while let c = next() {
175 switch c {
176 case "\"": return out
177 case "\\":
178 guard let e = next() else { break }
179 switch e {
180 case "n": out += "\n"
181 case "t": out += "\t"
182 case "\n": break
183 default: out.unicodeScalars.append(e)
184 }
185 default:
186 out.unicodeScalars.append(c)
187 }
188 }
189 throw error("unclosed string")
190 }
191
192 mutating func character() throws -> Int {
193 guard let c = next() else { throw error("bad character") }
194 guard c == "\\" else { return Int(c.value) }
195 guard let e = next() else { throw error("bad character") }
196 switch e {
197 case "n": return 10
198 case "t": return 9
199 case "s": return 32
200 case "e": return 27
201 case "C", "M", "S", "H", "A":
202 if peek == "-" { _ = next(); return try character() }
203 return Int(e.value)
204 default: return Int(e.value)
205 }
206 }
207
208 mutating func atom() -> String {
209 var out = ""
210 while let c = peek, !c.properties.isWhitespace, !Self.delimiters.contains(c) {
211 _ = next()
212 if c == "\\", let escaped = next() { out.unicodeScalars.append(escaped) } else { out.unicodeScalars.append(c) }
213 }
214 return out
215 }
216
217 func number(_ text: String) -> Sexp {
218 if let i = Int(text.hasPrefix("+") ? String(text.dropFirst()) : text) { return .integer(i) }
219 if text.contains(where: { $0 == "." || $0 == "e" }), text.first.map({ $0.isNumber || $0 == "-" || $0 == "+" || $0 == "." }) == true,
220 let d = Double(text) { return .float(d) }
221 return .symbol(text)
222 }
223 }
224}