krz/orgstar

A native macOS editor for org-mode files. editor org-mode swift

Sources/OrgCore/Compute/Babel.swift

b98a6509c5ae75e5172dd333d1b6105fd6ceee0e
orgstar/Sources/OrgCore/Compute/Babel.swift history · blame · raw

2017 lines · 106969 bytes

102 symbols in this file
   1import CryptoKit
   2import Foundation
   3
   4// Babel (ob-core.el, ob-shell.el, ob-python.el, ob-emacs-lisp.el, Org 9.8.7): executing a src
   5// block is planned here, run by the platform, and finished here. The plan resolves header
   6// arguments and refuses anything that would make the run differ from Emacs's; the finish turns
   7// the output into a result the way `org-babel-insert-result` writes it.
   8
   9/// A src block run for the platform to carry out.
  10public struct BabelJob: Sendable, Equatable {
  11    public enum Runner: Sendable, Equatable {
  12        /// An interpreter reading the program on standard input.
  13        case process(executable: String, arguments: [String])
  14        /// `emacs --batch` loading `input` as a script.
  15        case emacs
  16        /// A long-running interpreter named by `:session`: a shell gets `input` as commands, Python
  17        /// runs `input` as a program in its globals.
  18        case session(name: String, executable: String, python: Bool)
  19    }
  20
  21    public let language: String
  22    public let runner: Runner
  23    /// Standard input, or the Emacs script. `Babel.resultFileToken` stands for a file the runner
  24    /// creates; its contents come back as `BabelOutput.resultFile`.
  25    public let input: String
  26    /// `:dir`, relative to the file's folder; nil for the folder itself.
  27    public let directory: String?
  28    /// The block as it was planned, to find it again when the run ends.
  29    public let block: Range<Int>
  30    public let blockText: String
  31    /// Asks every time (`:eval query`) rather than trusting by content.
  32    public let alwaysAsk: Bool
  33    public let finish: Finish
  34    /// A named block a `:var` refers to: run it first, then `Babel.resume` with its output.
  35    public var needs: [Need] = []
  36    /// With `needs`: the text of every block the run may execute, the one asked about and
  37    /// those its references reach. A later job for a block not among them is refused.
  38    public internal(set) var approved: [String] = []
  39    /// Values of the references resolved so far, and how to plan again.
  40    var resolved: [String: Babel.VarValue] = [:]
  41    var caret = 0
  42    var options = Babel.Options()
  43    /// A script for the runner to write to an executable file, for `Babel.scriptFileToken`.
  44    public internal(set) var script: String?
  45    /// Programs the run needs: for each list, one of them on PATH.
  46    public internal(set) var programs: [[String]] = []
  47    /// An Emacs Lisp block's form, for `Babel.evaluateNatively`.
  48    public internal(set) var lispForm: String?
  49    /// A Lisp header value couldn't be evaluated without the file's path: plan again with
  50    /// `Babel.replan` before running.
  51    public internal(set) var needsFile = false
  52
  53    public struct Need: Sendable, Equatable {
  54        /// The `:var` value, `name` or `name(args)`.
  55        public let reference: String
  56        public let job: BabelJob
  57    }
  58
  59    public struct Finish: Sendable, Equatable {
  60        /// `compiled`: C, C++, D, Java, Fortran and Clojure, by `language`.
  61        enum Kind: Sendable, Equatable { case shell(valueIsExitStatus: Bool), python, generic, emacsLisp, compiled }
  62        let kind: Kind
  63        var language = ""
  64        public internal(set) var params: [String]
  65        let output: Bool
  66        let wrap: String?
  67        let name: String?
  68        /// An inline block: the result goes after it, in `{{{results(…)}}}`.
  69        var inline = false
  70        /// `:file` (under `:output-dir`) with `:results file`, and `:file-desc`.
  71        var file: String?
  72        var fileDescription: String?
  73        /// The program writes `file` itself, as graphics languages do.
  74        var writesFile = false
  75        /// `:cache yes`: the hash for the `#+RESULTS[…]:` line.
  76        var hash: String?
  77        /// Column and row names a table `:var` gave up, put back on a table result
  78        /// (`org-babel-reassemble-table`).
  79        var colnames: [String]?
  80        var rownames: [String]?
  81    }
  82}
  83
  84public struct BabelOutput: Sendable, Equatable {
  85    public var stdout: String
  86    public var resultFile: String?
  87    /// What the program wrote to standard error, and its exit status: Emacs shows them in
  88    /// `*Org-Babel Error Output*` and still inserts the results.
  89    public var stderr: String
  90    public var status: Int32
  91    public init(stdout: String, resultFile: String? = nil, stderr: String = "", status: Int32 = 0) {
  92        self.stdout = stdout
  93        self.resultFile = resultFile
  94        self.stderr = stderr
  95        self.status = status
  96    }
  97
  98    /// A line for the message area when the program failed or complained, else nil.
  99    public var problem: String? {
 100        let first = stderr.split(separator: "\n", omittingEmptySubsequences: true).first.map(String.init)
 101        switch (status, first) {
 102        case (0, nil): return nil
 103        case (0, let line?): return line
 104        case (let code, nil): return "Exited with status \(code)"
 105        case (let code, let line?): return "Exited with status \(code): \(line)"
 106        }
 107    }
 108}
 109
 110public enum Babel {
 111    public static let resultFileToken = "__ORGSTAR_RESULT_FILE__"
 112    /// Stands, in a job's arguments, for the executable file the runner writes `job.script` to.
 113    public static let scriptFileToken = "__ORGSTAR_SCRIPT_FILE__"
 114    static let shells: Set<String> = ["sh", "bash", "zsh", "shell", "fish", "ksh", "dash", "ash", "csh", "mksh", "posh"]
 115    static let generic: [String: (String, [String])] = [
 116        "ruby": ("ruby", []), "js": ("node", []), "javascript": ("node", []), "R": ("Rscript", ["-"]),
 117        "awk": ("awk", ["-f", "/dev/stdin"]),
 118    ]
 119
 120    public struct Options: Sendable, Equatable {
 121        public var python = "python3"
 122        /// `shell-file-name`, from `SHELL` as Emacs sets it: runs blocks in the `shell` language
 123        /// and chooses how variables are written in the `:cache` hash.
 124        public var shell = ProcessInfo.processInfo.environment["SHELL"] ?? "/bin/sh"
 125        /// The file the block is in, for Lisp header values that use it.
 126        public var file: String?
 127        public init() {}
 128    }
 129
 130    // MARK: - Planning
 131
 132    /// The run for the src block at the caret, or why it can't run.
 133    public static func plan(_ context: EditContext, options: Options = Options()) -> Result<BabelJob, Failure> {
 134        plan(context, options: options, resolved: [:])
 135    }
 136
 137    /// Plans the block again with the value of the block `job` needed: the output of
 138    /// `need.job`, as `org-babel-ref-resolve` reads it.
 139    public static func resume(_ job: BabelJob, need: BabelJob.Need, output: BabelOutput, text: String, tree: OrgTree) -> Result<BabelJob, Failure> {
 140        var resolved = job.resolved
 141        resolved[need.reference] = varValue(need.job.finish, output)
 142        let context = EditContext(revision: 0, text: text, tree: tree, selection: [job.caret..<job.caret])
 143        return plan(context, options: job.options, resolved: resolved)
 144    }
 145
 146    /// Plans `job`'s block again knowing the file it is in.
 147    public static func replan(_ job: BabelJob, file: String, text: String, tree: OrgTree) -> Result<BabelJob, Failure> {
 148        var options = job.options
 149        options.file = file
 150        let context = EditContext(revision: 0, text: text, tree: tree, selection: [job.caret..<job.caret])
 151        return plan(context, options: options, resolved: job.resolved)
 152    }
 153
 154    static func plan(_ context: EditContext, options: Options, resolved: [String: VarValue]) -> Result<BabelJob, Failure> {
 155        let text = context.text
 156        let ns = text as NSString
 157        let model = DocumentModel(tree: context.tree)
 158        let refs = References(resolved: resolved, options: options)
 159        func located(_ result: Result<BabelJob, Failure>) -> Result<BabelJob, Failure> {
 160            result.map { job in
 161                var job = job
 162                (job.caret, job.resolved, job.options) = (context.caret, resolved, options)
 163                return job
 164            }
 165        }
 166        do {
 167            if let block = model.srcBlocks.first(where: { $0.range.contains(context.caret) || $0.range.upperBound == context.caret }) {
 168                return located(plan(block, params: try Params(block: block, model: model, text: ns), model: model, text: ns, options: options,
 169                                    location: block.range, name: block.name, refs: refs))
 170            }
 171            if let call = try callAt(context.caret, model: model, text: ns) {
 172                return located(plan(call.block, params: call.params, model: model, text: ns, options: options, location: call.location, name: call.name, refs: refs))
 173            }
 174            if let call = try inlineCallAt(context.caret, tree: context.tree, model: model, text: ns) {
 175                return located(plan(call.block, params: call.params, model: model, text: ns, options: options, location: call.location, name: nil, inline: true, refs: refs))
 176            }
 177            if let inline = try inlineAt(context.caret, tree: context.tree, model: model, text: ns) {
 178                return located(plan(inline.block, params: inline.params, model: model, text: ns, options: options, location: inline.block.range, name: nil, inline: true, refs: refs))
 179            }
 180        } catch let error as Failure {
 181            return .failure(error)
 182        } catch {
 183            return .failure(.message("\(error)"))
 184        }
 185        return .failure(.message("Not in a source block"))
 186    }
 187
 188    /// The `#+CALL:` line at `offset`: the block it names, with the call's inside header, its
 189    /// arguments as `:var` and its end header over the block's own (`org-babel-lob-get-info`).
 190    static func callAt(_ offset: Int, model: DocumentModel, text: NSString) throws -> (block: SrcBlockInfo, params: Params, location: Range<Int>, name: String?)? {
 191        let lineRange = text.lineRange(for: NSRange(location: min(offset, text.length), length: 0))
 192        let line = text.substring(with: lineRange)
 193        guard let m = line.firstMatch(of: #/(?i)^[ \t]*#\+CALL:[ \t]*([^\s(\[]+)(?:\[([^\]]*)\])?\((.*?)\)[ \t]*(.*?)[ \t]*\n?$/#) else { return nil }
 194        let name = String(m.1)
 195        guard let block = model.srcBlocks.first(where: { $0.name == name }) else { throw Failure.message("Reference '\(name)' not found in this buffer") }
 196        var extra = [lobDefaults] + callProperties(block, at: lineRange.location, model: model)
 197        if let inside = m.2 { extra.append(Params.parse(String(inside))) }
 198        extra.append(splitArguments(String(m.3)).map { (":var", $0) })
 199        if !m.4.isEmpty { extra.append(Params.parse(String(m.4))) }
 200        // The call's own #+NAME names its results.
 201        var callName: String?
 202        if lineRange.location > 0 {
 203            let previous = text.substring(with: text.lineRange(for: NSRange(location: lineRange.location - 1, length: 0)))
 204            callName = previous.firstMatch(of: #/(?i)^[ \t]*#\+NAME:[ \t]*(\S+)/#).map { String($0.1) }
 205        }
 206        let location = lineRange.location..<NSMaxRange(lineRange)
 207        return (block, try Params(block: block, model: model, text: text, extra: extra), location, callName)
 208    }
 209
 210    /// The inline `call_NAME[INSIDE](ARGS)[END]` at `offset`: like `#+CALL`, with the result
 211    /// after the call.
 212    static func inlineCallAt(_ offset: Int, tree: OrgTree, model: DocumentModel, text: NSString) throws -> (block: SrcBlockInfo, params: Params, location: Range<Int>)? {
 213        var node = tree.root
 214        var found: SyntaxNode?
 215        while let child = node.child(containing: offset) {
 216            if child.kind == .inlineBabelCall { found = child }
 217            node = child
 218        }
 219        guard let object = found, let m = object.text.firstMatch(of: /^call_([^()\[\]\s]+)(?:\[([^\]]*)\])?\((.*?)\)(?:\[([^\]]*)\])?$/) else { return nil }
 220        let name = String(m.1)
 221        guard let block = model.srcBlocks.first(where: { $0.name == name }) else { throw Failure.message("Reference '\(name)' not found in this buffer") }
 222        var extra = [lobDefaults] + callProperties(block, at: object.range.lowerBound, model: model)
 223        if let inside = m.2 { extra.append(Params.parse(String(inside))) }
 224        extra.append(splitArguments(String(m.3)).map { (":var", $0) })
 225        if let end = m.4 { extra.append(Params.parse(String(end))) }
 226        return (block, try Params(block: block, model: model, text: text, extra: extra), object.range)
 227    }
 228
 229    /// The `header-args` properties where a call is, in the called block's language, which
 230    /// `org-babel-lob-get-info` puts after `lobDefaults`.
 231    static func callProperties(_ block: SrcBlockInfo, at location: Int, model: DocumentModel) -> [[(String, String)]] {
 232        let heading = model.headings.lastIndex { $0.headingRange.lowerBound <= location }
 233        return Params.propertyLayers(language: block.language ?? "", heading: heading, model: model)
 234    }
 235
 236    /// `org-babel-default-lob-header-args`, over the called block's own header arguments.
 237    static let lobDefaults = [(":exports", "results")]
 238
 239    /// `org-babel-default-header-args:LANG` in Org 9.8.7 other than `:exports`.
 240    static let languageDefaults = ["java": [(":results", "output"), (":dir", ".")]]
 241
 242    /// `:exports` from the language's `org-babel-default-header-args:LANG` in Org 9.8.7.
 243    static let languageExports = ["dot": "results", "plantuml": "results", "ditaa": "results", "gnuplot": "results",
 244                                  "latex": "results", "lilypond": "results"]
 245
 246    /// What export shows of the src block, `#+CALL:` line, inline call or inline src block
 247    /// starting at `offset` (`org-babel-exp-process-buffer`): `code`, `results`, `both` or
 248    /// `none`, from the header arguments as evaluation resolves them. Calls never show code.
 249    /// Nil when nothing there runs.
 250    public static func exports(at offset: Int, tree: OrgTree, model: DocumentModel, text: NSString) -> String? {
 251        func value(_ params: Params?, language: String?) -> String {
 252            (params?.single[":exports"] ?? language.flatMap { languageExports[$0] } ?? "code").lowercased()
 253        }
 254        if let block = model.srcBlocks.first(where: { $0.range.lowerBound == offset }) {
 255            return value(try? Params(block: block, model: model, text: text), language: block.language)
 256        }
 257        let line = text.substring(with: text.lineRange(for: NSRange(location: min(offset, text.length), length: 0)))
 258        let call: Params?
 259        if line.range(of: "(?i)^[ \\t]*#\\+CALL:", options: .regularExpression) != nil {
 260            call = (try? callAt(offset, model: model, text: text))?.params
 261        } else if tree.root.descendants().contains(where: { $0.kind == .inlineBabelCall && $0.range.lowerBound == offset }) {
 262            // `org-babel-remove-result` only takes away `#+RESULTS:`, so an inline call's
 263            // `{{{results(...)}}}` stays whatever `:exports` says.
 264            return "results"
 265        } else if let inline = try? inlineAt(offset, tree: tree, model: model, text: text), inline.block.range.lowerBound == offset {
 266            return value(inline.params, language: inline.block.language)
 267        } else {
 268            return nil
 269        }
 270        // `org-babel-exp-code` for a call is `org-babel-exp-call-line-template`, empty.
 271        switch call?.single[":exports"]?.lowercased() ?? "results" {
 272        case "both", "results": return "results"
 273        default: return "none"
 274        }
 275    }
 276
 277    /// Call arguments split on commas outside quotes and parentheses.
 278    static func splitArguments(_ s: String) -> [String] {
 279        var parts: [String] = []
 280        var current = ""
 281        var depth = 0
 282        var quoted = false
 283        for c in s {
 284            if c == "\"" { quoted.toggle() }
 285            if !quoted, c == "(" { depth += 1 }
 286            if !quoted, c == ")" { depth -= 1 }
 287            if c == ",", !quoted, depth == 0 {
 288                parts.append(current)
 289                current = ""
 290            } else {
 291                current.append(c)
 292            }
 293        }
 294        parts.append(current)
 295        return parts.map { $0.trimmingCharacters(in: .whitespaces) }.filter { !$0.isEmpty }
 296    }
 297
 298    /// The inline `src_LANG[HEADERS]{BODY}` at `offset`.
 299    static func inlineAt(_ offset: Int, tree: OrgTree, model: DocumentModel, text: NSString) throws -> (block: SrcBlockInfo, params: Params)? {
 300        var node = tree.root
 301        var found: SyntaxNode?
 302        while let child = node.child(containing: offset) {
 303            if child.kind == .inlineSourceBlock { found = child }
 304            node = child
 305        }
 306        guard let object = found, let m = object.text.firstMatch(of: /^src_([^\s\[{]+)(?:\[([^\]]*)\])?\{(.*)\}$/) else { return nil }
 307        let heading = model.headings.lastIndex { $0.headingRange.lowerBound <= object.range.lowerBound }
 308        let block = SrcBlockInfo(range: object.range, heading: heading, name: nil, language: String(m.1), switches: [], body: String(m.3), headerArguments: [:])
 309        let extra = m.2.map { [Params.parse(String($0))] } ?? []
 310        return (block, try Params(block: block, model: model, text: text, defaults: [(":exports", "results"), (":hlines", "yes")], extra: extra, inline: true))
 311    }
 312
 313    /// What `:var` references resolve to while planning.
 314    struct References {
 315        var resolved: [String: VarValue]
 316        var options: Options
 317        /// Blocks being planned for a reference, to stop cycles.
 318        var visiting: Set<String> = []
 319    }
 320
 321    /// A reference to a block that has to run first.
 322    struct Needed: Error {
 323        let need: BabelJob.Need
 324    }
 325
 326    static func plan(_ block: SrcBlockInfo, params: Params, model: DocumentModel, text ns: NSString, options: Options,
 327                     location: Range<Int>, name: String?, inline: Bool = false, refs: References? = nil, useCache: Bool = true) -> Result<BabelJob, Failure> {
 328        var evaluated = params
 329        evaluated.evaluateLisp(file: options.file)
 330        let needsFile = options.file == nil && evaluated.lisp.contains { evaluated.evaluated[$0] == nil }
 331        return planBlock(block, params: params, model: model, text: ns, options: options, location: location, name: name, inline: inline,
 332                         refs: refs, useCache: useCache).map { job in
 333            var job = job
 334            job.needsFile = needsFile
 335            return job
 336        }
 337    }
 338
 339    static func planBlock(_ block: SrcBlockInfo, params: Params, model: DocumentModel, text ns: NSString, options: Options,
 340                          location: Range<Int>, name: String?, inline: Bool, refs: References?, useCache: Bool) -> Result<BabelJob, Failure> {
 341        let refs = refs ?? References(resolved: [:], options: options)
 342        var params = params
 343        params.evaluateLisp(file: options.file)
 344        let needsFile = options.file == nil && params.lisp.contains { params.evaluated[$0] == nil }
 345        guard let language = block.language else { return .failure(.message("This block has no language")) }
 346        let eval = params.single[":eval"]?.lowercased()
 347        if eval == "never" || eval == "no" {
 348            return .failure(.message("Evaluation of this \(language) code block\(block.name.map { " (\($0)) " } ?? " ")is disabled."))
 349        }
 350        let compiled = expandedLanguages.contains(language)
 351        if params.single[":cmdline"] != nil, !["dot", "plantuml", "mermaid"].contains(language), !shells.contains(language), !compiled {
 352            return .failure(.message(":cmdline isn't supported for \(language) yet; nothing was run."))
 353        }
 354        for key in [":prologue", ":epilogue", ":post", ":stdin", ":shebang"] where params.single[key] != nil
 355            && !(shells.contains(language) && [":stdin", ":shebang"].contains(key)) && !(compiled && [":prologue", ":epilogue"].contains(key)) {
 356            return .failure(.message("\(key) isn't supported yet; nothing was run."))
 357        }
 358        // The compiled languages' `org-babel-execute:` ignore `:session`.
 359        let session = compiled ? nil : params.single[":session"].flatMap { $0 == "none" ? nil : ($0.isEmpty ? "*\(language)*" : $0) }
 360        if session != nil, !shells.contains(language), language != "python" {
 361            return .failure(.message(":session isn't supported for \(language) yet; nothing was run."))
 362        }
 363        var body = normalizedBody(block)
 364        if nowebEvaluates(params.single[":noweb"]) {
 365            do {
 366                body = try expandNoweb(block, params: params, model: model, text: ns)
 367            } catch let error as Failure {
 368                return .failure(error)
 369            } catch {
 370                return .failure(.message("\(error)"))
 371            }
 372        }
 373        let output = params.results.contains("output")
 374        let file = params.single[":file"].map { name in
 375            params.single[":output-dir"].map { $0.hasSuffix("/") ? $0 + name : $0 + "/" + name } ?? name
 376        }
 377        let toFile = params.results.contains("file")
 378        var hash: String?
 379        var tables = Tables()
 380        let finishBase = { (kind: BabelJob.Finish.Kind) in
 381            BabelJob.Finish(kind: kind, language: language, params: params.results, output: output, wrap: params.wrap, name: name, inline: inline,
 382                            file: toFile ? file : nil, fileDescription: params.single[":file-desc"], hash: hash,
 383                            colnames: tables.picked(tables.colnames, ":colnames", params), rownames: tables.picked(tables.rownames, ":rownames", params))
 384        }
 385        let vars: [(String, VarValue)]
 386        var stdin: VarValue?
 387        do {
 388            tables = try disassemble(params.vars.map { ($0.name, try resolve($0.value, model: model, text: ns, refs: refs)) }, params: params)
 389            vars = tables.vars
 390            stdin = try params.single[":stdin"].map { try resolve($0, model: model, text: ns, refs: refs) }
 391        } catch let needed as Needed {
 392            // Asked about once, with every block the references reach; asked every time when
 393            // any of them has `:eval query`.
 394            let reached = referencedBlocks(params, model: model, text: ns, seen: [])
 395            let texts = [ns.substring(with: NSRange(location))] + reached.map { ns.substring(with: NSRange($0.range)) }
 396            let query = eval == "query" || reached.contains { (try? Params(block: $0, model: model, text: ns))?.single[":eval"]?.lowercased() == "query" }
 397            var job = BabelJob(
 398                language: language, runner: .process(executable: "", arguments: []), input: "", directory: params.single[":dir"],
 399                block: location, blockText: texts.joined(separator: "\n"), alwaysAsk: query, finish: finishBase(.generic), needs: [needed.need]
 400            )
 401            job.approved = texts
 402            return .success(job)
 403        } catch let error as Failure {
 404            return .failure(error)
 405        } catch {
 406            return .failure(.message("\(error)"))
 407        }
 408        if useCache, !inline, !needsFile, params.single[":cache"] == "yes" {
 409            do {
 410                let state = try cacheState(language: language, params: params, tables: tables, body: body, location: location, name: name,
 411                                           call: location != block.range, text: ns, options: options, model: model)
 412                if state.current { return .failure(.message(cachedMessage(state.value))) }
 413                // Appended or prepended results accumulate, so they carry no hash.
 414                if params.results.contains("replace") { hash = state.hash }
 415            } catch let error as Failure {
 416                return .failure(error)
 417            } catch {
 418                return .failure(.message("\(error)"))
 419            }
 420        }
 421        let blockText = ns.substring(with: NSRange(location))
 422        let dir = params.single[":dir"]
 423        let alwaysAsk = eval == "query"
 424
 425        if shells.contains(language) {
 426            let valueIsExitStatus = params.results.contains("value")
 427            let executable = language == "shell" ? options.shell : language
 428            let lines = shellAssignments(vars, shell: executable, params: params)
 429            let cmdline = params.single[":cmdline"]
 430            let shebang = params.single[":shebang"].flatMap { $0.contains(where: { !$0.isWhitespace }) ? $0 : nil }
 431            if stdin != nil || cmdline != nil || (session == nil && shebang != nil) {
 432                // `org-babel-sh-evaluate`: an executable script, run by the shell with the
 433                // command line after it and `:stdin` as its input; the result is its standard
 434                // output, or its exit status for `:results value`. `:stdin` and `:cmdline`
 435                // run it even with a session, which the script doesn't use.
 436                var script = (shebang ?? "#!/usr/bin/env \(executable)") + "\n"
 437                if params.single[":padline"] != "no" { script += "\n" }
 438                script += (lines + [body]).joined(separator: "\n") + (valueIsExitStatus ? "\necho $?" : "")
 439                let command = Babel.scriptFileToken + (stdin != nil || cmdline != nil ? " " + (cmdline ?? "") : "")
 440                var job = BabelJob(
 441                    language: language, runner: .process(executable: executable, arguments: ["-c", command]),
 442                    input: stdin.map { shellString($0) } ?? "", directory: dir, block: location, blockText: blockText, alwaysAsk: alwaysAsk,
 443                    finish: finishBase(.shell(valueIsExitStatus: valueIsExitStatus))
 444                )
 445                job.script = script
 446                return .success(job)
 447            }
 448            let full = (lines + [body]).joined(separator: "\n") + (valueIsExitStatus ? "\necho $?" : "")
 449            return .success(BabelJob(
 450                language: language, runner: session.map { .session(name: $0, executable: executable, python: false) } ?? .process(executable: executable, arguments: []),
 451                input: full.trimmingCharacters(in: .whitespacesAndNewlines) + "\n", directory: dir,
 452                block: location, blockText: blockText, alwaysAsk: alwaysAsk,
 453                finish: finishBase(.shell(valueIsExitStatus: valueIsExitStatus))
 454            ))
 455        }
 456        if language == "python", let session {
 457            // `org-babel-python--eval-ast`: the body at top level in the session, the value its
 458            // last expression's.
 459            let lines = vars.map { "\($0.0)=\(pythonLiteral($0.1))" }
 460            let source = (lines + [body]).joined(separator: "\n")
 461            let literal = String(data: try! JSONSerialization.data(withJSONObject: [source]), encoding: .utf8)!.dropFirst().dropLast()
 462            let program: String
 463            if output {
 464                program = "exec(compile(\(literal), '<org-babel>', 'exec'), globals())\n"
 465            } else {
 466                program = pythonFormatter + """
 467                    import ast as __orgstar_ast
 468                    __orgstar_tree = __orgstar_ast.parse(\(literal))
 469                    __orgstar_last = None
 470                    if __orgstar_tree.body and isinstance(__orgstar_tree.body[-1], __orgstar_ast.Expr):
 471                        __orgstar_last = __orgstar_ast.Expression(__orgstar_tree.body.pop().value)
 472                    exec(compile(__orgstar_tree, '<org-babel>', 'exec'), globals())
 473                    __orgstar_format(eval(compile(__orgstar_last, '<org-babel>', 'eval'), globals()) if __orgstar_last else None, '\(resultFileToken)', [
 474                    """ + params.results.map { "\"\($0)\"" }.joined(separator: ", ") + "])\n"
 475            }
 476            return .success(BabelJob(
 477                language: language, runner: .session(name: session, executable: params.single[":python"] ?? options.python, python: true),
 478                input: program, directory: dir, block: location, blockText: blockText, alwaysAsk: alwaysAsk, finish: finishBase(.python)
 479            ))
 480        }
 481        if language == "python" {
 482            let lines = vars.map { "\($0.0)=\(pythonLiteral($0.1))" }
 483            var full = (lines + [body]).joined(separator: "\n")
 484            if let returnValue = params.single[":return"], !output { full += "\nreturn \(returnValue)" }
 485            let input: String
 486            if output {
 487                input = full + "\n"
 488            } else {
 489                input = pythonFormatter + "def main():\n" + shiftRight(full) + "\n\n__orgstar_format(main(), '\(resultFileToken)', ["
 490                    + params.results.map { "\"\($0)\"" }.joined(separator: ", ") + "])\n"
 491            }
 492            return .success(BabelJob(
 493                language: language, runner: .process(executable: params.single[":python"] ?? options.python, arguments: []),
 494                input: input, directory: dir, block: location, blockText: blockText, alwaysAsk: alwaysAsk,
 495                finish: finishBase(.python)
 496            ))
 497        }
 498        if language == "emacs-lisp" || language == "elisp" {
 499            let bindings = vars.map { "(\($0.0) '\(lispLiteral($0.1)))" }
 500            let expanded = bindings.isEmpty ? body + "\n" : "(let (\(bindings.joined(separator: "\n      ")))\n\(body)\n)"
 501            let form = output ? "(with-output-to-string \(expanded))" : "(progn \(expanded)\n)"
 502            var job = BabelJob(
 503                language: language, runner: .emacs, input: emacsScript(form: form, params: params.results),
 504                directory: dir, block: location, blockText: blockText, alwaysAsk: alwaysAsk, finish: finishBase(.emacsLisp)
 505            )
 506            job.lispForm = form
 507            return .success(job)
 508        }
 509        if ["dot", "plantuml", "mermaid"].contains(language) {
 510            // ob-dot, ob-plantuml and ob-mermaid: the program draws into `:file`.
 511            guard let file else { return .failure(.message("\(language) code blocks need a :file header argument")) }
 512            let quoted = "'" + file.replacingOccurrences(of: "'", with: "'\"'\"'") + "'"
 513            let ext = (file as NSString).pathExtension.isEmpty ? "png" : (file as NSString).pathExtension
 514            let cmdline = params.single[":cmdline"].map { " " + $0 } ?? ""
 515            var input = body + "\n"
 516            let command: String
 517            switch language {
 518            case "dot":
 519                command = "dot\(cmdline) -T\(ext) -o \(quoted)"
 520            case "plantuml":
 521                if body.range(of: "^[ \\t]*@start", options: .regularExpression) == nil { input = "@startuml\n" + body + "\n@enduml\n" }
 522                command = "plantuml\(cmdline) -p -t\(ext) > \(quoted)"
 523            default:
 524                command = "f=$(mktemp).mmd; cat > \"$f\"; mmdc\(cmdline) -i \"$f\" -o \(quoted); s=$?; rm -f \"$f\"; exit $s"
 525            }
 526            var finish = finishBase(.generic)
 527            finish.file = file
 528            finish.writesFile = true
 529            finish.params = params.results.filter { !["table", "scalar", "verbatim", "list", "vector"].contains($0) } + (params.results.contains("file") ? [] : ["file"])
 530            return .success(BabelJob(
 531                language: language, runner: .process(executable: "/bin/sh", arguments: ["-c", command]),
 532                input: input, directory: dir, block: location, blockText: blockText, alwaysAsk: alwaysAsk, finish: finish
 533            ))
 534        }
 535        if compiled {
 536            do {
 537                let heading = model.headings.lastIndex { $0.headingRange.lowerBound <= location.lowerBound }
 538                let run = try compiledRun(language: language, body: body, tables: tables, params: params,
 539                                          properties: { inheritedProperty($0, heading: heading, model: model) })
 540                var finish = finishBase(.compiled)
 541                // ob-clojure doesn't reassemble tables.
 542                if language == "clojure" { (finish.colnames, finish.rownames) = (nil, nil) }
 543                var job = BabelJob(language: language, runner: .process(executable: "/bin/sh", arguments: [scriptFileToken]), input: run.source,
 544                                   directory: dir, block: location, blockText: blockText, alwaysAsk: alwaysAsk, finish: finish)
 545                job.script = run.script
 546                job.programs = run.programs
 547                return .success(job)
 548            } catch let error as Failure {
 549                return .failure(error)
 550            } catch {
 551                return .failure(.message("\(error)"))
 552            }
 553        }
 554        if let (executable, arguments) = generic[language] {
 555            guard vars.isEmpty else { return .failure(.message(":var isn't supported for \(language) yet.")) }
 556            return .success(BabelJob(
 557                language: language, runner: .process(executable: params.single[":cmd"] ?? executable, arguments: arguments),
 558                input: body + "\n", directory: dir, block: location, blockText: blockText, alwaysAsk: alwaysAsk,
 559                finish: BabelJob.Finish(kind: .generic, language: language, params: params.results + (output ? [] : ["output"]), output: true, wrap: params.wrap, name: name, inline: inline, hash: hash)
 560            ))
 561        }
 562        return .failure(.message("No way to run \(language) blocks yet."))
 563    }
 564
 565    public enum Failure: Error, Equatable {
 566        case message(String)
 567    }
 568
 569    // MARK: - Noweb
 570
 571    /// `org-babel-noweb-p` in the `:eval` context.
 572    static func nowebEvaluates(_ value: String?) -> Bool {
 573        (value ?? "").split(whereSeparator: \.isWhitespace).contains { ["yes", "no-export", "strip-export", "eval", "strip-tangle"].contains(String($0)) }
 574    }
 575
 576    /// `org-babel-noweb-p` for `:tangle`.
 577    static func nowebTangles(_ value: String?) -> Bool {
 578        (value ?? "").split(whereSeparator: \.isWhitespace).contains { ["yes", "tangle", "no-export", "strip-export", "strip-tangle"].contains(String($0)) }
 579    }
 580
 581    /// `org-babel-expand-noweb-references` for evaluation: each `<<name>>` becomes the body of
 582    /// the block named so, or of every block with that `:noweb-ref`, joined by `:noweb-sep`, or
 583    /// the contents of the heading with that ID; the text before it on its line goes before
 584    /// each line it brings.
 585    /// `tangling` expands the blocks it brings in as tangling would (`:noweb tangle`), not as
 586    /// evaluation does.
 587    /// `links` gives a block's begin and end comments, for `:comments noweb` when tangling.
 588    static func expandNoweb(_ block: SrcBlockInfo, params: Params, model: DocumentModel, text: NSString, tangling: Bool = false, depth: Int = 0,
 589                            links: ((SrcBlockInfo, Params) throws -> (begin: String, end: String))? = nil) throws -> String {
 590        guard depth < 32 else { throw Failure.message("Noweb references nest too deeply") }
 591        let body = normalizedBody(block)
 592        let prefixing = params.single[":noweb-prefix"].map { $0 != "no" && $0 != "nil" } ?? true
 593        let commenting = tangling && links != nil && params.single[":comments"] == "noweb"
 594        func expand(_ other: SrcBlockInfo) throws -> String {
 595            let otherParams = try Params(block: other, model: model, text: text)
 596            let expands = tangling ? nowebTangles(otherParams.single[":noweb"]) : nowebEvaluates(otherParams.single[":noweb"])
 597            let b = expands
 598                ? try expandNoweb(other, params: otherParams, model: model, text: text, tangling: tangling, depth: depth + 1, links: links)
 599                : normalizedBody(other)
 600            guard commenting, let links else { return b }
 601            // `c-wrap`: commented in this block's language, trimmed.
 602            func wrap(_ s: String) throws -> String {
 603                try Tangle.commentRegion(s, language: block.language ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
 604            }
 605            var (begin, end) = try links(other.name == nil ? block : other, other.name == nil ? params : otherParams)
 606            // A `:noweb-ref` block without a name: the link is the including block's, the name empty.
 607            if other.name == nil {
 608                if let split = begin.range(of: "][") { begin = begin[..<split.lowerBound] + "][]]" }
 609                end = " ends here"
 610            }
 611            return try wrap(begin) + "\n" + b + "\n" + (try wrap(end)) + "\n"
 612        }
 613        func inCommentedHeading(_ b: SrcBlockInfo) -> Bool {
 614            guard let h = b.heading else { return false }
 615            return ([h] + model.ancestors(of: h)).contains { model.headings[$0].title == "COMMENT" || model.headings[$0].title.hasPrefix("COMMENT ") }
 616        }
 617        let regex = try! NSRegularExpression(pattern: "(.*?)(<<([^ \\t\\n](?:.*?[^ \\t\\n])?)>>)")
 618        let ns = body as NSString
 619        var out = ""
 620        var at = 0
 621        for m in regex.matches(in: body, range: NSRange(location: 0, length: ns.length)) {
 622            let prefix = ns.substring(with: m.range(at: 1))
 623            let id = ns.substring(with: m.range(at: 3))
 624            out += ns.substring(with: NSRange(location: at, length: m.range(at: 2).location - at))
 625            at = NSMaxRange(m.range)
 626            var expansion = ""
 627            if id.range(of: "\\(.*\\)", options: .regularExpression) != nil {
 628                throw Failure.message("Noweb references that run a block (\(id)) aren't supported yet; nothing was run.")
 629            } else if let heading = model.headings.firstIndex(where: { h in
 630                h.properties.contains { ($0.key.uppercased() == "ID" && $0.value == id) || ($0.key.uppercased() == "CUSTOM_ID" && "#" + $0.value == id) }
 631            }) {
 632                expansion = headlineBody(heading, model: model, text: text)
 633            } else if let named = model.srcBlocks.first(where: { $0.name == id }), !inCommentedHeading(named) {
 634                expansion = try expand(named)
 635            } else {
 636                var parts: [String] = []
 637                var separators: [String] = []
 638                for other in model.srcBlocks where !inCommentedHeading(other) {
 639                    let p = try Params(block: other, model: model, text: text)
 640                    guard p.single[":noweb-ref"] == id else { continue }
 641                    parts.append(try expand(other))
 642                    separators.append(p.single[":noweb-sep"] ?? "\n")
 643                }
 644                for (i, part) in parts.enumerated() {
 645                    if i > 0 { expansion += separators[i - 1] }
 646                    expansion += part
 647                }
 648            }
 649            out += prefixing ? expansion.components(separatedBy: CharacterSet(charactersIn: "\n\r")).joined(separator: "\n" + prefix) : expansion
 650        }
 651        return out + ns.substring(from: at)
 652    }
 653
 654    /// `org-babel-ref-headline-body`: the entry after its planning and drawers.
 655    static func headlineBody(_ index: Int, model: DocumentModel, text: NSString) -> String {
 656        let heading = model.headings[index]
 657        let buffer = EmacsBuffer(text as String, point: heading.headingRange.lowerBound)
 658        buffer.endOfMetaData()
 659        let start = buffer.point
 660        let end = model.headings[(index + 1)...].first.map { $0.headingRange.lowerBound } ?? text.length
 661        return start < end ? text.substring(with: NSRange(location: start, length: end - start)).trimmingCharacters(in: .whitespacesAndNewlines) : ""
 662    }
 663
 664    /// `org-babel--normalize-body`: without the final newline, and without the indentation
 665    /// common to its lines unless `-i` keeps it.
 666    static func normalizedBody(_ block: SrcBlockInfo) -> String {
 667        var lines = block.body.components(separatedBy: "\n")
 668        if lines.last == "" { lines.removeLast() }
 669        // Org escapes lines starting with `*` or `#+` inside blocks with a comma.
 670        lines = lines.map { $0.replacingOccurrences(of: "^([ \\t]*),([,*]|#\\+)", with: "$1$2", options: .regularExpression) }
 671        if !block.switches.contains("-i") {
 672            let indents = lines.filter { $0.contains(where: { !$0.isWhitespace }) }.map { $0.prefix { $0 == " " || $0 == "\t" }.count }
 673            let common = indents.min() ?? 0
 674            lines = lines.map { String($0.dropFirst(min(common, $0.prefix { $0 == " " || $0 == "\t" }.count))) }
 675        }
 676        return lines.joined(separator: "\n")
 677    }
 678
 679    // MARK: - Tables in variables
 680
 681    /// Variables after `org-babel-disassemble-tables`, with the names taken off their tables.
 682    struct Tables {
 683        var vars: [(String, VarValue)] = []
 684        var colnames: [(name: String, names: [Cell])] = []
 685        var rownames: [(name: String, names: [Cell])] = []
 686
 687        /// `org-babel-pick-name`: a Lisp list given as the header argument, or the last
 688        /// variable's names, unless the header argument is absent, whose `nil` selects none.
 689        func picked(_ names: [(name: String, names: [Cell])], _ key: String, _ params: Params) -> [String]? {
 690            if let list = params.evaluated[key].flatMap(Babel.lispNames) { return list }
 691            guard params.single[key] != nil, let last = names.last else { return nil }
 692            return last.names.map(Babel.cellText)
 693        }
 694    }
 695
 696    /// A Lisp list of strings and numbers, as names.
 697    static func lispNames(_ value: Sexp) -> [String]? {
 698        guard case .list(let items) = value else { return nil }
 699        return try? items.map { item in
 700            if let s = item.string { return s }
 701            let printed = Elisp.printed(item)
 702            guard number(printed) == printed else { throw Failure.message(printed) }
 703            return printed
 704        }
 705    }
 706
 707    static func cellText(_ c: Cell) -> String {
 708        switch c {
 709        case .number(let n): n
 710        case .text(let s): s
 711        }
 712    }
 713
 714    /// `org-babel-disassemble-tables`: a table's header row comes off when `:colnames` asks, or
 715    /// by default when only its second row is a rule; its first column with `:rownames`; its
 716    /// rules unless `:hlines yes`.
 717    static func disassemble(_ vars: [(String, VarValue)], params: Params) throws -> Tables {
 718        for key in [":colnames", ":rownames"] where params.lisp.contains(key) && params.evaluated[key].flatMap(lispNames) == nil {
 719            throw Failure.message("\(key) \(params.single[key] ?? "") is Lisp that only Emacs can evaluate; nothing was run.")
 720        }
 721        let colnames = params.single[":colnames"]
 722        let rownames = params.single[":rownames"]
 723        let hlines = params.single[":hlines"] ?? "no"
 724        var tables = Tables()
 725        for (name, value) in vars {
 726            guard case .table(var rows) = value else {
 727                tables.vars.append((name, value))
 728                continue
 729            }
 730            let auto = rows.count > 1 && rows[0] != nil && rows[1] == nil && !rows.dropFirst(2).contains(where: { $0 == nil })
 731            if colnames != "no", colnames != nil || auto {
 732                while let first = rows.first, first == nil { rows.removeFirst() }
 733                if let header = rows.first ?? nil {
 734                    rows.removeFirst(rows.count > 1 && rows[1] == nil ? 2 : 1)
 735                    tables.colnames.append((name, header))
 736                }
 737            }
 738            if let rownames, rownames != "no" {
 739                let kept = rows.compactMap { $0 }
 740                tables.rownames.append((name, kept.map { $0.first ?? .text("") }))
 741                rows = kept.map { Array($0.dropFirst()) }
 742            }
 743            if hlines != "yes" { rows = rows.filter { $0 != nil } }
 744            tables.vars.append((name, .table(rows)))
 745        }
 746        return tables
 747    }
 748
 749    /// `org-babel-reassemble-table` on a result.
 750    static func reassemble(_ value: Value, _ finish: BabelJob.Finish) -> Value {
 751        guard case .table(var rows) = value else { return value }
 752        if let names = finish.rownames, names.count == rows.count {
 753            var next = names.makeIterator()
 754            rows = rows.map { row in row.map { [next.next() ?? ""] + $0 } }
 755        }
 756        if let names = finish.colnames, let first = rows.first ?? nil, first.count == names.count {
 757            rows = [names, nil] + rows
 758        }
 759        return .table(rows)
 760    }
 761
 762    /// A `:var` value that is Lisp (`org-babel-read`): evaluated, as a number, string, list or
 763    /// table.
 764    static func lispValue(_ value: String) throws -> VarValue {
 765        let refusal = Failure.message(":var value \(value) is Lisp that only Emacs can evaluate.")
 766        guard let form = try? LispReader.readFirst(value).sexp, let result = try? Elisp().eval(form) else { throw refusal }
 767        func cell(_ v: Sexp) throws -> Cell {
 768            if let s = v.string { return .text(s) }
 769            if let n = number(Elisp.printed(v)), Elisp.printed(v) == n { return .number(n) }
 770            throw refusal
 771        }
 772        if let s = result.string { return .scalar(s) }
 773        if case .list(let items) = result, !items.isEmpty {
 774            if items.allSatisfy({ if case .list = $0 { return true }; return $0 == .symbol("hline") }) {
 775                return .table(try items.map { item in
 776                    if item == .symbol("hline") { return nil }
 777                    guard case .list(let cells) = item else { throw refusal }
 778                    return try cells.map(cell)
 779                })
 780            }
 781            return .list(try items.map(cell))
 782        }
 783        if case .number(let n) = try? cell(result) { return .number(n) }
 784        throw refusal
 785    }
 786
 787    // MARK: - Cache
 788
 789    /// `:cache yes` (`org-babel-execute-src-block`): the block's hash, and whether its results
 790    /// already carry it, with their value when it can be read.
 791    static func cacheState(language: String, params: Params, tables: Tables, body: String, location: Range<Int>, name: String?,
 792                           call: Bool, text: NSString, options: Options, model: DocumentModel) throws -> (hash: String, current: Bool, value: VarValue?) {
 793        let heading = model.headings.lastIndex { $0.headingRange.lowerBound <= location.lowerBound }
 794        // The hash is taken outside the shell's own binding of `shell-file-name`, so its
 795        // variables are written for the user's shell whatever the block's language.
 796        let expanded = try languageExpansion(body, language: language, tables: tables, params: params,
 797                                             properties: { inheritedProperty($0, heading: heading, model: model) })
 798            ?? expandedBody(language: language, vars: tables.vars, body: body, shell: options.shell, params: params)
 799        let input = try cacheInput(language: language, params: params, tables: tables, expanded: expanded, call: call)
 800        let hash = Insecure.SHA1.hash(data: Data(input.utf8)).map { String(format: "%02x", $0) }.joined()
 801        guard let line = existingResults(name: name, block: location, text: text), resultsHash(at: line, text: text) == hash else {
 802            return (hash, false, nil)
 803        }
 804        return (hash, true, readResult(after: line, text: text))
 805    }
 806
 807    static func cachedMessage(_ value: VarValue?) -> String {
 808        value.map { "Cached: " + lispLiteral($0) } ?? "Cached; nothing was run."
 809    }
 810
 811    /// The body with its variables, as the language's `org-babel-expand-body:` writes it.
 812    static func expandedBody(language: String, vars: [(String, VarValue)], body: String, shell: String, params: Params) -> String {
 813        if shells.contains(language) {
 814            return (shellAssignments(vars, shell: shell, params: params) + [body]).joined(separator: "\n")
 815        }
 816        if language == "python" {
 817            return (vars.map { "\($0.0)=\(pythonLiteral($0.1))" } + [body]).joined(separator: "\n")
 818        }
 819        if language == "emacs-lisp" || language == "elisp" {
 820            let bindings = vars.map { "(\($0.0) '\(lispLiteral($0.1)))" }
 821            return bindings.isEmpty ? body + "\n" : "(let (\(bindings.joined(separator: "\n      ")))\n\(body)\n)"
 822        }
 823        return body
 824    }
 825
 826    /// What `org-babel-sha1-hash` hashes: the header arguments after `org-babel-process-params`,
 827    /// sorted by key and printed with `%S`, then the expanded body.
 828    static func cacheInput(language: String, params: Params, tables: Tables, expanded: String, call: Bool) throws -> String {
 829        if let key = params.lisp.first(where: { params.evaluated[$0] == nil }) {
 830            throw Failure.message(":cache with \(key) \(params.single[key] ?? "") needs Emacs to evaluate the Lisp; nothing was run.")
 831        }
 832        let ignored: Set<String> = ["replace", "silent", "none", "discard", "append", "prepend"]
 833        let drawsFile = language == "dot" || language == "plantuml"
 834        // `org-babel-default-header-args`, the language's and `org-babel-default-lob-header-args`.
 835        var defaults = [":session": "none", ":exports": call || drawsFile ? "results" : "code", ":cache": "no", ":noweb": "no", ":hlines": "no", ":tangle": "no"]
 836        if language == "emacs-lisp" || language == "elisp" { defaults[":lexical"] = "no" }
 837        var entries: [(key: String, printed: String)] = []
 838        for (key, value) in defaults.merging(params.single, uniquingKeysWith: { $1 }) {
 839            let printed: String
 840            if let evaluated = params.evaluated[key] {
 841                printed = Elisp.printed(evaluated, escape: true)
 842            } else if value.isEmpty {
 843                continue
 844            } else if key == ":exports" {
 845                printed = lispString(value.split(whereSeparator: \.isWhitespace).map(String.init).filter { !ignored.contains($0) }.sorted().joined(separator: " "))
 846            } else if params.single[key] != nil, !params.quoted.contains(key), let n = number(value) {
 847                printed = n
 848            } else {
 849                printed = lispString(value)
 850            }
 851            entries.append((key, printed))
 852        }
 853        var words = params.results
 854        if drawsFile, !words.contains(where: { Params.resultGroups[0].contains($0) }) { words.append("file") }
 855        let kept = words.filter { !ignored.contains($0) }.sorted()
 856        if !kept.isEmpty { entries.append((":result-params", "(" + kept.map { lispString($0) }.joined(separator: " ") + ")")) }
 857        entries.append((":result-type", words.contains("output") ? "output" : "value"))
 858        entries.append((":results", lispString(kept.joined(separator: " "))))
 859        if let wrap = params.wrap, !wrap.isEmpty { entries.append((":wrap", lispString(wrap))) }
 860        for (key, names) in [(":colname-names", tables.colnames), (":rowname-names", tables.rownames)] where !names.isEmpty {
 861            entries.append((key, "(" + names.map { "(\($0.name) " + lispLiteral(.list($0.names)).dropFirst() }.joined(separator: " ") + ")"))
 862        }
 863        for (name, value) in tables.vars {
 864            switch value {
 865            case .scalar, .number: entries.append((":var", "(\(name) . \(lispLiteral(value)))"))
 866            case .list, .table: entries.append((":var", "(\(name) " + lispLiteral(value).dropFirst()))
 867            }
 868        }
 869        let sorted = entries.enumerated().sorted { ($0.element.key, $0.offset) < ($1.element.key, $1.offset) }.map(\.element.printed)
 870        return sorted.joined(separator: ":") + "-" + expanded
 871    }
 872
 873    /// `org-babel-read-result` for fixed-width lines, a table or a plain list after the
 874    /// `#+RESULTS` line at `line`; nil for anything else.
 875    static func readResult(after line: Int, text ns: NSString) -> VarValue? {
 876        var location = NSMaxRange(ns.lineRange(for: NSRange(location: line, length: 0)))
 877        var lines: [String] = []
 878        while location < ns.length {
 879            let range = ns.lineRange(for: NSRange(location: location, length: 0))
 880            let content = ns.substring(with: range).trimmingCharacters(in: .newlines)
 881            if content.trimmingCharacters(in: .whitespaces).isEmpty { break }
 882            lines.append(content.trimmingCharacters(in: .whitespaces))
 883            location = NSMaxRange(range)
 884        }
 885        guard let first = lines.first else { return nil }
 886        if first == ":" || first.hasPrefix(": ") {
 887            var fixed: [String] = []
 888            for l in lines {
 889                guard l == ":" || l.hasPrefix(": ") else { break }
 890                fixed.append(String(l.dropFirst(2)))
 891            }
 892            let value = fixed.joined(separator: "\n").trimmingCharacters(in: .whitespacesAndNewlines)
 893            return number(value).map(VarValue.number) ?? .scalar(value)
 894        }
 895        if first.hasPrefix("|") {
 896            var rows: [[Cell]?] = []
 897            for l in lines {
 898                guard l.hasPrefix("|") else { break }
 899                if l.hasPrefix("|-") {
 900                    rows.append(nil)
 901                    continue
 902                }
 903                let cells = l.dropFirst().split(separator: "|", omittingEmptySubsequences: false).map { $0.trimmingCharacters(in: .whitespaces) }
 904                rows.append((l.hasSuffix("|") ? Array(cells.dropLast()) : cells).map(cell))
 905            }
 906            return .table(rows)
 907        }
 908        if first.firstMatch(of: #/^[-+*] /#) != nil {
 909            var items: [Cell] = []
 910            for l in lines {
 911                guard let m = l.firstMatch(of: /^[-+*] (.*)$/) else { return nil }
 912                items.append(cell(String(m.1)))
 913            }
 914            return .list(items)
 915        }
 916        return nil
 917    }
 918
 919    // MARK: - Header arguments
 920
 921    struct Params {
 922        var results: [String] = ["replace"]
 923        var single: [String: String] = [:]
 924        var vars: [(name: String, value: String)] = []
 925        var wrap: String?
 926        /// Keys whose value is Lisp to evaluate (`org-babel-read`): unquoted, starting with `(`, `'`, `` ` `` or `[`.
 927        var lisp: Set<String> = []
 928        /// Keys whose value was a quoted string, which `org-babel-read` doesn't take for a number.
 929        var quoted: Set<String> = []
 930        /// Lisp values evaluated to something other than a string or number, such as a list of
 931        /// column names.
 932        var evaluated: [String: Sexp] = [:]
 933
 934        /// `org-babel-read` on the Lisp values the interpreter can evaluate: a string or number
 935        /// takes the form's place; other values are kept in `evaluated`.
 936        mutating func evaluateLisp(file: String?) {
 937            for key in lisp {
 938                guard let form = try? LispReader.readFirst(single[key] ?? "").sexp,
 939                      let value = try? (file.map { Tangle.fileLisp(path: $0) } ?? Elisp()).eval(form) else { continue }
 940                let printed = Elisp.printed(value)
 941                if let s = value.string {
 942                    single[key] = s
 943                    quoted.insert(key)
 944                    lisp.remove(key)
 945                } else if Babel.number(printed) == printed {
 946                    single[key] = printed
 947                    lisp.remove(key)
 948                } else {
 949                    evaluated[key] = value
 950                }
 951            }
 952        }
 953
 954        static let resultGroups: [Set<String>] = [
 955            ["file", "list", "vector", "table", "scalar", "verbatim"],
 956            ["raw", "html", "latex", "org", "code", "pp", "drawer", "link", "graphics"],
 957            ["replace", "silent", "none", "discard", "append", "prepend"],
 958            ["output", "value"],
 959        ]
 960
 961        /// `defaults` go under the properties, as `org-babel-default-inline-header-args` do.
 962        init(block: SrcBlockInfo, model: DocumentModel, text: NSString, defaults: [(String, String)] = [], extra: [[(String, String)]] = [],
 963             inline: Bool = false) throws {
 964            var layers: [[(String, String)]] = [defaults, Babel.languageDefaults[block.language ?? ""] ?? []]
 965            layers += Params.propertyLayers(language: block.language ?? "", heading: block.heading, model: model)
 966            let firstLine = inline ? "" : text.substring(with: text.lineRange(for: NSRange(location: block.range.lowerBound, length: 0)))
 967            if let r = firstLine.range(of: "(?i)#\\+begin_src[ \\t]+\\S+", options: .regularExpression) {
 968                var rest = String(firstLine[r.upperBound...]).trimmingCharacters(in: .whitespacesAndNewlines)
 969                while let first = rest.split(separator: " ").first, first.hasPrefix("-") || first.hasPrefix("+") {
 970                    rest = String(rest.dropFirst(first.count)).trimmingCharacters(in: .whitespaces)
 971                }
 972                layers.append(Params.parse(rest))
 973            }
 974            // `#+HEADER:` lines above the block.
 975            var headers: [String] = []
 976            var location = block.range.lowerBound
 977            while !inline, location > 0 {
 978                let previous = text.lineRange(for: NSRange(location: location - 1, length: 0))
 979                let line = text.substring(with: previous).trimmingCharacters(in: .whitespacesAndNewlines)
 980                guard line.range(of: "(?i)^#\\+(header|name|caption|attr_[a-z]+|results|plot|label):", options: .regularExpression) != nil else { break }
 981                if line.lowercased().hasPrefix("#+header:") { headers.insert(String(line.dropFirst("#+header:".count)), at: 0) }
 982                location = previous.location
 983            }
 984            for header in headers { layers.append(Params.parse(header)) }
 985            layers += extra
 986
 987            for layer in layers {
 988                for (key, raw) in layer {
 989                    let quoted = key != ":var" && raw.count >= 2 && raw.hasPrefix("\"") && raw.hasSuffix("\"")
 990                    let value = quoted ? Params.unescape(String(raw.dropFirst().dropLast())) : raw
 991                    if key != ":var" {
 992                        if !quoted, let first = value.first, "('`[".contains(first) { lisp.insert(key) } else { lisp.remove(key) }
 993                        if quoted { self.quoted.insert(key) } else { self.quoted.remove(key) }
 994                    }
 995                    switch key {
 996                    case ":results":
 997                        for word in value.split(whereSeparator: \.isWhitespace).map(String.init) {
 998                            if let group = Params.resultGroups.first(where: { $0.contains(word) }) { results.removeAll { group.contains($0) } }
 999                            results.removeAll { $0 == word }
1000                            results.insert(word, at: 0)
1001                        }
1002                    case ":var":
1003                        for value in Params.splitVars(value) {
1004                            guard let eq = value.firstIndex(of: "=") else { throw Failure.message(":var \(value) needs a name and a value") }
1005                            let name = value[..<eq].trimmingCharacters(in: .whitespaces)
1006                            let assigned = value[value.index(after: eq)...].trimmingCharacters(in: .whitespaces)
1007                            vars.removeAll { $0.name == name }
1008                            vars.append((name, assigned))
1009                        }
1010                    case ":wrap":
1011                        wrap = value
1012                    default:
1013                        single[key] = value
1014                    }
1015                }
1016            }
1017        }
1018
1019        /// `org-babel-params-from-properties` under `heading`, or before any: the inherited
1020        /// `header-args`, then the inherited `header-args:LANG`, each the nearest value with `+`
1021        /// additions after it.
1022        static func propertyLayers(language: String, heading: Int?, model: DocumentModel) -> [[(String, String)]] {
1023            var levels = [model.fileProperties]
1024            if let heading {
1025                levels += (model.ancestors(of: heading) + [heading]).map { model.headings[$0].properties }
1026            }
1027            return ["header-args", "header-args:\(language)"].compactMap { key in
1028                var value: String?
1029                for entries in levels {
1030                    for entry in entries where entry.key.lowercased() == key {
1031                        value = entry.additive ? [value, entry.value].compactMap { $0 }.joined(separator: " ") : entry.value
1032                    }
1033                }
1034                return value.map(Params.parse)
1035            }
1036        }
1037
1038        /// `org-babel-parse-header-arguments`: split before each `:key` outside quotes and
1039        /// parentheses.
1040        /// A Lisp string's contents as `read` gives them.
1041        static func unescape(_ s: String) -> String {
1042            var out = ""
1043            var escaped = false
1044            for c in s {
1045                if escaped {
1046                    switch c {
1047                    case "n": out.append("\n")
1048                    case "t": out.append("\t")
1049                    default: out.append(c)
1050                    }
1051                    escaped = false
1052                } else if c == "\\" {
1053                    escaped = true
1054                } else {
1055                    out.append(c)
1056                }
1057            }
1058            return out
1059        }
1060
1061        /// `org-babel-parse-multiple-vars`: one `:var` value holding several assignments split
1062        /// on spaces outside quotes and brackets, pieces split beside `=` joined again.
1063        static func splitVars(_ value: String) -> [String] {
1064            var pieces: [String] = []
1065            var current = ""
1066            var depth = 0
1067            var quoted = false
1068            var previous: Character = " "
1069            for c in value {
1070                if c == "\"", previous != "\\" { quoted.toggle() }
1071                previous = c
1072                if !quoted, "([{".contains(c) { depth += 1 }
1073                if !quoted, ")]}".contains(c) { depth = max(0, depth - 1) }
1074                if c == " ", !quoted, depth == 0 {
1075                    if !current.isEmpty { pieces.append(current) }
1076                    current = ""
1077                } else {
1078                    current.append(c)
1079                }
1080            }
1081            if !current.isEmpty { pieces.append(current) }
1082            var joined: [String] = []
1083            for piece in pieces {
1084                if let last = joined.last, last.hasSuffix("=") || piece.hasPrefix("=") {
1085                    joined[joined.count - 1] = last + piece
1086                } else {
1087                    joined.append(piece)
1088                }
1089            }
1090            return joined.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
1091        }
1092
1093        static func parse(_ text: String) -> [(String, String)] {
1094            var parts: [String] = []
1095            var current = ""
1096            var depth = 0
1097            var quoted = false
1098            var previous: Character = " "
1099            for c in text {
1100                if c == "\"", previous != "\\" { quoted.toggle() }
1101                if !quoted {
1102                    if c == "(" { depth += 1 }
1103                    if c == ")" { depth = max(0, depth - 1) }
1104                }
1105                if c == ":", !quoted, depth == 0, previous == " " || previous == "\t" {
1106                    parts.append(current)
1107                    current = ""
1108                }
1109                current.append(c)
1110                previous = c
1111            }
1112            parts.append(current)
1113            var pairs: [(String, String)] = []
1114            for part in parts {
1115                let trimmed = part.trimmingCharacters(in: .whitespaces)
1116                guard trimmed.hasPrefix(":") else { continue }
1117                let key = String(trimmed.prefix { !$0.isWhitespace }).lowercased()
1118                // Quotes stay until the value is used, so a quoted string isn't taken for Lisp.
1119                pairs.append((key, trimmed.dropFirst(key.count).trimmingCharacters(in: .whitespaces)))
1120            }
1121            return pairs
1122        }
1123    }
1124
1125    // MARK: - Variables
1126
1127    /// A referenced block's result as a `:var` value.
1128    static func varValue(_ finish: BabelJob.Finish, _ output: BabelOutput) -> VarValue {
1129        switch value(finish, output) {
1130        case .none: return .scalar("")
1131        case .string(let s): return number(s).map(VarValue.number) ?? .scalar(s)
1132        case .table(let rows): return .table(rows.map { $0?.map(cell) })
1133        }
1134    }
1135
1136    enum VarValue: Equatable, Sendable {
1137        case scalar(String)
1138        case number(String)
1139        /// Rows; nil is a rule (`hline`).
1140        case table([[Cell]?])
1141        case list([Cell])
1142    }
1143
1144    enum Cell: Equatable, Sendable {
1145        case text(String)
1146        case number(String)
1147    }
1148
1149    /// `org-babel--string-to-number`, keeping Emacs's printed form.
1150    static func number(_ s: String) -> String? {
1151        let t = s.trimmingCharacters(in: .whitespaces)
1152        guard !t.isEmpty, !t.contains(where: \.isWhitespace), t.range(of: "^[0-9e.+ -]+$", options: .regularExpression) != nil else { return nil }
1153        if t.range(of: "^[-+]?[0-9]+\\.?$", options: .regularExpression) != nil {
1154            var digits = t.hasSuffix(".") ? String(t.dropLast()) : t
1155            let negative = digits.hasPrefix("-")
1156            if digits.hasPrefix("-") || digits.hasPrefix("+") { digits.removeFirst() }
1157            digits = String(digits.drop { $0 == "0" })
1158            if digits.isEmpty { return "0" }
1159            return (negative ? "-" : "") + digits
1160        }
1161        guard t.range(of: "^[-+]?([0-9]+\\.[0-9]+|[0-9]*\\.[0-9]+|[0-9]+(\\.[0-9]*)?e[-+]?[0-9]+|[0-9]*\\.[0-9]+e[-+]?[0-9]+)$", options: .regularExpression) != nil,
1162              let d = Double(t) else { return nil }
1163        return emacsFloat(d)
1164    }
1165
1166    /// How Emacs prints a float.
1167    static func emacsFloat(_ d: Double) -> String {
1168        if d == d.rounded(), abs(d) < 1e16 { return String(format: "%.1f", d) }
1169        return "\(d)"
1170    }
1171
1172    static func cell(_ s: String) -> Cell {
1173        var t = s
1174        if let m = s.firstMatch(of: /^\s*"(.+)"\s*$/) { t = String(m.1) }
1175        return number(t).map(Cell.number) ?? .text(t)
1176    }
1177
1178    /// `name` or `name(args)` naming a src block in the file: the block and its call arguments.
1179    static func referencedBlock(_ value: String, model: DocumentModel) -> (block: SrcBlockInfo, arguments: String?)? {
1180        guard let m = value.firstMatch(of: /^([^\s()\[\]"]+)(?:\((.*)\))?$/),
1181              let block = model.srcBlocks.first(where: { $0.name == String(m.1) }) else { return nil }
1182        return (block, m.2.map(String.init))
1183    }
1184
1185    /// The blocks `params`' references run, directly or through theirs.
1186    static func referencedBlocks(_ params: Params, model: DocumentModel, text: NSString, seen: Set<String>) -> [SrcBlockInfo] {
1187        var found: [SrcBlockInfo] = []
1188        var seen = seen
1189        for value in params.vars.map(\.value) + [params.single[":stdin"]].compactMap({ $0 }) {
1190            guard let (block, arguments) = referencedBlock(value, model: model), let name = block.name, seen.insert(name).inserted else { continue }
1191            found.append(block)
1192            let extra = arguments.map { [splitArguments($0).map { (":var", $0) }] } ?? []
1193            if let inner = try? Params(block: block, model: model, text: text, extra: extra) {
1194                found += referencedBlocks(inner, model: model, text: text, seen: seen)
1195            }
1196        }
1197        return found
1198    }
1199
1200    /// `org-babel-ref-resolve` for literals, named tables and lists in the file, and named src
1201    /// blocks, which run first (`Needed`) and give their result.
1202    static func resolve(_ value: String, model: DocumentModel, text: NSString, refs: References) throws -> VarValue {
1203        if value.hasPrefix("\""), value.hasSuffix("\""), value.count >= 2 { return .scalar(Params.unescape(String(value.dropFirst().dropLast()))) }
1204        if let n = number(value) { return .number(n) }
1205        if let first = value.first, "('`[".contains(first) { return try lispValue(value) }
1206        if let known = refs.resolved[value] { return known }
1207        if let (block, arguments) = referencedBlock(value, model: model), let name = block.name {
1208            guard !refs.visiting.contains(name) else { throw Failure.message("\(name) refers to itself through :var") }
1209            let extra = arguments.map { [splitArguments($0).map { (":var", $0) }] } ?? []
1210            var inner = refs
1211            inner.visiting.insert(name)
1212            var params = try Params(block: block, model: model, text: text, extra: extra)
1213            params.evaluateLisp(file: refs.options.file)
1214            // A cached block gives its result without running, as `org-babel-execute-src-block` does.
1215            var useCache = true
1216            if params.single[":cache"] == "yes", let language = block.language {
1217                let tables = try disassemble(params.vars.map { ($0.name, try resolve($0.value, model: model, text: text, refs: inner)) }, params: params)
1218                let body = nowebEvaluates(params.single[":noweb"]) ? try expandNoweb(block, params: params, model: model, text: text) : normalizedBody(block)
1219                let state = try cacheState(language: language, params: params, tables: tables, body: body, location: block.range, name: name, call: false, text: text, options: refs.options, model: model)
1220                if state.current {
1221                    if let value = state.value { return value }
1222                    useCache = false
1223                }
1224            }
1225            switch plan(block, params: params, model: model, text: text,
1226                        options: refs.options, location: block.range, name: name, refs: inner, useCache: useCache) {
1227            case .failure(let failure): throw failure
1228            case .success(let job):
1229                throw Needed(need: job.needs.first ?? BabelJob.Need(reference: value, job: job))
1230            }
1231        }
1232        if value.contains("(") || value.contains("[") { throw Failure.message("References like \(value) aren't supported yet.") }
1233        // A named element: `#+NAME: value` above a table or list.
1234        let pattern = "(?im)^[ \\t]*#\\+name:[ \\t]*" + NSRegularExpression.escapedPattern(for: value) + "[ \\t]*$"
1235        guard let r = (text as String).range(of: pattern, options: .regularExpression) else {
1236            throw Failure.message("Can't find \(value) for :var.")
1237        }
1238        let start = NSMaxRange(text.lineRange(for: NSRange(r, in: text as String)))
1239        var location = start
1240        var rows: [[Cell]?] = []
1241        var items: [Cell] = []
1242        while location < text.length {
1243            let lineRange = text.lineRange(for: NSRange(location: location, length: 0))
1244            let line = text.substring(with: lineRange).trimmingCharacters(in: .newlines)
1245            let trimmed = line.trimmingCharacters(in: .whitespaces)
1246            if trimmed.hasPrefix("|") {
1247                if trimmed.hasPrefix("|-") {
1248                    rows.append(nil)
1249                    location = NSMaxRange(lineRange)
1250                    continue
1251                }
1252                let cells = trimmed.dropFirst().split(separator: "|", omittingEmptySubsequences: false).map { $0.trimmingCharacters(in: .whitespaces) }
1253                rows.append((trimmed.hasSuffix("|") ? Array(cells.dropLast()) : cells).map(cell))
1254            } else if let m = trimmed.firstMatch(of: /^[-+*] (.*)$/), rows.isEmpty {
1255                items.append(cell(String(m.1)))
1256            } else if trimmed.hasPrefix("#+") || (!rows.isEmpty || !items.isEmpty) {
1257                if rows.isEmpty, items.isEmpty, trimmed.lowercased().hasPrefix("#+begin_src") {
1258                    throw Failure.message("Using a src block's result in :var isn't supported yet.")
1259                }
1260                break
1261            } else {
1262                break
1263            }
1264            location = NSMaxRange(lineRange)
1265        }
1266        if !rows.isEmpty { return .table(rows) }
1267        if !items.isEmpty { return .list(items) }
1268        throw Failure.message("\(value) isn't a table or list.")
1269    }
1270
1271    /// `org-babel-sh-var-to-string`: a table's rows as tab-separated lines, a list's items as
1272    /// lines.
1273    static func shellString(_ value: VarValue, separator: String? = nil, hline: String? = nil) -> String {
1274        switch value {
1275        case .scalar(let s), .number(let s): return s
1276        case .list(let items): return items.map(cellText).joined(separator: "\n")
1277        case .table(let rows): return rows.compactMap { $0?.map(cellText).joined(separator: separator ?? "\t") ?? hline }.joined(separator: "\n")
1278        }
1279    }
1280
1281    /// `org-babel-variable-assignments:shell`, by the shell's name: for bash, an array for a
1282    /// list and an associative array for a table of two or more columns; `set` for fish; a
1283    /// quoted string otherwise.
1284    static func shellAssignments(_ vars: [(String, VarValue)], shell: String, params: Params) -> [String] {
1285        let separator = params.single[":separator"]
1286        let hline = params.single[":hlines"] == "yes" ? params.single[":hline-string"] ?? "hline" : nil
1287        func quoted(_ value: VarValue) -> String {
1288            "'" + shellString(value, separator: separator, hline: hline).replacingOccurrences(of: "'", with: "'\"'\"'") + "'"
1289        }
1290        return vars.map { name, value in
1291            if shell.hasSuffix("bash") {
1292                switch value {
1293                case .table(let rows) where (rows.first ?? nil).map({ $0.count >= 2 }) ?? false:
1294                    let entries = rows.compactMap { $0 }.map { row in
1295                        "\(name)[\(quoted(.scalar(cellText(row[0]))))]=\(quoted(.list(Array(row.dropFirst()))))"
1296                    }
1297                    return (["unset \(name)", "declare -A \(name)"] + entries).joined(separator: "\n")
1298                case .table(let rows):
1299                    let items = rows.map { $0.map { quoted(.list($0)) } ?? quoted(.scalar("hline")) }
1300                    return "unset \(name)\ndeclare -a \(name)=( \(items.joined(separator: " ")) )"
1301                case .list(let items):
1302                    return "unset \(name)\ndeclare -a \(name)=( \(items.map { quoted(.scalar(cellText($0))) }.joined(separator: " ")) )"
1303                case .scalar, .number:
1304                    break
1305                }
1306            }
1307            return (shell.hasSuffix("fish") ? "set \(name) " : "\(name)=") + quoted(value)
1308        }
1309    }
1310
1311    /// `org-babel-python-var-to-python`.
1312    static func pythonLiteral(_ value: VarValue) -> String {
1313        func cell(_ c: Cell) -> String {
1314            switch c {
1315            case .number(let n): return n
1316            case .text(let s): return lispString(s, triple: s.contains("\n") || s.contains("\r"))
1317            }
1318        }
1319        switch value {
1320        case .scalar(let s): return lispString(s, triple: s.contains("\n") || s.contains("\r"))
1321        case .number(let n): return n
1322        case .list(let items): return "[" + items.map(cell).joined(separator: ", ") + "]"
1323        case .table(let rows): return "[" + rows.map { $0.map { "[" + $0.map(cell).joined(separator: ", ") + "]" } ?? "None" }.joined(separator: ", ") + "]"
1324        }
1325    }
1326
1327    /// `prin1` of a string.
1328    static func lispString(_ s: String, triple: Bool = false) -> String {
1329        let quoted = "\"" + s.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "\"", with: "\\\"") + "\""
1330        return triple ? "\"\"" + quoted + "\"\"" : quoted
1331    }
1332
1333    static func lispLiteral(_ value: VarValue) -> String {
1334        func cell(_ c: Cell) -> String {
1335            switch c {
1336            case .number(let n): return n
1337            case .text(let s): return lispString(s)
1338            }
1339        }
1340        switch value {
1341        case .scalar(let s): return lispString(s)
1342        case .number(let n): return n
1343        case .list(let items): return "(" + items.map(cell).joined(separator: " ") + ")"
1344        case .table(let rows): return "(" + rows.map { $0.map { "(" + $0.map(cell).joined(separator: " ") + ")" } ?? "hline" }.joined(separator: " ") + ")"
1345        }
1346    }
1347
1348    // MARK: - Python
1349
1350    /// Writes the value of `main()` to the result file as org's Python wrapper does: `pprint`
1351    /// for `pp`, `key :: value` items for a dict as a list, dicts as rows for tables,
1352    /// otherwise `str`.
1353    static let pythonFormatter = """
1354    def __orgstar_format(value, path, params):
1355        def rows(v):
1356            if isinstance(v, dict):
1357                return [(k, rows(x)) for k, x in v.items()]
1358            if isinstance(v, (list, tuple)):
1359                return [rows(x) for x in v]
1360            return v
1361        with open(path, 'w') as out:
1362            if 'pp' in params:
1363                import pprint
1364                out.write(pprint.pformat(value))
1365            elif 'list' in params and isinstance(value, dict):
1366                out.write(str(['{} :: {}'.format(k, v) for k, v in value.items()]))
1367            else:
1368                if not set(params) & {'scalar', 'verbatim', 'raw'} and 'table' in params:
1369                    value = rows(value)
1370                out.write(str(value))
1371
1372    """
1373
1374    /// `org-babel-python--shift-right`: four spaces more on every line outside strings.
1375    static func shiftRight(_ body: String) -> String {
1376        var inString: String?
1377        var lines: [String] = []
1378        for line in body.components(separatedBy: "\n") {
1379            lines.append(inString == nil ? "    " + line : line)
1380            var i = line.startIndex
1381            while i < line.endIndex {
1382                if let delimiter = inString {
1383                    if line[i...].hasPrefix(delimiter) {
1384                        inString = nil
1385                        i = line.index(i, offsetBy: 3)
1386                        continue
1387                    }
1388                } else if line[i] == "#" {
1389                    break
1390                } else if line[i...].hasPrefix("\"\"\"") || line[i...].hasPrefix("'''") {
1391                    inString = String(line[i...].prefix(3))
1392                    i = line.index(i, offsetBy: 3)
1393                    continue
1394                } else if line[i] == "\"" || line[i] == "'" {
1395                    let quote = line[i]
1396                    i = line.index(after: i)
1397                    while i < line.endIndex, line[i] != quote {
1398                        if line[i] == "\\" { i = line.index(after: i) }
1399                        if i < line.endIndex { i = line.index(after: i) }
1400                    }
1401                }
1402                if i < line.endIndex { i = line.index(after: i) }
1403            }
1404        }
1405        return lines.joined(separator: "\n")
1406    }
1407
1408    // MARK: - Emacs Lisp
1409
1410    /// Evaluates `form` and prints the result as JSON: its `%s` and `%S` forms, and its rows
1411    /// when it is a table.
1412    static func emacsScript(form: String, params: [String]) -> String {
1413        """
1414        ;;; -*- lexical-binding: t -*-
1415        (require 'cl-lib)
1416        (require 'json)
1417        (let* ((result \(form))
1418               (print-level nil)
1419               (print-length nil)
1420               (cell (lambda (c) (if (stringp c) c (format "%s" c))))
1421               (tabulable (and (proper-list-p result)
1422                               (cl-every (lambda (e) (or (atom e) (proper-list-p e))) result)))
1423               (rows (and tabulable result
1424                          (if (cl-every (lambda (e) (or (eq e 'hline) (listp e))) result)
1425                              (mapcar (lambda (r) (if (eq r 'hline) "hline" (vconcat (mapcar cell r)))) result)
1426                            (list (vconcat (mapcar cell result)))))))
1427          (princ (json-encode `((s . ,(format "%s" result))
1428                                (S . ,(format "%S" result))
1429                                (string . ,(if (stringp result) t :json-false))
1430                                (rows . ,(if rows (vconcat rows) :json-null))))))
1431        """
1432    }
1433
1434    /// An Emacs Lisp job run by `Elisp` instead of Emacs: the output `emacsScript` would print.
1435    /// A Lisp error fails the run, as it does in Emacs; nil when the block uses what `Elisp`
1436    /// doesn't have.
1437    public static func evaluateNatively(_ job: BabelJob) throws(Failure) -> BabelOutput? {
1438        guard let form = job.lispForm, let sexp = try? LispReader.readFirst(form).sexp else { return nil }
1439        let lisp = Elisp()
1440        let result: Sexp
1441        do {
1442            result = try lisp.eval(sexp)
1443        } catch is Elisp.Unsupported {
1444            return nil
1445        } catch {
1446            throw .message("\(error)")
1447        }
1448        func cell(_ c: Sexp) -> String { Elisp.printed(c) }
1449        func isProper(_ v: Sexp) -> Bool {
1450            if case .list = v { return true }
1451            return Elisp.isNil(v)
1452        }
1453        func isAtom(_ v: Sexp) -> Bool {
1454            switch v {
1455            case .list(let items): items.isEmpty
1456            case .dotted: false
1457            default: true
1458            }
1459        }
1460        var rows: [Any] = []
1461        if case .list(let items) = result, items.allSatisfy({ isAtom($0) || isProper($0) }) {
1462            if items.allSatisfy({ $0 == .symbol("hline") || isProper($0) }) {
1463                rows = items.map { $0 == .symbol("hline") ? "hline" as Any : ((try? Elisp.elements($0)) ?? []).map(cell) as Any }
1464            } else {
1465                rows = [items.map(cell)]
1466            }
1467        }
1468        var object: [String: Any] = ["s": Elisp.printed(result), "S": Elisp.printed(result, escape: true), "string": result.string != nil]
1469        object["rows"] = rows.isEmpty ? NSNull() : rows
1470        guard let data = try? JSONSerialization.data(withJSONObject: object), let json = String(data: data, encoding: .utf8) else { return nil }
1471        return BabelOutput(stdout: lisp.standardOutput + json, stderr: lisp.messages)
1472    }
1473
1474    // MARK: - Finishing
1475
1476    /// What the run produced, as Babel holds it before inserting.
1477    enum Value: Equatable {
1478        case none
1479        case string(String)
1480        /// Rows of cells; nil rows are rules.
1481        case table([[String]?])
1482    }
1483
1484    /// The edit that writes the run's result under the block, or why it can't.
1485    public static func finish(_ job: BabelJob, output: BabelOutput, text: String, settings: OrgSettings = .default) -> Result<[TextEdit], Failure> {
1486        let ns = text as NSString
1487        var blockRange = job.block
1488        if blockRange.upperBound > ns.length || ns.substring(with: NSRange(blockRange)) != job.blockText {
1489            let found = ns.range(of: job.blockText)
1490            guard found.location != NSNotFound,
1491                  ns.range(of: job.blockText, range: NSRange(location: NSMaxRange(found), length: ns.length - NSMaxRange(found))).location == NSNotFound else {
1492                return .failure(.message("The block changed while it ran; the result wasn't written."))
1493            }
1494            blockRange = found.location..<NSMaxRange(found)
1495        }
1496        let value = Self.value(job.finish, output)
1497        if job.finish.params.contains("silent") || job.finish.params.contains("none") || job.finish.params.contains("discard") { return .success([]) }
1498        let buffer = EmacsBuffer(text, point: 0, settings: settings)
1499        if job.finish.inline {
1500            do {
1501                try insertInlineResult(value, job.finish, at: blockRange, buffer: buffer)
1502            } catch let error as Failure {
1503                return .failure(error)
1504            } catch {
1505                return .failure(.message("\(error)"))
1506            }
1507            return .success(TextEdit.between(text, buffer.text as String))
1508        }
1509        insertResult(value, job.finish, block: blockRange, buffer: buffer)
1510        return .success(TextEdit.between(text, buffer.text as String))
1511    }
1512
1513    /// The language's result handling: `org-babel-result-cond` and the table import.
1514    static func value(_ finish: BabelJob.Finish, _ output: BabelOutput) -> Value {
1515        reassemble(rawValue(finish, output), finish)
1516    }
1517
1518    static func rawValue(_ finish: BabelJob.Finish, _ output: BabelOutput) -> Value {
1519        let params = finish.params
1520        if params.contains("file") {
1521            // `org-babel-result-to-file`: a link to `:file`, or to the result as a path.
1522            let path = finish.file ?? rawResult(finish, output)
1523            var link = "[[file:" + path + "]"
1524            if let description = finish.fileDescription { link += "[" + (description.isEmpty ? path : description) + "]" }
1525            return .string(link + "]")
1526        }
1527        let scalar = params.contains("scalar") || params.contains("verbatim") || params.contains("html") || params.contains("code")
1528            || params.contains("pp") || params.contains("file")
1529            || ((params.contains("output") || params.contains("raw") || params.contains("org")) && !params.contains("table"))
1530        switch finish.kind {
1531        case .shell(let valueIsExitStatus):
1532            var results = output.stdout
1533            if valueIsExitStatus {
1534                results = results.split(separator: "\n", omittingEmptySubsequences: true).last.map(String.init) ?? ""
1535            }
1536            if scalar { return .string(results) }
1537            return importTable(results)
1538        case .generic:
1539            return .string(output.stdout)
1540        case .python:
1541            let raw = finish.output ? output.stdout : (output.resultFile ?? "")
1542            if scalar { return .string(raw) }
1543            return pythonValue(raw)
1544        case .emacsLisp:
1545            guard let data = output.stdout.data(using: .utf8),
1546                  let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return .string(output.stdout) }
1547            if scalar {
1548                return .string((params.contains("scalar") || params.contains("verbatim") ? object["S"] : object["s"]) as? String ?? "")
1549            }
1550            if object["string"] as? Bool == true { return .string(object["s"] as? String ?? "") }
1551            if let rows = object["rows"] as? [Any] {
1552                return .table(rows.map { $0 as? String == "hline" ? nil : ($0 as? [String] ?? []) })
1553            }
1554            let printed = object["S"] as? String ?? ""
1555            return printed == "nil" ? .none : .string(printed)
1556        case .compiled:
1557            return compiledValue(finish, output, scalar: scalar)
1558        }
1559    }
1560
1561    /// The result as the program left it, before tables are read.
1562    static func rawResult(_ finish: BabelJob.Finish, _ output: BabelOutput) -> String {
1563        switch finish.kind {
1564        case .python: return finish.output ? output.stdout : (output.resultFile ?? "")
1565        case .emacsLisp:
1566            guard let data = output.stdout.data(using: .utf8), let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return output.stdout }
1567            return object["s"] as? String ?? ""
1568        case .shell(let valueIsExitStatus):
1569            return valueIsExitStatus ? output.stdout.split(separator: "\n").last.map(String.init) ?? "" : output.stdout
1570        case .generic: return output.stdout
1571        case .compiled: return compiledResult(finish, output)
1572        }
1573    }
1574
1575    /// With `:results file` and `:file`, what to write there, relative to the run's folder;
1576    /// nil when there's nothing to write or the program wrote it.
1577    public static func fileResult(_ job: BabelJob, output: BabelOutput) -> (path: String, contents: String)? {
1578        guard job.finish.params.contains("file"), let file = job.finish.file, !job.finish.writesFile else { return nil }
1579        return (file, rawResult(job.finish, output))
1580    }
1581
1582    /// `org-babel-import-elisp-from-file`.
1583    static func importTable(_ results: String) -> Value {
1584        guard !results.isEmpty else { return .none }
1585        var lines = results.components(separatedBy: "\n")
1586        if lines.last == "" { lines.removeLast() }
1587        let nonEmpty = lines.filter { !$0.isEmpty }
1588        let rows: [[String]]
1589        if !nonEmpty.isEmpty, nonEmpty.allSatisfy({ $0.contains("\t") }) {
1590            rows = lines.map { $0.components(separatedBy: "\t") }
1591        } else if !nonEmpty.isEmpty, nonEmpty.allSatisfy({ $0.contains(",") }) {
1592            rows = lines.map(csvFields)
1593        } else if lines.count == 1 {
1594            rows = [[lines[0]]]
1595        } else {
1596            rows = lines.map { line in
1597                let trimmed = line.drop { $0 == " " }
1598                return trimmed.split(separator: " ", omittingEmptySubsequences: true).map(String.init).isEmpty
1599                    ? [""] : trimmed.split(separator: " ", omittingEmptySubsequences: true).map(String.init)
1600            }
1601        }
1602        let cells = rows.map { row in row.map { field -> String in
1603            let trimmed = field.trimmingCharacters(in: .whitespaces)
1604            switch cell(trimmed) {
1605            case .number(let n): return n
1606            case .text(let t): return t
1607            }
1608        } }
1609        if cells.count == 1, cells[0].count == 1 { return .string(cells[0][0]) }
1610        return .table(cells)
1611    }
1612
1613    static func csvFields(_ line: String) -> [String] {
1614        var fields: [String] = []
1615        var current = ""
1616        var quoted = false
1617        for c in line {
1618            if c == "\"" {
1619                quoted.toggle()
1620            } else if c == ",", !quoted {
1621                fields.append(current.trimmingCharacters(in: .whitespaces))
1622                current = ""
1623            } else {
1624                current.append(c)
1625            }
1626        }
1627        fields.append(current.trimmingCharacters(in: .whitespaces))
1628        return fields
1629    }
1630
1631    /// `org-babel-python-table-or-string`; with `none` "null" and `braces`,
1632    /// `org-babel-java-table-or-string`, which reads `{…}` as a list too.
1633    static func pythonValue(_ raw: String, none: String = "None", braces: Bool = false) -> Value {
1634        guard let first = raw.first, let last = raw.last, raw.count >= 2,
1635              (first == "[" && last == "]") || (first == "(" && last == ")") || (braces && first == "{" && last == "}") else {
1636            if raw.isEmpty { return .string("") }
1637            if raw.count > 2, (raw.hasPrefix("'") && raw.hasSuffix("'")) || (raw.hasPrefix("\"") && raw.hasSuffix("\"")) {
1638                return .string(String(raw.dropFirst().dropLast()))
1639            }
1640            if let n = number(raw) { return .string(n) }
1641            return .string(raw)
1642        }
1643        var parser = PythonLiteral(Array(raw))
1644        parser.braces = braces
1645        guard let parsed = parser.parse(), parser.atEnd, case .list(let items) = parsed else { return .string(raw) }
1646        func text(_ v: PythonLiteral.Node) -> String {
1647            switch v {
1648            case .atom(let s): return s
1649            case .string(let s): return s
1650            case .list(let xs): return "(" + xs.map(text).joined(separator: " ") + ")"
1651            }
1652        }
1653        if items.allSatisfy({ if case .list = $0 { return true }; if case .atom(none) = $0 { return true }; return false }) {
1654            return .table(items.map { item -> [String]? in
1655                if case .list(let cells) = item { return cells.map(text) }
1656                return nil
1657            })
1658        }
1659        return .table([items.map { if case .atom(none) = $0 { return "hline" }; return text($0) }])
1660    }
1661
1662    struct PythonLiteral {
1663        indirect enum Node: Equatable {
1664            case atom(String)
1665            case string(String)
1666            case list([Node])
1667        }
1668
1669        let chars: [Character]
1670        var i = 0
1671        /// `{…}` is a list, as `org-babel-script-escape` reads it.
1672        var braces = false
1673        init(_ chars: [Character]) { self.chars = chars }
1674        var atEnd: Bool { mutating get { skip(); return i == chars.count } }
1675
1676        mutating func skip() { while i < chars.count, chars[i] == " " || chars[i] == "\n" { i += 1 } }
1677
1678        mutating func parse() -> Node? {
1679            skip()
1680            guard i < chars.count else { return nil }
1681            let c = chars[i]
1682            if c == "[" || c == "(" || (braces && c == "{") {
1683                let close: Character = c == "[" ? "]" : c == "{" ? "}" : ")"
1684                i += 1
1685                var items: [Node] = []
1686                skip()
1687                if i < chars.count, chars[i] == close {
1688                    i += 1
1689                    return .list(items)
1690                }
1691                while true {
1692                    guard let item = parse() else { return nil }
1693                    items.append(item)
1694                    skip()
1695                    guard i < chars.count else { return nil }
1696                    if chars[i] == "," {
1697                        i += 1
1698                        skip()
1699                        if i < chars.count, chars[i] == close {
1700                            i += 1
1701                            return .list(items)
1702                        }
1703                        continue
1704                    }
1705                    if chars[i] == close {
1706                        i += 1
1707                        return .list(items)
1708                    }
1709                    return nil
1710                }
1711            }
1712            if c == "'" || c == "\"" {
1713                i += 1
1714                var s = ""
1715                while i < chars.count, chars[i] != c {
1716                    if chars[i] == "\\", i + 1 < chars.count {
1717                        i += 1
1718                        switch chars[i] {
1719                        case "n": s.append("\n")
1720                        case "t": s.append("\t")
1721                        default: s.append(chars[i])
1722                        }
1723                    } else {
1724                        s.append(chars[i])
1725                    }
1726                    i += 1
1727                }
1728                guard i < chars.count else { return nil }
1729                i += 1
1730                return .string(s)
1731            }
1732            var s = ""
1733            while i < chars.count, !",)] \n".contains(chars[i]), !(braces && chars[i] == "}") {
1734                s.append(chars[i])
1735                i += 1
1736            }
1737            guard !s.isEmpty else { return nil }
1738            return .atom(number(s) ?? s)
1739        }
1740    }
1741
1742    // MARK: - Inserting
1743
1744    static let resultKeyword = "(?i)^[ \\t]*#\\+results(\\[[^\\]]*\\])?:"
1745
1746    /// `org-babel-where-is-src-block-result` with `insert`: the line of the block's results
1747    /// keyword, added when there is none.
1748    /// The `#+RESULTS` line of a block's results (`org-babel-where-is-src-block-result`):
1749    /// the one naming `name`, or for an unnamed block an empty one right after it.
1750    static func existingResults(name: String?, block: Range<Int>, text ns: NSString) -> Int? {
1751        if let name {
1752            let pattern = "(?im)^[ \\t]*#\\+results(\\[[^\\]]*\\])?:[ \\t]*" + NSRegularExpression.escapedPattern(for: name) + "[ \\t]*$"
1753            let regex = try! NSRegularExpression(pattern: pattern)
1754            return regex.firstMatch(in: ns as String, range: NSRange(location: 0, length: ns.length))?.range.location
1755        }
1756        // The element after the block, past blank lines.
1757        var location = block.upperBound
1758        while location < ns.length {
1759            let line = ns.lineRange(for: NSRange(location: location, length: 0))
1760            let content = ns.substring(with: line)
1761            if content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
1762                location = NSMaxRange(line)
1763                continue
1764            }
1765            if content.range(of: "(?i)^[ \\t]*#\\+results(\\[[^\\]]*\\])?:[ \\t]*\\n?$", options: .regularExpression) != nil {
1766                return line.location
1767            }
1768            break
1769        }
1770        return nil
1771    }
1772
1773    /// The hash in the `#+RESULTS[…]:` line at `line` (`org-babel-result-regexp`).
1774    static func resultsHash(at line: Int, text ns: NSString) -> String? {
1775        let content = ns.substring(with: ns.lineRange(for: NSRange(location: line, length: 0)))
1776        return content.firstMatch(of: #/(?i)^[ \t]*#\+results\[(?:\(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d\) )?([0-9a-f]+)\]:/#).map { String($0.1) }
1777    }
1778
1779    static func resultsLine(_ finish: BabelJob.Finish, block: Range<Int>, buffer: EmacsBuffer) -> Int {
1780        if let line = existingResults(name: finish.name, block: block, text: buffer.text) {
1781            // `org-babel--clear-results-maybe`: a different hash clears the results and
1782            // rewrites the keyword.
1783            if let hash = finish.hash, resultsHash(at: line, text: buffer.text) != hash {
1784                buffer.point = line
1785                let indent = String(buffer.substring(line, buffer.lineEnd(line)).prefix { $0 == " " || $0 == "\t" })
1786                buffer.forwardLine(1)
1787                let contents = buffer.point
1788                buffer.deleteRegion(contents, max(contents, resultEnd(buffer)))
1789                buffer.deleteRegion(line, contents)
1790                buffer.point = line
1791                buffer.insert(indent + "#+RESULTS[\(hash)]:" + (finish.name.map { " " + $0 } ?? "") + "\n")
1792                if buffer.looking("^[ \\t]*$", caseInsensitive: false, setsMatch: false) == nil {
1793                    buffer.saveExcursion { buffer.insert("\n") }
1794                }
1795            }
1796            return line
1797        }
1798        // A new keyword after the block.
1799        buffer.point = min(block.upperBound, buffer.length)
1800        buffer.skipBackward([32, 9, 10, 13])
1801        buffer.forwardLine(1)
1802        if !buffer.bolp { buffer.insert("\n") }
1803        let indent = buffer.saveExcursion { () -> String in
1804            buffer.point = block.lowerBound
1805            let line = buffer.substring(buffer.lineStart(buffer.point), buffer.lineEnd(buffer.point))
1806            return String(line.prefix { $0 == " " || $0 == "\t" })
1807        }
1808        buffer.insert("\n")
1809        let keywordLine = buffer.point
1810        buffer.insert(indent + "#+RESULTS" + (finish.hash.map { "[\($0)]" } ?? "") + ":" + (finish.name.map { " " + $0 } ?? "") + "\n")
1811        if buffer.looking("^[ \\t]*$", caseInsensitive: false, setsMatch: false) == nil {
1812            buffer.saveExcursion { buffer.insert("\n") }
1813        }
1814        return keywordLine
1815    }
1816
1817    /// `org-babel-result-end` with point at the start of the result.
1818    static func resultEnd(_ buffer: EmacsBuffer) -> Int {
1819        let start = buffer.point
1820        let line = buffer.substring(buffer.lineStart(start), buffer.lineEnd(start))
1821        if line.trimmingCharacters(in: .whitespaces).isEmpty { return start }
1822        if line.range(of: "^[ \\t]*\\[\\[[^\\]]+\\](\\[[^\\]]*\\])?\\][ \\t]*$", options: .regularExpression) != nil {
1823            return min(buffer.length, buffer.lineEnd(start) + 1)
1824        }
1825        let tree = OrgParser.parse(buffer.text as String)
1826        var node = tree.root
1827        var found: SyntaxNode?
1828        while let child = node.child(containing: start) {
1829            if child.range.lowerBound == start || (buffer.lineStart(child.range.lowerBound) == start) {
1830                if [.fixedWidth, .table, .plainList, .drawer, .block, .dynamicBlock, .latexFragment].contains(child.kind) {
1831                    found = child
1832                    break
1833                }
1834            }
1835            node = child
1836        }
1837        guard let element = found else { return start }
1838        var end = element.range.upperBound
1839        while end > start, let c = buffer.character(at: end - 1), c == 32 || c == 9 || c == 10 || c == 13 { end -= 1 }
1840        return min(buffer.length, buffer.lineEnd(end) + 1)
1841    }
1842
1843    /// `org-babel-insert-result` for a src block.
1844    /// An inline block's result, after it: ` {{{results(=value=)}}}`, replacing one already
1845    /// there; raw results go in as they are.
1846    static func insertInlineResult(_ value: Value, _ finish: BabelJob.Finish, at object: Range<Int>, buffer: EmacsBuffer) throws {
1847        var string: String
1848        switch value {
1849        case .none: string = ""
1850        case .string(let s): string = s
1851        case .table(let rows):
1852            let cells = rows.compactMap { $0 }
1853            guard cells.count == 1, cells[0].count == 1 else { throw Failure.message("Inline source block results can't be tables or lists") }
1854            string = cells[0][0]
1855        }
1856        buffer.point = object.upperBound
1857        if let existing = buffer.lookingAt("[ \\t]*\\{\\{\\{results\\(.*?\\)\\}\\}\\}") {
1858            buffer.deleteRegion(existing[0]!.lowerBound, existing[0]!.upperBound)
1859        }
1860        if finish.params.contains("raw") {
1861            buffer.insert(" " + string)
1862            return
1863        }
1864        if string.hasSuffix("\n") { string.removeLast() }
1865        guard !string.contains("\n") else { throw Failure.message("Inline source block results must be one line") }
1866        buffer.insert(" {{{results(=" + string + "=)}}}")
1867    }
1868
1869    static func insertResult(_ value: Value, _ finish: BabelJob.Finish, block: Range<Int>, buffer: EmacsBuffer) {
1870        let params = finish.params
1871        let keyword = resultsLine(finish, block: block, buffer: buffer)
1872        buffer.point = keyword
1873        let indent = buffer.substring(buffer.lineStart(keyword), buffer.lineEnd(keyword)).prefix { $0 == " " || $0 == "\t" }.count
1874        buffer.forwardLine(1)
1875        if buffer.point == buffer.lineEnd(keyword) { buffer.insert("\n") }
1876        var beg = buffer.point
1877        if params.contains("replace") {
1878            buffer.deleteRegion(beg, resultEnd(buffer))
1879            buffer.point = beg
1880        } else if params.contains("append") {
1881            buffer.point = resultEnd(buffer)
1882            beg = buffer.point
1883        }
1884        var tabulated = false
1885        // A list made from a string is examplified, as a string result is.
1886        var listed = false
1887        switch value {
1888        case .none:
1889            break
1890        case .string(var s) where params.contains("list"):
1891            if s.last != "\n" { s += "\n" }
1892            let items = s.split(separator: "\n", omittingEmptySubsequences: true)
1893            buffer.insert(items.map { "- " + $0 }.joined(separator: "\n") + "\n")
1894        case .table(let rows) where params.contains("list"):
1895            listed = true
1896            let items = rows.count == 1 ? (rows[0] ?? []) : rows.compactMap { $0?.joined(separator: " ") }
1897            buffer.insert(items.map { "- " + $0 }.joined(separator: "\n") + "\n")
1898        case .table(let rows):
1899            tabulated = true
1900            let lines = rows.map { row in row.map { "| " + $0.map { " " + $0 + " " }.joined(separator: "|").dropFirst() + " |" } ?? "|-" }
1901            let start = buffer.point
1902            buffer.insert(lines.joined(separator: "\n") + "\n")
1903            buffer.point = start
1904            buffer.tableAlign()
1905            buffer.point = buffer.tableEnd()
1906        case .string(var s):
1907            if !s.isEmpty, s.last != "\n" { s += "\n" }
1908            buffer.insert(s)
1909        }
1910        let end = buffer.marker(buffer.point, advances: true)
1911        defer { buffer.release(end) }
1912        func wrap(_ open: String, _ close: String, escape: Bool) {
1913            buffer.point = end.position
1914            buffer.insert(close + "\n")
1915            buffer.point = beg
1916            buffer.insert(open + "\n")
1917            if escape { escapeCode(buffer, from: buffer.point, to: end.position - close.utf16.count - 1) }
1918        }
1919        if let wrapValue = finish.wrap {
1920            let full = wrapValue.isEmpty ? "results" : wrapValue
1921            let type = String(full.split(separator: " ").first ?? "results")
1922            if type.lowercased() != "nil", type.lowercased() != "no" {
1923                wrap("#+begin_" + full, "#+end_" + type, escape: !["example", "src", "export"].contains(type.lowercased()) ? false : true)
1924            }
1925        } else if params.contains("html") {
1926            wrap("#+begin_export html", "#+end_export", escape: true)
1927        } else if params.contains("latex") {
1928            wrap("#+begin_export latex", "#+end_export", escape: true)
1929        } else if params.contains("org") {
1930            wrap("#+begin_src org", "#+end_src", escape: true)
1931        } else if params.contains("code") {
1932            wrap("#+begin_src \(finish.language.isEmpty ? "none" : finish.language)", "#+end_src", escape: true)
1933        } else if params.contains("raw") || params.contains("file") {
1934            // As inserted.
1935        } else if params.contains("drawer") {
1936            wrap(":results:", ":end:", escape: false)
1937        } else if !tabulated, !(params.contains("list") && listed) {
1938            examplify(buffer, beg, end.position)
1939        }
1940        if indent > 0, !(tabulated && params.contains("append")) {
1941            buffer.point = beg
1942            while buffer.point < end.position {
1943                if !buffer.eolp { buffer.insert(String(repeating: " ", count: indent)) }
1944                if buffer.forwardLine(1) != 0 { break }
1945            }
1946        }
1947    }
1948
1949    /// `org-escape-code-in-region`.
1950    static func escapeCode(_ buffer: EmacsBuffer, from start: Int, to end: Int) {
1951        buffer.point = start
1952        let stop = buffer.marker(end)
1953        defer { buffer.release(stop) }
1954        while buffer.point < stop.position {
1955            let line = buffer.substring(buffer.point, buffer.lineEnd(buffer.point))
1956            if let m = line.firstMatch(of: /^[ \t]*(,*(\*|#\+))/) {
1957                buffer.point += line[..<m.1.startIndex].utf16.count
1958                buffer.insert(",")
1959            }
1960            if buffer.forwardLine(1) != 0 { break }
1961        }
1962    }
1963
1964    /// `org-babel-examplify-region`: fewer than ten lines get `: `, more an example block.
1965    static func examplify(_ buffer: EmacsBuffer, _ beg: Int, _ end: Int) {
1966        let text = buffer.substring(beg, end)
1967        var size = text.components(separatedBy: "\n").count - (text.hasSuffix("\n") ? 1 : 0)
1968        if text.isEmpty { size = 0 }
1969        guard size > 0 else { return }
1970        if size < 10 {
1971            buffer.point = beg
1972            for _ in 0..<size {
1973                buffer.point = buffer.lineStart(buffer.point)
1974                buffer.insert(": ")
1975                buffer.forwardLine(1)
1976            }
1977            return
1978        }
1979        let endMarker = buffer.marker(end)
1980        defer { buffer.release(endMarker) }
1981        buffer.point = beg
1982        buffer.insert("#+begin_example\n")
1983        escapeCode(buffer, from: buffer.point, to: endMarker.position)
1984        buffer.point = endMarker.position
1985        if !buffer.bolp { buffer.insert("\n") }
1986        buffer.insert("#+end_example")
1987        if !buffer.eolp { buffer.insert("\n") }
1988    }
1989}
1990
1991/// C-c C-c in a src block: `org-babel-execute-src-block`.
1992public struct ExecuteSrcBlock: OrgCommand {
1993    public init() {}
1994    public var id: String { "org.babel.execute" }
1995    public var title: String { "Run Source Block" }
1996    public func applies(in context: EditContext) -> Bool { KeyContext.holds("src", in: context) || Self.atCallOrInline(context) }
1997
1998    /// On a `#+CALL:` line, an inline `call_` or an inline `src_` block.
1999    static func atCallOrInline(_ context: EditContext) -> Bool {
2000        let ns = context.text as NSString
2001        let line = ns.substring(with: ns.lineRange(for: NSRange(location: min(context.caret, ns.length), length: 0)))
2002        if line.range(of: "(?i)^[ \\t]*#\\+CALL:", options: .regularExpression) != nil { return true }
2003        var node = context.tree.root
2004        while let child = node.child(containing: context.caret) {
2005            if child.kind == .inlineSourceBlock || child.kind == .inlineBabelCall { return true }
2006            node = child
2007        }
2008        return false
2009    }
2010
2011    public func run(in context: EditContext) -> CommandStep {
2012        switch Babel.plan(context) {
2013        case .success(let job): return .external(.babel(job))
2014        case .failure(.message(let message)): return .failed(message)
2015        }
2016    }
2017}