| @@ -113,8 +113,9 @@ public enum Babel { |
| 113 | |
113 | |
| 114 | public struct Options: Sendable, Equatable { |
114 | public struct Options: Sendable, Equatable { |
| 115 | public var python = "python3" |
115 | public var python = "python3" |
| 116 | /// `shell-file-name`, for blocks in the `shell` language. |
116 | /// `shell-file-name`, from `SHELL` as Emacs sets it: runs blocks in the `shell` language |
| 117 | public var shell = "/bin/sh" |
117 | /// and chooses how variables are written in the `:cache` hash. |
| |
118 | public var shell = ProcessInfo.processInfo.environment["SHELL"] ?? "/bin/sh" |
| 118 | public init() {} |
119 | public init() {} |
| 119 | } |
120 | } |
| 120 | |
121 | |
| @@ -262,6 +263,8 @@ public enum Babel { |
| 262 | static func plan(_ block: SrcBlockInfo, params: Params, model: DocumentModel, text ns: NSString, options: Options, |
263 | static func plan(_ block: SrcBlockInfo, params: Params, model: DocumentModel, text ns: NSString, options: Options, |
| 263 | location: Range<Int>, name: String?, inline: Bool = false, refs: References? = nil, useCache: Bool = true) -> Result<BabelJob, Failure> { |
264 | location: Range<Int>, name: String?, inline: Bool = false, refs: References? = nil, useCache: Bool = true) -> Result<BabelJob, Failure> { |
| 264 | let refs = refs ?? References(resolved: [:], options: options) |
265 | let refs = refs ?? References(resolved: [:], options: options) |
| |
266 | var params = params |
| |
267 | params.evaluateLisp() |
| 265 | guard let language = block.language else { return .failure(.message("This block has no language")) } |
268 | guard let language = block.language else { return .failure(.message("This block has no language")) } |
| 266 | let eval = params.single[":eval"]?.lowercased() |
269 | let eval = params.single[":eval"]?.lowercased() |
| 267 | if eval == "never" || eval == "no" || eval == "never-export" || eval == "no-export" { |
270 | if eval == "never" || eval == "no" || eval == "never-export" || eval == "no-export" { |
| @@ -298,7 +301,7 @@ public enum Babel { |
| 298 | let finishBase = { (kind: BabelJob.Finish.Kind) in |
301 | let finishBase = { (kind: BabelJob.Finish.Kind) in |
| 299 | BabelJob.Finish(kind: kind, language: language, params: params.results, output: output, wrap: params.wrap, name: name, inline: inline, |
302 | BabelJob.Finish(kind: kind, language: language, params: params.results, output: output, wrap: params.wrap, name: name, inline: inline, |
| 300 | file: toFile ? file : nil, fileDescription: params.single[":file-desc"], hash: hash, |
303 | file: toFile ? file : nil, fileDescription: params.single[":file-desc"], hash: hash, |
| 301 | colnames: tables.picked(tables.colnames, params.single[":colnames"]), rownames: tables.picked(tables.rownames, params.single[":rownames"])) |
304 | colnames: tables.picked(tables.colnames, ":colnames", params), rownames: tables.picked(tables.rownames, ":rownames", params)) |
| 302 | } |
305 | } |
| 303 | let vars: [(String, VarValue)] |
306 | let vars: [(String, VarValue)] |
| 304 | var stdin: VarValue? |
307 | var stdin: VarValue? |
| @@ -581,14 +584,26 @@ public enum Babel { |
| 581 | var colnames: [(name: String, names: [Cell])] = [] |
584 | var colnames: [(name: String, names: [Cell])] = [] |
| 582 | var rownames: [(name: String, names: [Cell])] = [] |
585 | var rownames: [(name: String, names: [Cell])] = [] |
| 583 | |
586 | |
| 584 | /// `org-babel-pick-name`: the last variable's names, unless the header argument is |
587 | /// `org-babel-pick-name`: a Lisp list given as the header argument, or the last |
| 585 | /// absent, whose `nil` selects none. |
588 | /// variable's names, unless the header argument is absent, whose `nil` selects none. |
| 586 | func picked(_ names: [(name: String, names: [Cell])], _ selector: String?) -> [String]? { |
589 | func picked(_ names: [(name: String, names: [Cell])], _ key: String, _ params: Params) -> [String]? { |
| 587 | guard selector != nil, let last = names.last else { return nil } |
590 | if let list = params.evaluated[key].flatMap(Babel.lispNames) { return list } |
| |
591 | guard params.single[key] != nil, let last = names.last else { return nil } |
| 588 | return last.names.map(Babel.cellText) |
592 | return last.names.map(Babel.cellText) |
| 589 | } |
593 | } |
| 590 | } |
594 | } |
| 591 | |
595 | |
| |
596 | /// A Lisp list of strings and numbers, as names. |
| |
597 | static func lispNames(_ value: Sexp) -> [String]? { |
| |
598 | guard case .list(let items) = value else { return nil } |
| |
599 | return try? items.map { item in |
| |
600 | if let s = item.string { return s } |
| |
601 | let printed = Elisp.printed(item) |
| |
602 | guard number(printed) == printed else { throw Failure.message(printed) } |
| |
603 | return printed |
| |
604 | } |
| |
605 | } |
| |
606 | |
| 592 | static func cellText(_ c: Cell) -> String { |
607 | static func cellText(_ c: Cell) -> String { |
| 593 | switch c { |
608 | switch c { |
| 594 | case .number(let n): n |
609 | case .number(let n): n |
| @@ -600,8 +615,8 @@ public enum Babel { |
| 600 | /// by default when only its second row is a rule; its first column with `:rownames`; its |
615 | /// by default when only its second row is a rule; its first column with `:rownames`; its |
| 601 | /// rules unless `:hlines yes`. |
616 | /// rules unless `:hlines yes`. |
| 602 | static func disassemble(_ vars: [(String, VarValue)], params: Params) throws -> Tables { |
617 | static func disassemble(_ vars: [(String, VarValue)], params: Params) throws -> Tables { |
| 603 | for key in [":colnames", ":rownames"] where params.lisp.contains(key) { |
618 | for key in [":colnames", ":rownames"] where params.lisp.contains(key) && params.evaluated[key].flatMap(lispNames) == nil { |
| 604 | throw Failure.message("\(key) with a Lisp value isn't supported yet; nothing was run.") |
619 | throw Failure.message("\(key) \(params.single[key] ?? "") is Lisp that only Emacs can evaluate; nothing was run.") |
| 605 | } |
620 | } |
| 606 | let colnames = params.single[":colnames"] |
621 | let colnames = params.single[":colnames"] |
| 607 | let rownames = params.single[":rownames"] |
622 | let rownames = params.single[":rownames"] |
| @@ -708,8 +723,8 @@ public enum Babel { |
| 708 | /// What `org-babel-sha1-hash` hashes: the header arguments after `org-babel-process-params`, |
723 | /// What `org-babel-sha1-hash` hashes: the header arguments after `org-babel-process-params`, |
| 709 | /// sorted by key and printed with `%S`, then the expanded body. |
724 | /// sorted by key and printed with `%S`, then the expanded body. |
| 710 | static func cacheInput(language: String, params: Params, tables: Tables, expanded: String, call: Bool) throws -> String { |
725 | static func cacheInput(language: String, params: Params, tables: Tables, expanded: String, call: Bool) throws -> String { |
| 711 | if !params.lisp.isEmpty { |
726 | if let key = params.lisp.first(where: { params.evaluated[$0] == nil }) { |
| 712 | throw Failure.message(":cache with Lisp header values isn't supported yet; nothing was run.") |
727 | throw Failure.message(":cache with \(key) \(params.single[key] ?? "") needs Emacs to evaluate the Lisp; nothing was run.") |
| 713 | } |
728 | } |
| 714 | let ignored: Set<String> = ["replace", "silent", "none", "discard", "append", "prepend"] |
729 | let ignored: Set<String> = ["replace", "silent", "none", "discard", "append", "prepend"] |
| 715 | let drawsFile = language == "dot" || language == "plantuml" |
730 | let drawsFile = language == "dot" || language == "plantuml" |
| @@ -719,7 +734,9 @@ public enum Babel { |
| 719 | var entries: [(key: String, printed: String)] = [] |
734 | var entries: [(key: String, printed: String)] = [] |
| 720 | for (key, value) in defaults.merging(params.single, uniquingKeysWith: { $1 }) { |
735 | for (key, value) in defaults.merging(params.single, uniquingKeysWith: { $1 }) { |
| 721 | let printed: String |
736 | let printed: String |
| 722 | if value.isEmpty { |
737 | if let evaluated = params.evaluated[key] { |
| |
738 | printed = Elisp.printed(evaluated, escape: true) |
| |
739 | } else if value.isEmpty { |
| 723 | continue |
740 | continue |
| 724 | } else if key == ":exports" { |
741 | } else if key == ":exports" { |
| 725 | printed = lispString(value.split(whereSeparator: \.isWhitespace).map(String.init).filter { !ignored.contains($0) }.sorted().joined(separator: " ")) |
742 | printed = lispString(value.split(whereSeparator: \.isWhitespace).map(String.init).filter { !ignored.contains($0) }.sorted().joined(separator: " ")) |
| @@ -803,10 +820,32 @@ public enum Babel { |
| 803 | var single: [String: String] = [:] |
820 | var single: [String: String] = [:] |
| 804 | var vars: [(name: String, value: String)] = [] |
821 | var vars: [(name: String, value: String)] = [] |
| 805 | var wrap: String? |
822 | var wrap: String? |
| 806 | /// Keys whose value is Lisp to evaluate (`org-babel-read`): unquoted, starting with `(`. |
823 | /// Keys whose value is Lisp to evaluate (`org-babel-read`): unquoted, starting with `(`, `'`, `` ` `` or `[`. |
| 807 | var lisp: Set<String> = [] |
824 | var lisp: Set<String> = [] |
| 808 | /// Keys whose value was a quoted string, which `org-babel-read` doesn't take for a number. |
825 | /// Keys whose value was a quoted string, which `org-babel-read` doesn't take for a number. |
| 809 | var quoted: Set<String> = [] |
826 | var quoted: Set<String> = [] |
| |
827 | /// Lisp values evaluated to something other than a string or number, such as a list of |
| |
828 | /// column names. |
| |
829 | var evaluated: [String: Sexp] = [:] |
| |
830 | |
| |
831 | /// `org-babel-read` on the Lisp values the interpreter can evaluate: a string or number |
| |
832 | /// takes the form's place; other values are kept in `evaluated`. |
| |
833 | mutating func evaluateLisp() { |
| |
834 | for key in lisp { |
| |
835 | guard let form = try? LispReader.readFirst(single[key] ?? "").sexp, let value = try? Elisp().eval(form) else { continue } |
| |
836 | let printed = Elisp.printed(value) |
| |
837 | if let s = value.string { |
| |
838 | single[key] = s |
| |
839 | quoted.insert(key) |
| |
840 | lisp.remove(key) |
| |
841 | } else if Babel.number(printed) == printed { |
| |
842 | single[key] = printed |
| |
843 | lisp.remove(key) |
| |
844 | } else { |
| |
845 | evaluated[key] = value |
| |
846 | } |
| |
847 | } |
| |
848 | } |
| 810 | |
849 | |
| 811 | static let resultGroups: [Set<String>] = [ |
850 | static let resultGroups: [Set<String>] = [ |
| 812 | ["file", "list", "vector", "table", "scalar", "verbatim"], |
851 | ["file", "list", "vector", "table", "scalar", "verbatim"], |
| @@ -859,7 +898,7 @@ public enum Babel { |
| 859 | let quoted = key != ":var" && raw.count >= 2 && raw.hasPrefix("\"") && raw.hasSuffix("\"") |
898 | let quoted = key != ":var" && raw.count >= 2 && raw.hasPrefix("\"") && raw.hasSuffix("\"") |
| 860 | let value = quoted ? Params.unescape(String(raw.dropFirst().dropLast())) : raw |
899 | let value = quoted ? Params.unescape(String(raw.dropFirst().dropLast())) : raw |
| 861 | if key != ":var" { |
900 | if key != ":var" { |
| 862 | if !quoted, value.hasPrefix("(") { lisp.insert(key) } else { lisp.remove(key) } |
901 | if !quoted, let first = value.first, "('`[".contains(first) { lisp.insert(key) } else { lisp.remove(key) } |
| 863 | if quoted { self.quoted.insert(key) } else { self.quoted.remove(key) } |
902 | if quoted { self.quoted.insert(key) } else { self.quoted.remove(key) } |
| 864 | } |
903 | } |
| 865 | switch key { |
904 | switch key { |
| @@ -1060,7 +1099,8 @@ public enum Babel { |
| 1060 | let extra = arguments.map { [splitArguments($0).map { (":var", $0) }] } ?? [] |
1099 | let extra = arguments.map { [splitArguments($0).map { (":var", $0) }] } ?? [] |
| 1061 | var inner = refs |
1100 | var inner = refs |
| 1062 | inner.visiting.insert(name) |
1101 | inner.visiting.insert(name) |
| 1063 | let params = try Params(block: block, model: model, text: text, extra: extra) |
1102 | var params = try Params(block: block, model: model, text: text, extra: extra) |
| |
1103 | params.evaluateLisp() |
| 1064 | // A cached block gives its result without running, as `org-babel-execute-src-block` does. |
1104 | // A cached block gives its result without running, as `org-babel-execute-src-block` does. |
| 1065 | var useCache = true |
1105 | var useCache = true |
| 1066 | if params.single[":cache"] == "yes", let language = block.language { |
1106 | if params.single[":cache"] == "yes", let language = block.language { |