Sources/OrgCore/Compute/BabelExpand.swift
401 lines · 20963 bytes
21 symbols in this file
1import Foundation
2
3// `org-babel-expand-body:LANG` of ob-C (C, C++, cpp, D), ob-java, ob-fortran and ob-clojure
4// (Org 9.8.7), which tangling and the `:cache` hash use. These languages don't run.
5
6extension Babel {
7 static let expandedLanguages: Set<String> = ["C", "C++", "cpp", "D", "java", "fortran", "clojure"]
8
9 /// The body as the language's `org-babel-expand-body:` writes it, or nil for other languages.
10 static func languageExpansion(_ body: String, language: String, tables: Tables, params: Params) throws -> String? {
11 switch language {
12 case "C", "C++", "cpp": return try expandC(body, d: false, tables: tables, params: params)
13 case "D": return try expandC(body, d: true, tables: tables, params: params)
14 case "java": return try expandJava(body, vars: tables.vars, params: params)
15 case "fortran": return try expandFortran(body, vars: tables.vars, params: params)
16 case "clojure": return expandClojure(body, vars: tables.vars, params: params)
17 default: return nil
18 }
19 }
20
21 enum HeaderValue {
22 case string(String)
23 case items([String])
24
25 /// `split-string` of a string, or the items.
26 var words: [String] {
27 switch self {
28 case .string(let s): s.split(whereSeparator: \.isWhitespace).map(String.init)
29 case .items(let items): items
30 }
31 }
32 }
33
34 /// A header value as `org-babel-read` gives it: a string, or the items of a Lisp list;
35 /// nil when absent.
36 static func headerValue(_ key: String, _ params: Params) throws -> HeaderValue? {
37 guard let raw = params.single[key] else { return nil }
38 var value = params.evaluated[key]
39 if value == nil, params.lisp.contains(key) {
40 guard let form = try? LispReader.readFirst(raw).sexp, let result = try? Elisp().eval(form) else {
41 throw Failure.message("\(key) \(raw) is Lisp that only Emacs can evaluate.")
42 }
43 value = result
44 }
45 guard let value else { return .string(raw) }
46 if let s = value.string { return .string(s) }
47 if Elisp.isNil(value) { return .items([]) }
48 guard case .list(let items) = value else { throw Failure.message("\(key) \(raw) isn't a list of names.") }
49 return .items(items.map { $0.string ?? Elisp.printed($0) })
50 }
51
52 enum BaseType { case integer, float, string }
53
54 /// `org-babel-C-val-to-base-type` and `org-babel-java-val-to-base-type`.
55 static func baseType(_ cells: [Cell]) -> BaseType? {
56 var type: BaseType?
57 for cell in cells {
58 switch cell {
59 case .text: type = .string
60 case .number(let n) where n.contains(".") || n.contains("e") || n.contains("N"):
61 if type != .string { type = .float }
62 case .number:
63 if type == nil { type = .integer }
64 }
65 }
66 return type
67 }
68
69 static func cells(_ value: VarValue) throws -> [Cell] {
70 switch value {
71 case .scalar(let s): return [.text(s)]
72 case .number(let n): return [.number(n)]
73 case .list(let items): return items
74 case .table(let rows):
75 guard !rows.contains(where: { $0 == nil }) else { throw Failure.message("Tables with rules in :var aren't supported for this language yet.") }
76 return rows.flatMap { $0! }
77 }
78 }
79
80 // MARK: - C, C++ and D
81
82 static let cColumnUtility = """
83
84 #ifndef _STRING_H
85 #include <string.h>
86 #endif
87 int get_column_num (int nbcols, const char** header, const char* column)
88 {
89 int c;
90 for (c=0; c<nbcols; c++)
91 if (strcmp(header[c],column)==0)
92 return c;
93 return -1;
94 }
95
96 """
97 static let dColumnUtility = """
98 int get_column_num (string[] header, string column)
99 {
100 foreach (c, h; header)
101 if (h==column)
102 return to!int(c);
103 return -1;
104 }
105
106 """
107
108 /// `org-babel-C-expand-C` and `org-babel-C-expand-D`.
109 static func expandC(_ body: String, d: Bool, tables: Tables, params: Params) throws -> String {
110 let vars = tables.vars
111 func typeName(_ type: BaseType?) throws -> String {
112 switch type {
113 case .integer: return "int"
114 case .float: return "double"
115 case .string: return d ? "string" : "const char*"
116 case nil: throw Failure.message("A :var value has no type C can take.")
117 }
118 }
119 func literal(_ cell: Cell, _ type: BaseType) -> String {
120 type == .string ? "\"" + cellText(cell) + "\"" : cellText(cell)
121 }
122 // `org-babel-C-var-to-C`.
123 let declarations = try vars.map { name, value -> String in
124 let type = try baseType(cells(value))
125 let typeName = try typeName(type)
126 var suffix = ""
127 let data: String
128 switch value {
129 case .scalar, .number:
130 data = literal(try cells(value)[0], type!)
131 case .list(let items):
132 suffix = "[\(items.count)]"
133 data = (d ? "[" : "{") + items.map { literal($0, type!) }.joined(separator: ",") + (d ? "]" : "}")
134 case .table(let rows):
135 let rows = rows.map { $0! }
136 suffix = d ? "[\(rows.first?.count ?? 0)][\(rows.count)]" : "[\(rows.count)][\(rows.first?.count ?? 0)]"
137 data = (d ? "[\n" : "{\n")
138 + rows.map { (d ? " [" : " {") + $0.map { literal($0, type!) }.joined(separator: ",") + (d ? "]" : "}") }.joined(separator: ",\n")
139 + (d ? "\n]" : "\n}")
140 }
141 return d ? "\(typeName)\(suffix) \(name) = \(data);" : "\(typeName) \(name)\(suffix) = \(data);"
142 }
143 // `org-babel-C-table-sizes-to-C`.
144 let sizes = vars.map { name, value -> String in
145 switch value {
146 case .table(let rows): "const int \(name)_rows = \(rows.count);\nconst int \(name)_cols = \(rows.first??.count ?? 0);"
147 case .list(let items): "const int \(name)_cols = \(items.count);"
148 case .scalar, .number: ""
149 }
150 }
151 // `org-babel-C-header-to-C`.
152 let headers = try tables.colnames.map { table, names -> String in
153 let value = vars.first { $0.0 == table }?.1
154 let typeName = try typeName(try value.map(cells).flatMap(baseType))
155 let quoted = names.map { "\"" + cellText($0) + "\"" }.joined(separator: ",")
156 return d
157 ? "string[\(names.count)] \(table)_header = [\(quoted)];\n\(typeName) \(table)_h (size_t row, string col) { return \(table)[row][get_column_num(\(table)_header,col)]; }"
158 : "const char* \(table)_header[\(names.count)] = {\(quoted)};\n\(typeName) \(table)_h (int row, const char* col) { return \(table)[row][get_column_num(\(names.count),\(table)_header,col)]; }"
159 }
160 var body = body
161 var parts: [String] = []
162 if d {
163 // `:imports` and the inherited IMPORTS property, which tangling reads outside the
164 // Org buffer, so never.
165 parts.append("module mmm;")
166 parts.append(((try headerValue(":imports", params)?.words ?? []) + ["std.stdio", "std.conv"]).map { "import \($0);" }.joined(separator: "\n"))
167 } else {
168 let includes = try headerValue(":includes", params)?.words ?? []
169 parts.append(includes.map { $0.hasPrefix("<") ? "#include \($0)" : "#include \"\($0)\"" }.joined(separator: "\n"))
170 var defines: [String] = []
171 switch try headerValue(":defines", params) {
172 case .string(let s):
173 let words = HeaderValue.string(s).words
174 defines = stride(from: 0, to: words.count - 1, by: 2).map { words[$0] + " " + words[$0 + 1] }
175 case .items(let items): defines = items
176 case nil: break
177 }
178 parts.append(defines.map { "#define \($0)" }.joined(separator: "\n"))
179 parts.append((try headerValue(":namespaces", params)?.words ?? []).map { "using namespace \($0);" }.joined(separator: "\n"))
180 body = (params.single[":prologue"].map { $0 + "\n" } ?? "") + body + (params.single[":epilogue"].map { "\n" + $0 + "\n" } ?? "")
181 }
182 parts.append(declarations.joined(separator: "\n"))
183 parts.append(sizes.joined(separator: "\n"))
184 parts.append(tables.colnames.isEmpty ? "" : d ? dColumnUtility : cColumnUtility)
185 parts.append(headers.joined(separator: "\n"))
186 // `org-babel-C-ensure-main-wrap`.
187 let hasMain = body.range(of: "(?im)^[ \\t]*[intvod]+[ \\t\\n\\r]*main[ \\t]*\\(.*\\)", options: .regularExpression) != nil
188 parts.append(params.single[":main"] == "no" || hasMain ? body : "int main() {\n\(body)\nreturn 0;\n}\n")
189 parts.append("\n")
190 return parts.joined(separator: "\n")
191 }
192
193 // MARK: - Java
194
195 static let javaSpace = "[ \\t\\n\\r\\f]"
196 static let javaPackage = try! NSRegularExpression(pattern: "(?im)^\(javaSpace)*package\(javaSpace)+([\\p{L}\\p{N}_.]+)\(javaSpace)*;$")
197 static let javaImports = try! NSRegularExpression(pattern: "(?im)^\(javaSpace)*import(?:\(javaSpace)+static)?\(javaSpace)+([\\p{L}\\p{N}_.*]+)\(javaSpace)*;$")
198 static let javaClass = try! NSRegularExpression(pattern: "(?im)^\(javaSpace)*(?:public\(javaSpace)+)?class\(javaSpace)+([\\p{L}\\p{N}_]+)\(javaSpace)*\\{")
199 static let javaMain = try! NSRegularExpression(pattern: "(?im)^\(javaSpace)*public\(javaSpace)+static\(javaSpace)+void\(javaSpace)+main\(javaSpace)*\\(\(javaSpace)*String"
200 + "[\\p{L}\\p{N}_\\[\\] \\t\\n\\r\\f]+\\)\(javaSpace)*(?:throws[\\p{L}\\p{N}_,. \\t\\n\\r\\f]+)?\\{")
201 static let javaMethod = try! NSRegularExpression(pattern: "(?im)^\(javaSpace)*(?:[\\p{L}\\p{N}]+\(javaSpace)+)?(?:static\(javaSpace)+)?[\\p{L}\\p{N}_\\[\\]]+\(javaSpace)+[\\p{L}\\p{N}_]+"
202 + "\(javaSpace)*\\([\\p{L}\\p{N}_\\[\\], \\t\\n\\r\\f]*\\)\(javaSpace)*(?:throws[\\p{L}\\p{N}_,. \\t\\n\\r\\f]+)?\\{")
203
204 /// `org-babel-expand-body:java`: the body wrapped in a `main` method when it has no method,
205 /// and in a class when it has none, with `:var` as static fields, `:imports` and the
206 /// package of `:classname`.
207 static func expandJava(_ body: String, vars: [(String, VarValue)], params: Params) throws -> String {
208 let fullName = params.single[":classname"] ?? javaClassName(body)
209 let className = fullName.components(separatedBy: ".").last!
210 let packageName = fullName.contains(".") ? (fullName as NSString).deletingPathExtension : nil
211 let varLines = try vars.map { name, value -> String in
212 // A rule is `hline`, a string.
213 let typed: [Cell]
214 if case .table(let rows) = value, rows.contains(where: { $0 == nil }) {
215 typed = rows.compactMap { $0 }.flatMap { $0 } + [.text("hline")]
216 } else {
217 typed = try cells(value)
218 }
219 guard let type = baseType(typed) else { throw Failure.message("A :var value has no type java can take.") }
220 let base = type == .integer ? "Integer" : type == .float ? "Double" : "String"
221 func literal(_ cell: Cell?) throws -> String {
222 guard let cell else { return "null" }
223 switch type {
224 case .integer: return cellText(cell)
225 case .float: return String(format: "%f", Double(cellText(cell)) ?? 0)
226 case .string:
227 let s = cellText(cell)
228 if s.range(of: ".\\n+.", options: .regularExpression) != nil { throw Failure.message("Java does not support multiline string literals") }
229 return "\"" + s + "\""
230 }
231 }
232 let declared: String
233 let data: String
234 switch value {
235 case .scalar, .number:
236 declared = base
237 data = try literal(try cells(value)[0])
238 case .list(let items):
239 declared = "List<\(base)>"
240 data = "Arrays.asList(" + (try items.map { try literal($0) }).joined(separator: ", ") + ")"
241 case .table(let rows):
242 declared = "List<List<\(base)>>"
243 data = "Arrays.asList(" + (try rows.map { row in
244 try row.map { "Arrays.asList(" + (try $0.map { try literal($0) }).joined(separator: ", ") + ")" } ?? "null"
245 }).joined(separator: ", ") + ")"
246 }
247 return " static \(declared) \(name) = \(data);"
248 }
249 let imports = try headerValue(":imports", params).map { value -> [String] in
250 guard case .string(let s) = value else { throw Failure.message(":imports for java takes a string.") }
251 return s.components(separatedBy: " ")
252 }
253
254 let buffer = NSMutableString(string: (params.single[":prologue"].map { $0 + "\n" } ?? "") + body + (params.single[":epilogue"].map { "\n" + $0 } ?? ""))
255 func search(_ regex: NSRegularExpression, from point: Int) -> NSTextCheckingResult? {
256 regex.firstMatch(in: buffer as String, options: [.withTransparentBounds, .withoutAnchoringBounds],
257 range: NSRange(location: point, length: buffer.length - point))
258 }
259 // `org-babel-java--move-past`.
260 func movePast(_ regex: NSRegularExpression, _ point: inout Int) {
261 while let m = search(regex, from: point) { point = min(NSMaxRange(m.range) + 1, buffer.length) }
262 }
263 func wrap(_ opening: String) {
264 var point = 0
265 movePast(javaPackage, &point)
266 movePast(javaImports, &point)
267 buffer.insert(opening, at: point)
268 indentRigidly(buffer, from: point + opening.utf16.count, by: 4)
269 buffer.append("\n}")
270 }
271 if search(javaMain, from: 0) == nil, search(javaMethod, from: 0) == nil { wrap("public static void main(String[] args) {\n") }
272 if search(javaClass, from: 0) == nil { wrap("\npublic class \(className) {\n") }
273 if !varLines.isEmpty {
274 var point = 0
275 movePast(javaClass, &point)
276 buffer.insert(varLines.joined(separator: "\n") + "\n", at: point)
277 }
278 if let imports {
279 var point = 0
280 movePast(javaPackage, &point)
281 buffer.insert(imports.map { "import \($0);" }.joined(separator: "\n") + "\n", at: point)
282 }
283 if let packageName, search(javaPackage, from: 0) == nil { buffer.insert("package \(packageName);\n", at: 0) }
284 return buffer as String
285 }
286
287 /// `org-babel-java-find-classname`.
288 static func javaClassName(_ body: String) -> String {
289 let ns = body as NSString
290 func first(_ regex: NSRegularExpression) -> String? {
291 regex.firstMatch(in: body, range: NSRange(location: 0, length: ns.length)).map { ns.substring(with: $0.range(at: 1)) }
292 }
293 let package = first(javaPackage)
294 let className = first(javaClass)
295 if let package, let className { return package + "." + className }
296 return className ?? package.map { $0 + ".Main" } ?? "Main"
297 }
298
299 /// `indent-code-rigidly` from `start`, at the beginning of a line, to the end: each line
300 /// not starting inside a string moved right, blank lines emptied, with `indent-tabs-mode` nil.
301 static func indentRigidly(_ buffer: NSMutableString, from start: Int, by columns: Int) {
302 var location = start
303 var inString = false
304 while location < buffer.length {
305 if !inString {
306 var end = location
307 var column = 0
308 while end < buffer.length, let c = UnicodeScalar(buffer.character(at: end)), c == " " || c == "\t" {
309 column = c == "\t" ? (column / 8 + 1) * 8 : column + 1
310 end += 1
311 }
312 let atEnd = end >= buffer.length || buffer.character(at: end) == 10
313 let indent = atEnd ? "" : String(repeating: " ", count: column + columns)
314 buffer.replaceCharacters(in: NSRange(location: location, length: end - location), with: indent)
315 }
316 let line = buffer.lineRange(for: NSRange(location: location, length: 0))
317 var escaped = false
318 for i in line.location..<NSMaxRange(line) {
319 let c = buffer.character(at: i)
320 if escaped { escaped = false } else if c == 92 { escaped = true } else if c == 34 { inString.toggle() }
321 }
322 location = NSMaxRange(line)
323 }
324 }
325
326 // MARK: - Fortran
327
328 /// `org-babel-expand-body:fortran`.
329 static func expandFortran(_ body: String, vars: [(String, VarValue)], params: Params) throws -> String {
330 let prologue = params.single[":prologue"]
331 let epilogue = params.single[":epilogue"]
332 // `%S`.
333 func printed(_ cell: Cell) -> String {
334 switch cell {
335 case .number(let n): n
336 case .text(let s): lispString(s)
337 }
338 }
339 // `org-babel-fortran-var-to-fortran`.
340 let declarations = try vars.map { name, value -> String in
341 switch value {
342 case .number(let n) where baseType([.number(n)]) == .integer: return "integer, parameter :: \(name) = \(n)\n"
343 case .number(let n): return "real, parameter :: \(name) = \(n)\n"
344 case .scalar(let s): return "character(len=\(s.unicodeScalars.count)), parameter :: \(name) = '\(s)'\n"
345 case .table(let rows):
346 guard !rows.contains(where: { $0 == nil }) else { throw Failure.message("Tables with rules in :var aren't supported for fortran yet.") }
347 let rows = rows.map { $0! }
348 let columns = rows.first?.count ?? 0
349 let data = "(/" + rows.map { "(/" + $0.map(printed).joined(separator: ", ") + "/)" }.joined(separator: ", ") + "/)"
350 return "real, parameter :: \(name)(\(rows.count),\(columns)) = transpose( reshape( \(data) , (/ \(columns), \(rows.count) /) ) )\n"
351 case .list(let items):
352 return "real, parameter :: \(name)(\(items.count)) = (/" + items.map(printed).joined(separator: ", ") + "/)\n"
353 }
354 }
355 // The INCLUDES and DEFINES properties are read outside the Org buffer when tangling.
356 func items(_ value: HeaderValue?) -> [String] {
357 switch value {
358 case .string(let s): [s]
359 case .items(let items): items
360 case nil: []
361 }
362 }
363 let includes = items(try headerValue(":includes", params))
364 let defines = items(try headerValue(":defines", params))
365 let main: String
366 if params.single[":main"] == "no" {
367 main = (prologue.map { $0 + "\n" } ?? "") + body + (epilogue.map { "\n" + $0 + "\n" } ?? "")
368 } else {
369 // Org writes the prologue again where the epilogue would go.
370 let inner = declarations.joined(separator: "\n") + (prologue.map { $0 + "\n" } ?? "") + body + (prologue.map { $0 + "\n" } ?? "")
371 if inner.range(of: "(?im)^[ \\t]*program\\b", options: .regularExpression) != nil {
372 if !vars.isEmpty { throw Failure.message("Cannot use :vars if `program' statement is present") }
373 main = inner
374 } else {
375 main = "program main\n\(inner)\nend program main\n"
376 }
377 }
378 return [includes.map { "#include \($0)" }.joined(separator: "\n"), defines.map { "#define \($0)" }.joined(separator: "\n"), main, "\n"]
379 .joined(separator: "\n")
380 }
381
382 // MARK: - Clojure
383
384 /// `org-babel-expand-body:clojure`, for a backend other than CIDER's (whose namespace
385 /// only matters with `:ns`, which is then used).
386 static func expandClojure(_ body: String, vars: [(String, VarValue)], params: Params) -> String {
387 var expanded = params.single[":ns"].map { "(ns \($0))\n" } ?? ""
388 if vars.isEmpty {
389 expanded += body.trimmingCharacters(in: .whitespacesAndNewlines)
390 } else {
391 let uncommented = body.replacingOccurrences(of: "(?m)^[ ]*;+.*$", with: "", options: .regularExpression)
392 expanded += "(let [" + vars.map { "\($0.0) '\(lispLiteral($0.1))" }.joined(separator: "\n ") + "]\n\(uncommented))"
393 }
394 expanded = expanded.trimmingCharacters(in: .whitespacesAndNewlines)
395 if params.results.contains("output") { return expanded }
396 let printer = params.results.contains("code") || params.results.contains("pp")
397 ? "(require '[clojure.pprint :refer [pprint]]) (pprint "
398 : "(prn "
399 return printer + "(binding [*out* (java.io.StringWriter.)]" + expanded + "))"
400 }
401}