Sources/OrgCore/Compute/BabelExpand.swift
640 lines · 34693 bytes
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, the `:cache` hash and runs use, and their `org-babel-execute:LANG`:
5// a script that writes the expansion to a file, compiles it and runs it.
6
7extension Babel {
8 static let expandedLanguages: Set<String> = ["C", "C++", "cpp", "D", "java", "fortran", "clojure"]
9
10 /// The body as the language's `org-babel-expand-body:` writes it, or nil for other languages.
11 /// `properties` gives inherited properties where the block is, which D's and Fortran's
12 /// expansions read; tangling reads them outside the Org buffer, so never.
13 static func languageExpansion(_ body: String, language: String, tables: Tables, params: Params,
14 properties: (String) -> String? = { _ in nil }) throws -> String? {
15 switch language {
16 case "C", "C++", "cpp": return try expandC(body, d: false, tables: tables, params: params)
17 case "D": return try expandC(body, d: true, tables: tables, params: params, properties: properties)
18 case "java": return try expandJava(body, vars: tables.vars, params: params)
19 case "fortran": return try expandFortran(body, vars: tables.vars, params: params, properties: properties)
20 case "clojure": return expandClojure(body, vars: tables.vars, params: params)
21 default: return nil
22 }
23 }
24
25 enum HeaderValue {
26 case string(String)
27 case items([String])
28
29 /// `split-string` of a string, or the items.
30 var words: [String] {
31 switch self {
32 case .string(let s): s.split(whereSeparator: \.isWhitespace).map(String.init)
33 case .items(let items): items
34 }
35 }
36 }
37
38 /// A header value as `org-babel-read` gives it: a string, or the items of a Lisp list;
39 /// nil when absent.
40 static func headerValue(_ key: String, _ params: Params) throws -> HeaderValue? {
41 guard let raw = params.single[key] else { return nil }
42 var value = params.evaluated[key]
43 if value == nil, params.lisp.contains(key) {
44 guard let form = try? LispReader.readFirst(raw).sexp, let result = try? Elisp().eval(form) else {
45 throw Failure.message("\(key) \(raw) is Lisp that only Emacs can evaluate.")
46 }
47 value = result
48 }
49 guard let value else { return .string(raw) }
50 if let s = value.string { return .string(s) }
51 if Elisp.isNil(value) { return .items([]) }
52 guard case .list(let items) = value else { throw Failure.message("\(key) \(raw) isn't a list of names.") }
53 return .items(items.map { $0.string ?? Elisp.printed($0) })
54 }
55
56 /// `org-babel-read` of a property value: Lisp evaluated, a quoted string read, else the
57 /// string.
58 static func propertyValue(_ value: String?) throws -> HeaderValue? {
59 guard let value else { return nil }
60 guard let first = value.first, "('`[".contains(first) else {
61 guard case .string(let s) = read(value) else { return .string(value) }
62 return .string(s)
63 }
64 guard let form = try? LispReader.readFirst(value).sexp, let result = try? Elisp().eval(form) else {
65 throw Failure.message("The property value \(value) is Lisp that only Emacs can evaluate.")
66 }
67 if let s = result.string { return .string(s) }
68 if Elisp.isNil(result) { return .items([]) }
69 guard case .list(let items) = result else { throw Failure.message("The property value \(value) isn't a list of names.") }
70 return .items(items.map { $0.string ?? Elisp.printed($0) })
71 }
72
73 /// `(org-entry-get nil KEY t)` under `heading`, or before any: the nearest value from
74 /// `#+PROPERTY` down the outline, with `KEY+` additions.
75 static func inheritedProperty(_ key: String, heading: Int?, model: DocumentModel) -> String? {
76 var levels = [model.fileProperties]
77 if let heading { levels += (model.ancestors(of: heading) + [heading]).map { model.headings[$0].properties } }
78 var value: String?
79 for entries in levels {
80 for entry in entries where entry.key.uppercased() == key.uppercased() {
81 value = entry.additive ? [value, entry.value].compactMap { $0 }.joined(separator: " ") : entry.value
82 }
83 }
84 return value
85 }
86
87 enum BaseType { case integer, float, string }
88
89 /// `org-babel-C-val-to-base-type` and `org-babel-java-val-to-base-type`.
90 static func baseType(_ cells: [Cell]) -> BaseType? {
91 var type: BaseType?
92 for cell in cells {
93 switch cell {
94 case .text: type = .string
95 case .number(let n) where n.contains(".") || n.contains("e") || n.contains("N"):
96 if type != .string { type = .float }
97 case .number:
98 if type == nil { type = .integer }
99 }
100 }
101 return type
102 }
103
104 static func cells(_ value: VarValue) throws -> [Cell] {
105 switch value {
106 case .scalar(let s): return [.text(s)]
107 case .number(let n): return [.number(n)]
108 case .list(let items): return items
109 case .table(let rows):
110 guard !rows.contains(where: { $0 == nil }) else { throw Failure.message("Tables with rules in :var aren't supported for this language yet.") }
111 return rows.flatMap { $0! }
112 }
113 }
114
115 // MARK: - C, C++ and D
116
117 static let cColumnUtility = """
118
119 #ifndef _STRING_H
120 #include <string.h>
121 #endif
122 int get_column_num (int nbcols, const char** header, const char* column)
123 {
124 int c;
125 for (c=0; c<nbcols; c++)
126 if (strcmp(header[c],column)==0)
127 return c;
128 return -1;
129 }
130
131 """
132 static let dColumnUtility = """
133 int get_column_num (string[] header, string column)
134 {
135 foreach (c, h; header)
136 if (h==column)
137 return to!int(c);
138 return -1;
139 }
140
141 """
142
143 /// `org-babel-C-expand-C` and `org-babel-C-expand-D`.
144 static func expandC(_ body: String, d: Bool, tables: Tables, params: Params, properties: (String) -> String? = { _ in nil }) throws -> String {
145 let vars = tables.vars
146 func typeName(_ type: BaseType?) throws -> String {
147 switch type {
148 case .integer: return "int"
149 case .float: return "double"
150 case .string: return d ? "string" : "const char*"
151 case nil: throw Failure.message("A :var value has no type C can take.")
152 }
153 }
154 func literal(_ cell: Cell, _ type: BaseType) -> String {
155 type == .string ? "\"" + cellText(cell) + "\"" : cellText(cell)
156 }
157 // `org-babel-C-var-to-C`.
158 let declarations = try vars.map { name, value -> String in
159 let type = try baseType(cells(value))
160 let typeName = try typeName(type)
161 var suffix = ""
162 let data: String
163 switch value {
164 case .scalar, .number:
165 data = literal(try cells(value)[0], type!)
166 case .list(let items):
167 suffix = "[\(items.count)]"
168 data = (d ? "[" : "{") + items.map { literal($0, type!) }.joined(separator: ",") + (d ? "]" : "}")
169 case .table(let rows):
170 let rows = rows.map { $0! }
171 suffix = d ? "[\(rows.first?.count ?? 0)][\(rows.count)]" : "[\(rows.count)][\(rows.first?.count ?? 0)]"
172 data = (d ? "[\n" : "{\n")
173 + rows.map { (d ? " [" : " {") + $0.map { literal($0, type!) }.joined(separator: ",") + (d ? "]" : "}") }.joined(separator: ",\n")
174 + (d ? "\n]" : "\n}")
175 }
176 return d ? "\(typeName)\(suffix) \(name) = \(data);" : "\(typeName) \(name)\(suffix) = \(data);"
177 }
178 // `org-babel-C-table-sizes-to-C`.
179 let sizes = vars.map { name, value -> String in
180 switch value {
181 case .table(let rows): "const int \(name)_rows = \(rows.count);\nconst int \(name)_cols = \(rows.first??.count ?? 0);"
182 case .list(let items): "const int \(name)_cols = \(items.count);"
183 case .scalar, .number: ""
184 }
185 }
186 // `org-babel-C-header-to-C`.
187 let headers = try tables.colnames.map { table, names -> String in
188 let value = vars.first { $0.0 == table }?.1
189 let typeName = try typeName(try value.map(cells).flatMap(baseType))
190 let quoted = names.map { "\"" + cellText($0) + "\"" }.joined(separator: ",")
191 return d
192 ? "string[\(names.count)] \(table)_header = [\(quoted)];\n\(typeName) \(table)_h (size_t row, string col) { return \(table)[row][get_column_num(\(table)_header,col)]; }"
193 : "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)]; }"
194 }
195 var body = body
196 var parts: [String] = []
197 if d {
198 // `:imports`, or the inherited IMPORTS property.
199 parts.append("module mmm;")
200 let imports = try headerValue(":imports", params) ?? propertyValue(properties("imports"))
201 parts.append(((imports?.words ?? []) + ["std.stdio", "std.conv"]).map { "import \($0);" }.joined(separator: "\n"))
202 } else {
203 let includes = try headerValue(":includes", params)?.words ?? []
204 parts.append(includes.map { $0.hasPrefix("<") ? "#include \($0)" : "#include \"\($0)\"" }.joined(separator: "\n"))
205 var defines: [String] = []
206 switch try headerValue(":defines", params) {
207 case .string(let s):
208 let words = HeaderValue.string(s).words
209 defines = stride(from: 0, to: words.count - 1, by: 2).map { words[$0] + " " + words[$0 + 1] }
210 case .items(let items): defines = items
211 case nil: break
212 }
213 parts.append(defines.map { "#define \($0)" }.joined(separator: "\n"))
214 parts.append((try headerValue(":namespaces", params)?.words ?? []).map { "using namespace \($0);" }.joined(separator: "\n"))
215 body = (params.single[":prologue"].map { $0 + "\n" } ?? "") + body + (params.single[":epilogue"].map { "\n" + $0 + "\n" } ?? "")
216 }
217 parts.append(declarations.joined(separator: "\n"))
218 parts.append(sizes.joined(separator: "\n"))
219 parts.append(tables.colnames.isEmpty ? "" : d ? dColumnUtility : cColumnUtility)
220 parts.append(headers.joined(separator: "\n"))
221 // `org-babel-C-ensure-main-wrap`.
222 let hasMain = body.range(of: "(?im)^[ \\t]*[intvod]+[ \\t\\n\\r]*main[ \\t]*\\(.*\\)", options: .regularExpression) != nil
223 parts.append(params.single[":main"] == "no" || hasMain ? body : "int main() {\n\(body)\nreturn 0;\n}\n")
224 parts.append("\n")
225 return parts.joined(separator: "\n")
226 }
227
228 // MARK: - Java
229
230 static let javaSpace = "[ \\t\\n\\r\\f]"
231 static let javaPackage = try! NSRegularExpression(pattern: "(?im)^\(javaSpace)*package\(javaSpace)+([\\p{L}\\p{N}_.]+)\(javaSpace)*;$")
232 static let javaImports = try! NSRegularExpression(pattern: "(?im)^\(javaSpace)*import(?:\(javaSpace)+static)?\(javaSpace)+([\\p{L}\\p{N}_.*]+)\(javaSpace)*;$")
233 static let javaClass = try! NSRegularExpression(pattern: "(?im)^\(javaSpace)*(?:public\(javaSpace)+)?class\(javaSpace)+([\\p{L}\\p{N}_]+)\(javaSpace)*\\{")
234 static let javaMain = try! NSRegularExpression(pattern: "(?im)^\(javaSpace)*public\(javaSpace)+static\(javaSpace)+void\(javaSpace)+main\(javaSpace)*\\(\(javaSpace)*String"
235 + "[\\p{L}\\p{N}_\\[\\] \\t\\n\\r\\f]+\\)\(javaSpace)*(?:throws[\\p{L}\\p{N}_,. \\t\\n\\r\\f]+)?\\{")
236 static let javaMethod = try! NSRegularExpression(pattern: "(?im)^\(javaSpace)*(?:[\\p{L}\\p{N}]+\(javaSpace)+)?(?:static\(javaSpace)+)?[\\p{L}\\p{N}_\\[\\]]+\(javaSpace)+[\\p{L}\\p{N}_]+"
237 + "\(javaSpace)*\\([\\p{L}\\p{N}_\\[\\], \\t\\n\\r\\f]*\\)\(javaSpace)*(?:throws[\\p{L}\\p{N}_,. \\t\\n\\r\\f]+)?\\{")
238
239 /// `org-babel-expand-body:java`: the body wrapped in a `main` method when it has no method,
240 /// and in a class when it has none, with `:var` as static fields, `:imports` and the
241 /// package of `:classname`.
242 static func expandJava(_ body: String, vars: [(String, VarValue)], params: Params) throws -> String {
243 let fullName = params.single[":classname"] ?? javaClassName(body)
244 let className = fullName.components(separatedBy: ".").last!
245 let packageName = fullName.contains(".") ? (fullName as NSString).deletingPathExtension : nil
246 let varLines = try vars.map { name, value -> String in
247 // A rule is `hline`, a string.
248 let typed: [Cell]
249 if case .table(let rows) = value, rows.contains(where: { $0 == nil }) {
250 typed = rows.compactMap { $0 }.flatMap { $0 } + [.text("hline")]
251 } else {
252 typed = try cells(value)
253 }
254 guard let type = baseType(typed) else { throw Failure.message("A :var value has no type java can take.") }
255 let base = type == .integer ? "Integer" : type == .float ? "Double" : "String"
256 func literal(_ cell: Cell?) throws -> String {
257 guard let cell else { return "null" }
258 switch type {
259 case .integer: return cellText(cell)
260 case .float: return String(format: "%f", Double(cellText(cell)) ?? 0)
261 case .string:
262 let s = cellText(cell)
263 if s.range(of: ".\\n+.", options: .regularExpression) != nil { throw Failure.message("Java does not support multiline string literals") }
264 return "\"" + s + "\""
265 }
266 }
267 let declared: String
268 let data: String
269 switch value {
270 case .scalar, .number:
271 declared = base
272 data = try literal(try cells(value)[0])
273 case .list(let items):
274 declared = "List<\(base)>"
275 data = "Arrays.asList(" + (try items.map { try literal($0) }).joined(separator: ", ") + ")"
276 case .table(let rows):
277 declared = "List<List<\(base)>>"
278 data = "Arrays.asList(" + (try rows.map { row in
279 try row.map { "Arrays.asList(" + (try $0.map { try literal($0) }).joined(separator: ", ") + ")" } ?? "null"
280 }).joined(separator: ", ") + ")"
281 }
282 return " static \(declared) \(name) = \(data);"
283 }
284 let imports = try headerValue(":imports", params).map { value -> [String] in
285 guard case .string(let s) = value else { throw Failure.message(":imports for java takes a string.") }
286 return s.components(separatedBy: " ")
287 }
288
289 let buffer = NSMutableString(string: (params.single[":prologue"].map { $0 + "\n" } ?? "") + body + (params.single[":epilogue"].map { "\n" + $0 } ?? ""))
290 func search(_ regex: NSRegularExpression, from point: Int) -> NSTextCheckingResult? {
291 regex.firstMatch(in: buffer as String, options: [.withTransparentBounds, .withoutAnchoringBounds],
292 range: NSRange(location: point, length: buffer.length - point))
293 }
294 // `org-babel-java--move-past`.
295 func movePast(_ regex: NSRegularExpression, _ point: inout Int) {
296 while let m = search(regex, from: point) { point = min(NSMaxRange(m.range) + 1, buffer.length) }
297 }
298 func wrap(_ opening: String) {
299 var point = 0
300 movePast(javaPackage, &point)
301 movePast(javaImports, &point)
302 buffer.insert(opening, at: point)
303 indentRigidly(buffer, from: point + opening.utf16.count, by: 4)
304 buffer.append("\n}")
305 }
306 if search(javaMain, from: 0) == nil, search(javaMethod, from: 0) == nil { wrap("public static void main(String[] args) {\n") }
307 if search(javaClass, from: 0) == nil { wrap("\npublic class \(className) {\n") }
308 if !varLines.isEmpty {
309 var point = 0
310 movePast(javaClass, &point)
311 buffer.insert(varLines.joined(separator: "\n") + "\n", at: point)
312 }
313 if let imports {
314 var point = 0
315 movePast(javaPackage, &point)
316 buffer.insert(imports.map { "import \($0);" }.joined(separator: "\n") + "\n", at: point)
317 }
318 if let packageName, search(javaPackage, from: 0) == nil { buffer.insert("package \(packageName);\n", at: 0) }
319 return buffer as String
320 }
321
322 /// `org-babel-java-find-classname`.
323 static func javaClassName(_ body: String) -> String {
324 let ns = body as NSString
325 func first(_ regex: NSRegularExpression) -> String? {
326 regex.firstMatch(in: body, range: NSRange(location: 0, length: ns.length)).map { ns.substring(with: $0.range(at: 1)) }
327 }
328 let package = first(javaPackage)
329 let className = first(javaClass)
330 if let package, let className { return package + "." + className }
331 return className ?? package.map { $0 + ".Main" } ?? "Main"
332 }
333
334 /// `indent-code-rigidly` from `start`, at the beginning of a line, to the end: each line
335 /// not starting inside a string moved right, blank lines emptied, with `indent-tabs-mode` nil.
336 static func indentRigidly(_ buffer: NSMutableString, from start: Int, by columns: Int) {
337 var location = start
338 var inString = false
339 while location < buffer.length {
340 if !inString {
341 var end = location
342 var column = 0
343 while end < buffer.length, let c = UnicodeScalar(buffer.character(at: end)), c == " " || c == "\t" {
344 column = c == "\t" ? (column / 8 + 1) * 8 : column + 1
345 end += 1
346 }
347 let atEnd = end >= buffer.length || buffer.character(at: end) == 10
348 let indent = atEnd ? "" : String(repeating: " ", count: column + columns)
349 buffer.replaceCharacters(in: NSRange(location: location, length: end - location), with: indent)
350 }
351 let line = buffer.lineRange(for: NSRange(location: location, length: 0))
352 var escaped = false
353 for i in line.location..<NSMaxRange(line) {
354 let c = buffer.character(at: i)
355 if escaped { escaped = false } else if c == 92 { escaped = true } else if c == 34 { inString.toggle() }
356 }
357 location = NSMaxRange(line)
358 }
359 }
360
361 // MARK: - Fortran
362
363 /// `org-babel-expand-body:fortran`.
364 static func expandFortran(_ body: String, vars: [(String, VarValue)], params: Params, properties: (String) -> String? = { _ in nil }) throws -> String {
365 let prologue = params.single[":prologue"]
366 let epilogue = params.single[":epilogue"]
367 // `%S`.
368 func printed(_ cell: Cell) -> String {
369 switch cell {
370 case .number(let n): n
371 case .text(let s): lispString(s)
372 }
373 }
374 // `org-babel-fortran-var-to-fortran`.
375 let declarations = try vars.map { name, value -> String in
376 switch value {
377 case .number(let n) where baseType([.number(n)]) == .integer: return "integer, parameter :: \(name) = \(n)\n"
378 case .number(let n): return "real, parameter :: \(name) = \(n)\n"
379 case .scalar(let s): return "character(len=\(s.unicodeScalars.count)), parameter :: \(name) = '\(s)'\n"
380 case .table(let rows):
381 guard !rows.contains(where: { $0 == nil }) else { throw Failure.message("Tables with rules in :var aren't supported for fortran yet.") }
382 let rows = rows.map { $0! }
383 let columns = rows.first?.count ?? 0
384 let data = "(/" + rows.map { "(/" + $0.map(printed).joined(separator: ", ") + "/)" }.joined(separator: ", ") + "/)"
385 return "real, parameter :: \(name)(\(rows.count),\(columns)) = transpose( reshape( \(data) , (/ \(columns), \(rows.count) /) ) )\n"
386 case .list(let items):
387 return "real, parameter :: \(name)(\(items.count)) = (/" + items.map(printed).joined(separator: ", ") + "/)\n"
388 }
389 }
390 func items(_ value: HeaderValue?) -> [String] {
391 switch value {
392 case .string(let s): [s]
393 case .items(let items): items
394 case nil: []
395 }
396 }
397 let includes = items(try headerValue(":includes", params) ?? propertyValue(properties("includes")))
398 let defines = items(try headerValue(":defines", params) ?? propertyValue(properties("defines")))
399 let main: String
400 if params.single[":main"] == "no" {
401 main = (prologue.map { $0 + "\n" } ?? "") + body + (epilogue.map { "\n" + $0 + "\n" } ?? "")
402 } else {
403 // Org writes the prologue again where the epilogue would go.
404 let inner = declarations.joined(separator: "\n") + (prologue.map { $0 + "\n" } ?? "") + body + (prologue.map { $0 + "\n" } ?? "")
405 if inner.range(of: "(?im)^[ \\t]*program\\b", options: .regularExpression) != nil {
406 if !vars.isEmpty { throw Failure.message("Cannot use :vars if `program' statement is present") }
407 main = inner
408 } else {
409 main = "program main\n\(inner)\nend program main\n"
410 }
411 }
412 return [includes.map { "#include \($0)" }.joined(separator: "\n"), defines.map { "#define \($0)" }.joined(separator: "\n"), main, "\n"]
413 .joined(separator: "\n")
414 }
415
416 // MARK: - Clojure
417
418 /// `org-babel-expand-body:clojure`, for a backend other than CIDER's (whose namespace
419 /// only matters with `:ns`, which is then used).
420 static func expandClojure(_ body: String, vars: [(String, VarValue)], params: Params) -> String {
421 var expanded = params.single[":ns"].map { "(ns \($0))\n" } ?? ""
422 if vars.isEmpty {
423 expanded += body.trimmingCharacters(in: .whitespacesAndNewlines)
424 } else {
425 let uncommented = body.replacingOccurrences(of: "(?m)^[ ]*;+.*$", with: "", options: .regularExpression)
426 expanded += "(let [" + vars.map { "\($0.0) '\(lispLiteral($0.1))" }.joined(separator: "\n ") + "]\n\(uncommented))"
427 }
428 expanded = expanded.trimmingCharacters(in: .whitespacesAndNewlines)
429 if params.results.contains("output") { return expanded }
430 let printer = params.results.contains("code") || params.results.contains("pp")
431 ? "(require '[clojure.pprint :refer [pprint]]) (pprint "
432 : "(prn "
433 return printer + "(binding [*out* (java.io.StringWriter.)]" + expanded + "))"
434 }
435
436 // MARK: - Running
437
438 /// A compiled language's run: `source` on the script's standard input, which the script
439 /// writes to a file, compiles and runs with a newline as standard input, as
440 /// `org-babel-eval` gives each command; the compiler's output is dropped.
441 struct CompiledRun {
442 let source: String
443 let script: String
444 let programs: [[String]]
445 }
446
447 /// `org-babel-C-execute`, `org-babel-execute:java`, `org-babel-execute:fortran` and
448 /// `org-babel-execute:clojure` with the babashka or Clojure CLI backend, with Org's
449 /// default compilers.
450 static func compiledRun(language: String, body: String, tables: Tables, params: Params, properties: (String) -> String?) throws -> CompiledRun {
451 func quoted(_ s: String) -> String { "'" + s.replacingOccurrences(of: "'", with: "'\"'\"'") + "'" }
452 /// A string, or a Lisp list's items joined by spaces.
453 func joined(_ value: HeaderValue?) -> String {
454 switch value {
455 case .string(let s): s
456 case .items(let items): items.joined(separator: " ")
457 case nil: ""
458 }
459 }
460 func command(_ s: String) -> String { String(s.prefix { !$0.isWhitespace }) }
461 let expanded = try languageExpansion(body, language: language, tables: tables, params: params, properties: properties)!
462 let cmdline = params.single[":cmdline"].map { " " + $0 } ?? ""
463 let flags = joined(try headerValue(":flags", params))
464 let start = "d=$(mktemp -d) || exit 1\n"
465 let end = "s=$?\nrm -rf \"$d\"\nexit $s\n"
466 switch language {
467 case "C", "C++", "cpp":
468 let (compiler, ext) = language == "C" ? ("gcc", "c") : ("g++", "cpp")
469 let libs = joined(try headerValue(":libs", params) ?? propertyValue(properties("libs")))
470 let script = start + "cat > \"$d/C-src.\(ext)\"\necho | \(compiler) -o \"$d/C-bin\" \(flags) \"$d/C-src.\(ext)\" \(libs) > /dev/null\n"
471 + "echo | \"$d/C-bin\"\(cmdline)\n" + end
472 return CompiledRun(source: expanded, script: script, programs: [[compiler]])
473 case "D":
474 // rdmd compiles and runs.
475 let script = start + "cat > \"$d/C-src.d\"\necho | rdmd \(flags) \"$d/C-src.d\"\(cmdline)\n" + end
476 return CompiledRun(source: expanded, script: script, programs: [["rdmd"]])
477 case "fortran":
478 let script = start + "cat > \"$d/fortran-src.F90\"\necho | gfortran -o \"$d/fortran-bin\" \(flags) \"$d/fortran-src.F90\" > /dev/null\n"
479 + "echo | \"$d/fortran-bin\"\(cmdline)\n" + end
480 return CompiledRun(source: expanded, script: script, programs: [["gfortran"]])
481 case "java":
482 let javac = params.single[":javac"] ?? "javac"
483 let java = params.single[":java"] ?? "java"
484 let fullName = params.single[":classname"] ?? javaClassName(body)
485 let className = fullName.components(separatedBy: ".").last!
486 let packageName = fullName.contains(".") ? (fullName as NSString).deletingPathExtension : nil
487 // Without `:dir` (Java's default is "."), in a temporary folder without the
488 // package; else in the run's folder, under the package's folders.
489 let fromTemp = params.single[":dir"] == nil
490 let source = javaForEvaluation(expanded, suppressPackage: fromTemp, value: !params.results.contains("output"))
491 let cmpflag = params.single[":cmpflag"] ?? ""
492 let flags = params.single[":cmdline"] ?? ""
493 let cmdargs = params.single[":cmdargs"] ?? ""
494 let script: String
495 if fromTemp {
496 script = start + "cat > \"$d/\(className).java\"\n"
497 + "echo | { \(javac) \(cmpflag) \"$d/\(className).java\" && \(java) -cp \"$d/\" \(flags) \(className) \(cmdargs); }\n" + end
498 } else {
499 let folder = packageName.map { $0.replacingOccurrences(of: ".", with: "/") + "/" } ?? ""
500 let file = quoted(folder + className + ".java")
501 script = (folder.isEmpty ? "" : "mkdir -p \(quoted(folder))\n") + "cat > \(file)\n"
502 + "echo | { \(javac) \(cmpflag) \(file) && \(java) -cp \"$PWD/\" \(flags) \(fullName) \(cmdargs); }\n"
503 }
504 return CompiledRun(source: source, script: script, programs: [[command(javac)], [command(java)]])
505 case "clojure":
506 let file = "\"$d/clojure-cmd-script.clj\""
507 let run: String
508 let programs: [String]
509 switch params.single[":backend"] {
510 case nil:
511 // `org-babel-clojure-backend`: babashka when installed, else the Clojure CLI.
512 run = "if command -v bb > /dev/null 2>&1; then echo | bb \(file); else echo | clojure -M \(file); fi\n"
513 programs = ["bb", "clojure"]
514 case "babashka":
515 run = "echo | bb \(file)\n"
516 programs = ["bb"]
517 case "clojure-cli":
518 run = "echo | clojure -M \(file)\n"
519 programs = ["clojure"]
520 case let backend?:
521 throw Failure.message("The \(backend) backend for clojure needs Emacs; Orgstar runs babashka or clojure-cli.")
522 }
523 return CompiledRun(source: expanded, script: start + "cat > \(file)\n" + run + end, programs: [programs])
524 default:
525 throw Failure.message("No way to run \(language) blocks yet.")
526 }
527 }
528
529 /// `org-babel-java--result-wrapper`, writing to the result file.
530 static let javaResultWrapper = """
531
532 public static String __toString(Object val) {
533 if (val instanceof String) {
534 return "\\"" + val + "\\"";
535 } else if (val == null) {
536 return "null";
537 } else if (val.getClass().isArray()) {
538 StringBuffer sb = new StringBuffer();
539 Object[] vals = (Object[])val;
540 sb.append("[");
541 for (int ii=0; ii<vals.length; ii++) {
542 sb.append(__toString(vals[ii]));
543 if (ii<vals.length-1)
544 sb.append(",");
545 }
546 sb.append("]");
547 return sb.toString();
548 } else if (val instanceof List) {
549 StringBuffer sb = new StringBuffer();
550 List vals = (List)val;
551 sb.append("[");
552 for (int ii=0; ii<vals.size(); ii++) {
553 sb.append(__toString(vals.get(ii)));
554 if (ii<vals.size()-1)
555 sb.append(",");
556 }
557 sb.append("]");
558 return sb.toString();
559 } else {
560 return String.valueOf(val);
561 }
562 }
563
564 public static void main(String[] args) throws IOException {
565 BufferedWriter output = new BufferedWriter(new FileWriter("\(resultFileToken)"));
566 output.write(__toString(_main(args)));
567 output.close();
568 }
569 """
570
571 /// `org-babel-java--expand-for-evaluation`: without the package when run from a temporary
572 /// folder, a `main` method when there is none, the value written to the result file, and
573 /// the imports those need.
574 static func javaForEvaluation(_ body: String, suppressPackage: Bool, value: Bool) -> String {
575 let buffer = NSMutableString(string: body)
576 func search(_ regex: NSRegularExpression, from point: Int) -> NSTextCheckingResult? {
577 regex.firstMatch(in: buffer as String, options: [.withTransparentBounds, .withoutAnchoringBounds],
578 range: NSRange(location: point, length: buffer.length - point))
579 }
580 func movePast(_ regex: NSRegularExpression) -> Int {
581 var point = 0
582 while let m = search(regex, from: point) { point = min(NSMaxRange(m.range) + 1, buffer.length) }
583 return point
584 }
585 if suppressPackage, let m = search(javaPackage, from: 0) { buffer.replaceCharacters(in: m.range, with: "") }
586 if search(javaMain, from: 0) == nil {
587 buffer.insert("\n public static void main(String[] args) {\n System.out.print(\"success\");\n }\n\n", at: movePast(javaClass))
588 }
589 if value {
590 let point = movePast(javaClass)
591 buffer.insert(javaResultWrapper, at: point)
592 let after = point + javaResultWrapper.utf16.count
593 let main = buffer.range(of: "public static void main(", options: .caseInsensitive, range: NSRange(location: after, length: buffer.length - after))
594 if main.location != NSNotFound { buffer.replaceCharacters(in: main, with: "public static Object _main(") }
595 }
596 // `org-babel-java--import-maybe`, with `case-fold-search`.
597 for (package, name) in [("java.util", "List"), ("java.util", "Arrays"), ("java.io", "BufferedWriter"), ("java.io", "FileWriter"), ("java.io", "IOException")] {
598 let used = buffer.range(of: name, options: .caseInsensitive).location != NSNotFound
599 let imported = buffer.range(of: "(?im)^import .*\(package).*(?:\\*|\(name));", options: .regularExpression).location != NSNotFound
600 if used, !imported { buffer.insert("import \(package).\(name);\n", at: movePast(javaPackage)) }
601 }
602 return buffer as String
603 }
604
605 /// A compiled language's result before it is read: standard output without its common
606 /// indentation, trimmed for Fortran; Java's value from the result file; Clojure's as printed.
607 static func compiledResult(_ finish: BabelJob.Finish, _ output: BabelOutput) -> String {
608 switch finish.language {
609 case "java": finish.output ? output.stdout : output.resultFile ?? ""
610 case "clojure": output.stdout
611 case "fortran": Tangle.removeIndentation(output.stdout).trimmingCharacters(in: .whitespacesAndNewlines)
612 default: Tangle.removeIndentation(output.stdout)
613 }
614 }
615
616 /// `org-babel-result-cond` of the languages: a string as it is, or with `org-babel-read`
617 /// for Fortran; else a table from the output (`org-babel-import-elisp-from-file`), or for
618 /// Java and Clojure `org-babel-script-escape`.
619 static func compiledValue(_ finish: BabelJob.Finish, _ output: BabelOutput, scalar: Bool) -> Value {
620 let raw = compiledResult(finish, output)
621 switch finish.language {
622 case "java": return scalar ? .string(raw) : pythonValue(raw, none: "null", braces: true)
623 case "clojure": return scalar ? .string(raw) : read(raw)
624 case "fortran": return scalar ? read(raw) : importTable(raw)
625 default: return scalar ? .string(raw) : importTable(raw)
626 }
627 }
628
629 /// `org-babel-read` of printed output: a number, or a single string's contents; other
630 /// values as printed. Clojure prints a final newline, so `org-babel-script-escape` never
631 /// takes its output for a list.
632 static func read(_ raw: String) -> Value {
633 let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
634 if let n = number(trimmed) { return .string(n) }
635 if trimmed.hasPrefix("\""), let (sexp, length) = try? LispReader.readFirst(trimmed), length == trimmed.count, let s = sexp.string {
636 return .string(s)
637 }
638 return .string(raw)
639 }
640}