| @@ -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 | } |