Babel differences from Emacs !165
7 files changed, +642 −29
Layout: unified · split
Sources/OrgCore/Compute/Babel.swift +38 −23
| @@ -190,7 +190,7 @@ public enum Babel { | ||
| 190 | 190 | guard let m = line.firstMatch(of: #/(?i)^[ \t]*#\+CALL:[ \t]*([^\s(\[]+)(?:\[([^\]]*)\])?\((.*?)\)[ \t]*(.*?)[ \t]*\n?$/#) else { return nil } |
| 191 | 191 | let name = String(m.1) |
| 192 | 192 | guard let block = model.srcBlocks.first(where: { $0.name == name }) else { throw Failure.message("Reference '\(name)' not found in this buffer") } |
| 193 | var extra = [lobDefaults] | |
| 193 | var extra = [lobDefaults] + callProperties(block, at: lineRange.location, model: model) | |
| 194 | 194 | if let inside = m.2 { extra.append(Params.parse(String(inside))) } |
| 195 | 195 | extra.append(splitArguments(String(m.3)).map { (":var", $0) }) |
| 196 | 196 | if !m.4.isEmpty { extra.append(Params.parse(String(m.4))) } |
| @@ -216,13 +216,20 @@ public enum Babel { | ||
| 216 | 216 | guard let object = found, let m = object.text.firstMatch(of: /^call_([^()\[\]\s]+)(?:\[([^\]]*)\])?\((.*?)\)(?:\[([^\]]*)\])?$/) else { return nil } |
| 217 | 217 | let name = String(m.1) |
| 218 | 218 | guard let block = model.srcBlocks.first(where: { $0.name == name }) else { throw Failure.message("Reference '\(name)' not found in this buffer") } |
| 219 | var extra = [lobDefaults] | |
| 219 | var extra = [lobDefaults] + callProperties(block, at: object.range.lowerBound, model: model) | |
| 220 | 220 | if let inside = m.2 { extra.append(Params.parse(String(inside))) } |
| 221 | 221 | extra.append(splitArguments(String(m.3)).map { (":var", $0) }) |
| 222 | 222 | if let end = m.4 { extra.append(Params.parse(String(end))) } |
| 223 | 223 | return (block, try Params(block: block, model: model, text: text, extra: extra), object.range) |
| 224 | 224 | } |
| 225 | 225 | |
| 226 | /// The `header-args` properties where a call is, in the called block's language, which | |
| 227 | /// `org-babel-lob-get-info` puts after `lobDefaults`. | |
| 228 | static func callProperties(_ block: SrcBlockInfo, at location: Int, model: DocumentModel) -> [[(String, String)]] { | |
| 229 | let heading = model.headings.lastIndex { $0.headingRange.lowerBound <= location } | |
| 230 | return Params.propertyLayers(language: block.language ?? "", heading: heading, model: model) | |
| 231 | } | |
| 232 | ||
| 226 | 233 | /// `org-babel-default-lob-header-args`, over the called block's own header arguments. |
| 227 | 234 | static let lobDefaults = [(":exports", "results")] |
| 228 | 235 | |
| @@ -246,7 +253,9 @@ public enum Babel { | ||
| 246 | 253 | if line.range(of: "(?i)^[ \\t]*#\\+CALL:", options: .regularExpression) != nil { |
| 247 | 254 | call = (try? callAt(offset, model: model, text: text))?.params |
| 248 | 255 | } else if tree.root.descendants().contains(where: { $0.kind == .inlineBabelCall && $0.range.lowerBound == offset }) { |
| 249 | call = (try? inlineCallAt(offset, tree: tree, model: model, text: text))?.params | |
| 256 | // `org-babel-remove-result` only takes away `#+RESULTS:`, so an inline call's | |
| 257 | // `{{{results(...)}}}` stays whatever `:exports` says. | |
| 258 | return "results" | |
| 250 | 259 | } else if let inline = try? inlineAt(offset, tree: tree, model: model, text: text), inline.block.range.lowerBound == offset { |
| 251 | 260 | return value(inline.params, language: inline.block.language) |
| 252 | 261 | } else { |
| @@ -411,10 +420,11 @@ public enum Babel { | ||
| 411 | 420 | let lines = shellAssignments(vars, shell: executable, params: params) |
| 412 | 421 | let cmdline = params.single[":cmdline"] |
| 413 | 422 | let shebang = params.single[":shebang"].flatMap { $0.contains(where: { !$0.isWhitespace }) ? $0 : nil } |
| 414 | if session == nil, stdin != nil || cmdline != nil || shebang != nil { | |
| 423 | if stdin != nil || cmdline != nil || (session == nil && shebang != nil) { | |
| 415 | 424 | // `org-babel-sh-evaluate`: an executable script, run by the shell with the |
| 416 | 425 | // command line after it and `:stdin` as its input; the result is its standard |
| 417 | // output, or its exit status for `:results value`. | |
| 426 | // output, or its exit status for `:results value`. `:stdin` and `:cmdline` | |
| 427 | // run it even with a session, which the script doesn't use. | |
| 418 | 428 | var script = (shebang ?? "#!/usr/bin/env \(executable)") + "\n" |
| 419 | 429 | if params.single[":padline"] != "no" { script += "\n" } |
| 420 | 430 | script += (lines + [body]).joined(separator: "\n") + (valueIsExitStatus ? "\necho $?" : "") |
| @@ -755,8 +765,9 @@ public enum Babel { | ||
| 755 | 765 | call: Bool, text: NSString, options: Options) throws -> (hash: String, current: Bool, value: VarValue?) { |
| 756 | 766 | // The hash is taken outside the shell's own binding of `shell-file-name`, so its |
| 757 | 767 | // variables are written for the user's shell whatever the block's language. |
| 758 | let input = try cacheInput(language: language, params: params, tables: tables, | |
| 759 | expanded: expandedBody(language: language, vars: tables.vars, body: body, shell: options.shell, params: params), call: call) | |
| 768 | let expanded = try languageExpansion(body, language: language, tables: tables, params: params) | |
| 769 | ?? expandedBody(language: language, vars: tables.vars, body: body, shell: options.shell, params: params) | |
| 770 | let input = try cacheInput(language: language, params: params, tables: tables, expanded: expanded, call: call) | |
| 760 | 771 | let hash = Insecure.SHA1.hash(data: Data(input.utf8)).map { String(format: "%02x", $0) }.joined() |
| 761 | 772 | guard let line = existingResults(name: name, block: location, text: text), resultsHash(at: line, text: text) == hash else { |
| 762 | 773 | return (hash, false, nil) |
| @@ -922,22 +933,7 @@ public enum Babel { | ||
| 922 | 933 | init(block: SrcBlockInfo, model: DocumentModel, text: NSString, defaults: [(String, String)] = [], extra: [[(String, String)]] = [], |
| 923 | 934 | inline: Bool = false) throws { |
| 924 | 935 | var layers: [[(String, String)]] = [defaults] |
| 925 | let language = block.language ?? "" | |
| 926 | // `org-babel-params-from-properties`: the inherited `header-args`, then the inherited | |
| 927 | // `header-args:LANG`, each the nearest value with `+` additions after it. | |
| 928 | var levels = [model.fileProperties] | |
| 929 | if let heading = block.heading { | |
| 930 | levels += (model.ancestors(of: heading).reversed() + [heading]).map { model.headings[$0].properties } | |
| 931 | } | |
| 932 | for key in ["header-args", "header-args:\(language)"] { | |
| 933 | var value: String? | |
| 934 | for entries in levels { | |
| 935 | for entry in entries where entry.key.lowercased() == key { | |
| 936 | value = entry.additive ? [value, entry.value].compactMap { $0 }.joined(separator: " ") : entry.value | |
| 937 | } | |
| 938 | } | |
| 939 | if let value { layers.append(Params.parse(value)) } | |
| 940 | } | |
| 936 | layers += Params.propertyLayers(language: block.language ?? "", heading: block.heading, model: model) | |
| 941 | 937 | let firstLine = inline ? "" : text.substring(with: text.lineRange(for: NSRange(location: block.range.lowerBound, length: 0))) |
| 942 | 938 | if let r = firstLine.range(of: "(?i)#\\+begin_src[ \\t]+\\S+", options: .regularExpression) { |
| 943 | 939 | var rest = String(firstLine[r.upperBound...]).trimmingCharacters(in: .whitespacesAndNewlines) |
| @@ -991,6 +987,25 @@ public enum Babel { | ||
| 991 | 987 | } |
| 992 | 988 | } |
| 993 | 989 | |
| 990 | /// `org-babel-params-from-properties` under `heading`, or before any: the inherited | |
| 991 | /// `header-args`, then the inherited `header-args:LANG`, each the nearest value with `+` | |
| 992 | /// additions after it. | |
| 993 | static func propertyLayers(language: String, heading: Int?, model: DocumentModel) -> [[(String, String)]] { | |
| 994 | var levels = [model.fileProperties] | |
| 995 | if let heading { | |
| 996 | levels += (model.ancestors(of: heading).reversed() + [heading]).map { model.headings[$0].properties } | |
| 997 | } | |
| 998 | return ["header-args", "header-args:\(language)"].compactMap { key in | |
| 999 | var value: String? | |
| 1000 | for entries in levels { | |
| 1001 | for entry in entries where entry.key.lowercased() == key { | |
| 1002 | value = entry.additive ? [value, entry.value].compactMap { $0 }.joined(separator: " ") : entry.value | |
| 1003 | } | |
| 1004 | } | |
| 1005 | return value.map(Params.parse) | |
| 1006 | } | |
| 1007 | } | |
| 1008 | ||
| 994 | 1009 | /// `org-babel-parse-header-arguments`: split before each `:key` outside quotes and |
| 995 | 1010 | /// parentheses. |
| 996 | 1011 | /// A Lisp string's contents as `read` gives them. |
Sources/OrgCore/Compute/BabelExpand.swift added +401
| @@ -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 | } | |
Sources/OrgCore/Compute/Tangle.swift +7 −4
| @@ -133,7 +133,8 @@ public enum Tangle { | ||
| 133 | 133 | |
| 134 | 134 | static func params(_ block: SrcBlockInfo, model: DocumentModel, text: NSString, path: String) throws -> Babel.Params { |
| 135 | 135 | var params = try Babel.Params(block: block, model: model, text: text) |
| 136 | for key in params.lisp where key != ":tangle-mode" { | |
| 136 | // Lists for ob-C, ob-java and ob-fortran are read by the language's expansion. | |
| 137 | for key in params.lisp where ![":tangle-mode", ":includes", ":defines", ":namespaces", ":imports"].contains(key) { | |
| 137 | 138 | guard let value = params.single[key] else { continue } |
| 138 | 139 | params.single[key] = try evaluate(value, key: key, path: path) |
| 139 | 140 | } |
| @@ -357,16 +358,18 @@ public enum Tangle { | ||
| 357 | 358 | } |
| 358 | 359 | |
| 359 | 360 | /// `org-babel-expand-body:LANG`, or the generic expansion with the language's variable |
| 360 | /// assignments. | |
| 361 | /// assignments; tables in `:var` taken apart by `org-babel-disassemble-tables`. | |
| 361 | 362 | static func expandBody(_ body: String, language: String, params: Babel.Params, model: DocumentModel, text: NSString) throws -> String { |
| 362 | 363 | let refs = Babel.References(resolved: [:], options: Babel.Options()) |
| 363 | let vars = try params.vars.map { variable in | |
| 364 | let tables = try Babel.disassemble(try params.vars.map { variable in | |
| 364 | 365 | do { |
| 365 | 366 | return (variable.name, try Babel.resolve(variable.value, model: model, text: text, refs: refs)) |
| 366 | 367 | } catch is Babel.Needed { |
| 367 | 368 | throw Babel.Failure.message(":var \(variable.value) would run a block while tangling, which isn't supported yet; nothing was tangled.") |
| 368 | 369 | } |
| 369 | } | |
| 370 | }, params: params) | |
| 371 | let vars = tables.vars | |
| 372 | if let expanded = try Babel.languageExpansion(body, language: language, tables: tables, params: params) { return expanded } | |
| 370 | 373 | let prologue = params.single[":prologue"] |
| 371 | 374 | let epilogue = params.single[":epilogue"] |
| 372 | 375 | if language == "emacs-lisp" || language == "elisp" { |
Tests/OrgCoreTests/BabelCacheTests.swift +27
| @@ -121,6 +121,33 @@ struct BabelCacheTests { | ||
| 121 | 121 | #expect(job.directory == (emacs[0] as NSString).deletingLastPathComponent + "/") |
| 122 | 122 | } |
| 123 | 123 | |
| 124 | /// Languages that don't run hash the body as their `org-babel-expand-body:LANG` writes it. | |
| 125 | @Test(.enabled(if: ProcessInfo.processInfo.environment["ORGSTAR_SKIP_ORACLE"] == nil)) | |
| 126 | func expandedLanguagesHashLikeEmacs() throws { | |
| 127 | let texts = [ | |
| 128 | "#+NAME: tbl\n| a | b |\n|---+---|\n| 1 | 2 |\n\n#+begin_src C :cache yes :includes <stdio.h> :defines N 1 :var t=tbl n=2\nprintf(\"%d\", n);\n#+end_src\n", | |
| 129 | "#+begin_src C++ :cache yes :namespaces std :main no\nint f();\n#+end_src\n", | |
| 130 | "#+begin_src D :cache yes :var s=\"x\"\nwriteln(s);\n#+end_src\n", | |
| 131 | "#+begin_src fortran :cache yes :var n=3\nprint *, n\n#+end_src\n", | |
| 132 | "#+begin_src clojure :cache yes :var n=3\n(+ n 1)\n#+end_src\n", | |
| 133 | ] | |
| 134 | for text in texts { | |
| 135 | let emacs = try EmacsOracle.evaluate(text, """ | |
| 136 | (progn (require 'ob-C) (require 'ob-fortran) (require 'ob-clojure) (setq org-babel-clojure-backend 'babashka) | |
| 137 | (goto-char (point-min)) (search-forward "#+begin_src") | |
| 138 | (list (org-babel-sha1-hash))) | |
| 139 | """) | |
| 140 | let cached = text + "\n#+RESULTS[\(emacs[0])]:\n: 42\n" | |
| 141 | let caret = (cached as NSString).range(of: "#+begin_src").location + 2 | |
| 142 | let context = EditContext(revision: 0, text: cached, tree: OrgParser.parse(cached), selection: [caret..<caret]) | |
| 143 | guard case .failure(.message(let message)) = Babel.plan(context) else { | |
| 144 | Issue.record("planned:\n\(text)") | |
| 145 | continue | |
| 146 | } | |
| 147 | #expect(message == "Cached: 42", "\(text)") | |
| 148 | } | |
| 149 | } | |
| 150 | ||
| 124 | 151 | @Test func lispHeadersAreRefused() { |
| 125 | 152 | let text = "#+begin_src sh :cache yes :dir (user-full-name)\necho hi\n#+end_src\n" |
| 126 | 153 | let context = EditContext(revision: 0, text: text, tree: OrgParser.parse(text), selection: [2..<2]) |
Tests/OrgCoreTests/BabelTests.swift +26
| @@ -156,6 +156,10 @@ struct BabelTests { | ||
| 156 | 156 | "#+begin_src sh :results value :cmdline x\necho one\n#+end_src\n", |
| 157 | 157 | "#+begin_src bash :shebang #!/bin/bash :cmdline \"a b\" c :results output\necho $#\n#+end_src\n", |
| 158 | 158 | "#+begin_src sh :stdin src :results output\ntr a-z A-Z\n#+end_src\n\n#+NAME: src\n#+begin_src sh\necho hello\n#+end_src\n", |
| 159 | // `:stdin` and `:cmdline` run a script rather than the session. | |
| 160 | "#+begin_src sh :session s1 :cmdline a b :results output\necho $# $1\n#+end_src\n", | |
| 161 | "#+NAME: in\n| hello |\n\n#+begin_src sh :session s2 :stdin in :results output\ncat\n#+end_src\n", | |
| 162 | "#+begin_src bash :session s3 :shebang #!/bin/bash :cmdline x :var v=3\necho $v $1 ${BASH_VERSION:+bash}\n#+end_src\n", | |
| 159 | 163 | // `:var` from other blocks' results. |
| 160 | 164 | "#+begin_src sh :var x=five\necho $((x+1))\n#+end_src\n\n#+NAME: five\n#+begin_src sh\necho 5\n#+end_src\n", |
| 161 | 165 | "#+begin_src python :var t=tbl\nreturn [len(t), t[1][0]]\n#+end_src\n\n#+NAME: tbl\n#+begin_src python\nreturn [[1, 2], [3, 4]]\n#+end_src\n", |
| @@ -218,6 +222,8 @@ struct BabelTests { | ||
| 218 | 222 | (setq org-confirm-babel-evaluate nil |
| 219 | 223 | org-babel-python-command "python3" |
| 220 | 224 | default-directory temporary-file-directory) |
| 225 | ;; No shell session in batch; the cases with one don't use it. | |
| 226 | (advice-add 'org-babel-sh-initiate-session :override (lambda (s &rest _) (unless (member s '(nil "none")) s))) | |
| 221 | 227 | (org-babel-execute-src-block)) |
| 222 | 228 | """)) |
| 223 | 229 | } |
| @@ -362,7 +368,27 @@ struct BabelCallTests { | ||
| 362 | 368 | (named + "Inline call_double[:results verbatim](n=1) here.\n", "call_"), |
| 363 | 369 | (named + "Inline call_double(n=2) {{{results(=0=)}}} here.\n", "call_"), |
| 364 | 370 | (named + "Inline call_double(n=2) and call_double(n=3) here.\n", "call_double(n=3"), |
| 371 | // Properties where the call is, not where the block is. | |
| 372 | (underHeadings + "#+CALL: double()\n", "#+CALL"), | |
| 373 | (underHeadings + "#+CALL: double() :var n=1\n", "#+CALL"), | |
| 374 | (underHeadings + "Inline call_double() here.\n", "call_"), | |
| 365 | 375 | ] |
| 376 | static let underHeadings = """ | |
| 377 | * Library | |
| 378 | :PROPERTIES: | |
| 379 | :header-args: :results verbatim | |
| 380 | :END: | |
| 381 | #+NAME: double | |
| 382 | #+begin_src sh :var n=2 | |
| 383 | echo $((n*2)) | |
| 384 | #+end_src | |
| 385 | * Use | |
| 386 | :PROPERTIES: | |
| 387 | :header-args:sh: :var n=7 | |
| 388 | :header-args: :results raw | |
| 389 | :END: | |
| 390 | ||
| 391 | """ | |
| 366 | 392 | |
| 367 | 393 | @Test(.enabled(if: ProcessInfo.processInfo.environment["ORGSTAR_SKIP_ORACLE"] == nil)) |
| 368 | 394 | func matchesEmacs() throws { |
Tests/OrgCoreTests/ExportTests.swift +13
| @@ -320,6 +320,19 @@ struct ExportResultsTests { | ||
| 320 | 320 | Inline src_sh[:exports both]{echo codeinlineboth} {{{results(=resinlineboth=)}}} three. |
| 321 | 321 | |
| 322 | 322 | Inline call_named() {{{results(=resinlinecall=)}}} four. |
| 323 | ||
| 324 | * Other | |
| 325 | :PROPERTIES: | |
| 326 | :header-args: :exports none | |
| 327 | :END: | |
| 328 | #+CALL: named() | |
| 329 | ||
| 330 | #+RESULTS: | |
| 331 | : rescallother | |
| 332 | ||
| 333 | Inline call_named() {{{results(=resinlinecallother=)}}} five. | |
| 334 | ||
| 335 | Inline call_named[:exports code]() {{{results(=resinlinecallcode=)}}} six. | |
| 323 | 336 | """ + "\n" |
| 324 | 337 | |
| 325 | 338 | /// Which code and results the HTML and Markdown exports keep, against ox-html with nothing |
Tests/OrgCoreTests/TangleTests.swift +130 −2
| @@ -230,7 +230,7 @@ struct TangleTests { | ||
| 230 | 230 | (let* ((dir (file-truename (make-temp-file "orgstar-tangle" t))) |
| 231 | 231 | (src (expand-file-name "notes.org" dir)) |
| 232 | 232 | (text (buffer-string)) out) |
| 233 | (require 'ob-shell) (require 'ob-python) | |
| 233 | (require 'ob-shell) (require 'ob-python) (require 'ob-C) (require 'ob-java) | |
| 234 | 234 | (with-temp-file src (insert text)) |
| 235 | 235 | (with-current-buffer (find-file-noselect src) |
| 236 | 236 | (let ((files (org-babel-tangle))) |
| @@ -291,7 +291,7 @@ struct TangleTests { | ||
| 291 | 291 | #expect(Self.describe(ours, root: "/oracle") == theirs.text, "ours:\n\(Self.describe(ours, root: "/oracle"))\nemacs:\n\(theirs.text)\n\(theirs.error)") |
| 292 | 292 | } |
| 293 | 293 | |
| 294 | /// The file names only: Emacs also wraps C, D, Java, Fortran and Clojure bodies with org-babel-expand-body:LANG. | |
| 294 | /// The file names only; `expandsBodiesLikeOrg` checks the bodies. | |
| 295 | 295 | @Test func extensionsLikeOrg() throws { |
| 296 | 296 | let languages = ["python", "ruby", "perl", "C", "C++", "D", "awk", "sed", "lua", "haskell", "java", "latex", "LilyPond", |
| 297 | 297 | "fortran", "clojure", "ocaml", "julia", "sh", "text"] |
| @@ -315,6 +315,134 @@ struct TangleTests { | ||
| 315 | 315 | #expect(names == theirs.text, "ours:\n\(names)\nemacs:\n\(theirs.text)\n\(theirs.error)") |
| 316 | 316 | } |
| 317 | 317 | |
| 318 | static let expanded = #""" | |
| 319 | #+NAME: tbl | |
| 320 | | a | b | | |
| 321 | |---+-----| | |
| 322 | | 1 | 2.5 | | |
| 323 | | 3 | 4 | | |
| 324 | ||
| 325 | #+NAME: items | |
| 326 | - x | |
| 327 | - y | |
| 328 | ||
| 329 | #+NAME: ints | |
| 330 | | 1 | 2 | | |
| 331 | | 3 | 4 | | |
| 332 | ||
| 333 | * C | |
| 334 | :PROPERTIES: | |
| 335 | :includes: <never.h> | |
| 336 | :imports: never.d | |
| 337 | :END: | |
| 338 | #+begin_src C :tangle out.c | |
| 339 | printf("hi\n"); | |
| 340 | #+end_src | |
| 341 | #+begin_src C :tangle out.c :includes <stdio.h> <math.h> :defines N 5 M 6 :var n=3 x=2.5 s="str" l=items | |
| 342 | printf("%d\n", n); | |
| 343 | #+end_src | |
| 344 | #+begin_src C :tangle out.c :includes '(<stdio.h> "local.h") :defines '("A 1") :var t=tbl | |
| 345 | int main() { return t_h(0, "a"); } | |
| 346 | #+end_src | |
| 347 | #+begin_src C :tangle out.c :main no :prologue "// pro" :epilogue "// epi" :var m=ints | |
| 348 | int f(void) { return 1; } | |
| 349 | #+end_src | |
| 350 | #+begin_src C++ :tangle out.cpp :namespaces std :includes <iostream> | |
| 351 | cout << 1; | |
| 352 | #+end_src | |
| 353 | #+begin_src cpp :tangle out.cpp | |
| 354 | INT main(void) {} | |
| 355 | #+end_src | |
| 356 | #+begin_src D :tangle out.d :imports std.math :var n=3 s="d" l=items t=tbl | |
| 357 | writeln(n); | |
| 358 | #+end_src | |
| 359 | #+begin_src D :tangle out.d :main no | |
| 360 | void f() {} | |
| 361 | #+end_src | |
| 362 | * Java | |
| 363 | #+begin_src java :tangle Main.java | |
| 364 | System.out.println(1); | |
| 365 | if (true) { | |
| 366 | System.out.println(2); | |
| 367 | } | |
| 368 | #+end_src | |
| 369 | #+begin_src java :tangle Foo.java :classname com.x.Foo :imports java.util.List java.util.Map :var n=3 x=2.5 s="s" l=items t=ints | |
| 370 | System.out.println(n); | |
| 371 | #+end_src | |
| 372 | #+begin_src java :tangle Pkg.java | |
| 373 | package org.y; | |
| 374 | import java.util.List; | |
| 375 | ||
| 376 | public class Pkg { | |
| 377 | public static void main(String[] args) { | |
| 378 | System.out.println(1); | |
| 379 | } | |
| 380 | } | |
| 381 | #+end_src | |
| 382 | #+begin_src java :tangle Methods.java :var n=1 | |
| 383 | static int twice(int x) { | |
| 384 | return 2 * x; | |
| 385 | } | |
| 386 | #+end_src | |
| 387 | #+begin_src java :tangle Pro.java :prologue "int a = 1;" :epilogue "int b = 2;" | |
| 388 | int c = 3; | |
| 389 | #+end_src | |
| 390 | * Fortran | |
| 391 | #+begin_src fortran :tangle out.F90 | |
| 392 | print *, 1 | |
| 393 | #+end_src | |
| 394 | #+begin_src fortran :tangle out.F90 :var n=3 x=2.5 s="str" l='(1 2) m=ints :includes "a.h" :defines N 5 :prologue "! pro" | |
| 395 | print *, n | |
| 396 | #+end_src | |
| 397 | #+begin_src fortran :tangle out.F90 | |
| 398 | PROGRAM hello | |
| 399 | print *, 2 | |
| 400 | end program hello | |
| 401 | #+end_src | |
| 402 | #+begin_src fortran :tangle out.F90 :main no :prologue "! pro" :epilogue "! epi" :var n=1 | |
| 403 | subroutine s | |
| 404 | end subroutine s | |
| 405 | #+end_src | |
| 406 | * Clojure | |
| 407 | #+begin_src clojure :tangle out.clj | |
| 408 | (+ 1 2) | |
| 409 | #+end_src | |
| 410 | #+begin_src clojure :tangle out.clj :var n=3 s="a\"b" l=items t=tbl | |
| 411 | ;; a comment | |
| 412 | (+ n 1) | |
| 413 | #+end_src | |
| 414 | #+begin_src clojure :tangle out.clj :ns my.ns :results output | |
| 415 | (println 1) | |
| 416 | #+end_src | |
| 417 | #+begin_src clojure :tangle out.clj :results pp | |
| 418 | {:a 1} | |
| 419 | #+end_src | |
| 420 | * Python | |
| 421 | #+begin_src python :tangle out.py :var t=tbl | |
| 422 | print(t) | |
| 423 | #+end_src | |
| 424 | """# + "\n" | |
| 425 | ||
| 426 | @Test func expandsBodiesLikeOrg() throws { | |
| 427 | let form = """ | |
| 428 | (let* ((dir (file-truename (make-temp-file "orgstar-tangle" t))) | |
| 429 | (src (expand-file-name "notes.org" dir)) | |
| 430 | (text (buffer-string)) out) | |
| 431 | (require 'ob-C) (require 'ob-java) (require 'ob-fortran) (require 'ob-clojure) (require 'ob-python) | |
| 432 | (setq org-babel-clojure-backend 'babashka) | |
| 433 | (with-temp-file src (insert text)) | |
| 434 | (with-current-buffer (find-file-noselect src) | |
| 435 | (let ((files (org-babel-tangle))) | |
| 436 | (setq out (mapconcat (lambda (f) (format "== %s %o\\n%s" (file-relative-name f dir) (file-modes f) | |
| 437 | (with-temp-buffer (insert-file-contents f) (buffer-string)))) | |
| 438 | (sort (copy-sequence files) #'string<) "")))) | |
| 439 | (erase-buffer) (insert out)) | |
| 440 | """ | |
| 441 | let theirs = try EmacsOracle.run([EmacsOracle.Case(text: Self.expanded, point: 1, form: form)])[0] | |
| 442 | let ours = try Tangle.run(text: Self.expanded, path: "/oracle/notes.org").get() | |
| 443 | #expect(Self.describe(ours, root: "/oracle") == theirs.text, "ours:\n\(Self.describe(ours, root: "/oracle"))\nemacs:\n\(theirs.text)\n\(theirs.error)") | |
| 444 | } | |
| 445 | ||
| 318 | 446 | @Test func refusesWhatItCantDo() { |
| 319 | 447 | for (language, header) in [("haskell", ":comments link"), ("sh", ":tangle (concat user-emacs-directory \"a.sh\")"), ("sh", ":tangle-mode go+q")] { |
| 320 | 448 | let text = "#+begin_src \(language) :tangle yes \(header)\necho\n#+end_src\n" |