| @@ -0,0 +1,401 @@ |
| 1 | import 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 | |
| 6 | extension 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 | } |