krz/orgstar

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

Sources/OrgCore/Compute/Calc.swift

879 lines · 37464 bytes

62 symbols in this file
  1import Foundation
  2
  3/// The part of Emacs Calc that table formulas use, with Calc's number semantics: exact
  4/// integers, decimal floats rounded to `calc-internal-prec` digits after every operation, and
  5/// Calc's display formats. Anything outside this domain throws `Calc.Unsupported`, and the
  6/// caller hands the work to Emacs.
  7public enum Calc {
  8    public struct Unsupported: Error, Equatable, CustomStringConvertible {
  9        public let reason: String
 10        public var description: String { reason }
 11        init(_ reason: String) { self.reason = reason }
 12    }
 13
 14    /// An error Calc reports, which a table shows as `#ERROR`.
 15    public struct Failure: Error, Equatable, CustomStringConvertible {
 16        public let reason: String
 17        public var description: String { reason }
 18        init(_ reason: String) { self.reason = reason }
 19    }
 20
 21    public indirect enum Value: Equatable, Sendable {
 22        case int(Int)
 23        /// Mantissa and exponent, normalized: at most `precision` digits, no trailing zeros.
 24        case float(Int, Int)
 25        case vector([Value])
 26        /// A date form: the day number, with the time of day as a fraction.
 27        case date(Value)
 28        case nan
 29        /// An exact fraction, reduced, the denominator above 1 (`calc-prefer-frac`).
 30        case frac(Int, Int)
 31    }
 32
 33    public enum FloatFormat: Equatable, Sendable {
 34        case float(Int), fix(Int), sci(Int), eng(Int)
 35    }
 36
 37    public struct Modes: Equatable, Sendable {
 38        public var precision = 12
 39        public var format = FloatFormat.float(8)
 40        public var degrees = true
 41        /// `calc-prefer-frac`: dividing integers gives a fraction.
 42        public var preferFractions = false
 43        public init() {}
 44    }
 45
 46    /// `calc-eval` of `expression`, as the string Calc would return; with `numeric`, as
 47    /// `calc-eval` with `num`, a result that isn't a number is an error.
 48    public static func evaluate(_ expression: String, modes: Modes = Modes(), numeric: Bool = false) throws -> String {
 49        guard modes.precision == 12 else { throw Unsupported("calc precision") }
 50        var parser = Parser(expression)
 51        let tree = try parser.parse()
 52        let value = try Evaluator(modes: modes).eval(tree)
 53        if numeric {
 54            switch value {
 55            case .int, .float: break
 56            default: throw Failure("Number expected")
 57            }
 58        }
 59        return try format(value, modes.format)
 60    }
 61
 62    // MARK: - Numbers
 63
 64    static let precision = 12
 65
 66    static func digits(_ x: Int128) -> Int {
 67        var n = x.magnitude
 68        var count = 1
 69        while n >= 10 {
 70            n /= 10
 71            count += 1
 72        }
 73        return count
 74    }
 75
 76    static func power10(_ n: Int) throws -> Int128 {
 77        guard n >= 0, n <= 37 else { throw Unsupported("number size") }
 78        var result: Int128 = 1
 79        for _ in 0..<n { result *= 10 }
 80        return result
 81    }
 82
 83    /// `math-scale-rounding`: `x` times 10^n, rounding half away from zero when n < 0.
 84    static func scaleRounding(_ x: Int128, _ n: Int) throws -> Int128 {
 85        if n >= 0 {
 86            let (result, overflow) = x.multipliedReportingOverflow(by: try power10(n))
 87            if overflow { throw Unsupported("number size") }
 88            return result
 89        }
 90        if x < 0 { return -(try scaleRounding(-x, n)) }
 91        if -n > 38 { return 0 }
 92        let truncated = -n - 1 > 37 ? 0 : x / (try power10(-n - 1))
 93        return (truncated + 5) / 10
 94    }
 95
 96    /// `math-make-float`.
 97    static func makeFloat(_ mantissa: Int128, _ exponent: Int) throws -> Value {
 98        var m = mantissa
 99        var e = exponent
100        if m == 0 { return .float(0, 0) }
101        let excess = digits(m) - precision
102        if excess > 0 {
103            m = try scaleRounding(m, -excess)
104            e += excess
105        }
106        if m == 0 { return .float(0, 0) }
107        while m % 10 == 0 {
108            m /= 10
109            e += 1
110        }
111        return .float(Int(m), e)
112    }
113
114    static func parts(_ v: Value) throws -> (Int128, Int) {
115        switch v {
116        case .int(let i): return (Int128(i), 0)
117        case .float(let m, let e): return (Int128(m), e)
118        case .frac(let n, let d): return try parts(try divide(.int(n), .int(d)))
119        case .vector: throw Unsupported("vector arithmetic")
120        case .date, .nan: throw Unsupported("date arithmetic")
121        }
122    }
123
124    /// An integer or a fraction as numerator and denominator.
125    static func rational(_ v: Value) -> (Int, Int)? {
126        switch v {
127        case .int(let i): (i, 1)
128        case .frac(let n, let d): (n, d)
129        default: nil
130        }
131    }
132
133    /// `math-make-frac`: reduced, as an integer when it is one.
134    static func fraction(_ n: Int, _ d: Int) throws -> Value {
135        guard d != 0 else { throw Unsupported("division by zero") }
136        guard n != .min, d != .min else { throw Unsupported("integer size") }
137        func gcd(_ a: Int, _ b: Int) -> Int { b == 0 ? abs(a) : gcd(b, a % b) }
138        let g = gcd(n, d)
139        var (num, den) = (n / g, d / g)
140        if den < 0 { (num, den) = (-num, -den) }
141        return den == 1 ? .int(num) : .frac(num, den)
142    }
143
144    /// Two rationals, one of them a fraction, combined exactly; nil otherwise.
145    static func exact(_ a: Value, _ b: Value, _ op: (Int, Int, Int, Int) -> (Int, Int, Bool)) throws -> Value? {
146        var fraction = false
147        if case .frac = a { fraction = true }
148        if case .frac = b { fraction = true }
149        guard fraction, let (n1, d1) = rational(a), let (n2, d2) = rational(b) else { return nil }
150        let (n, d, overflow) = op(n1, d1, n2, d2)
151        if overflow { throw Unsupported("integer size") }
152        return try self.fraction(n, d)
153    }
154
155    static func double(_ v: Value) throws -> Double {
156        switch v {
157        case .int(let i): return Double(i)
158        case .float(let m, let e): return Double(m) * pow(10, Double(e))
159        case .frac(let n, let d): return Double(n) / Double(d)
160        case .vector: throw Unsupported("vector arithmetic")
161        case .date, .nan: throw Unsupported("date arithmetic")
162        }
163    }
164
165    static func fromDouble(_ d: Double) throws -> Value {
166        guard d.isFinite else { throw Unsupported("infinite result") }
167        if d == 0 { return .float(0, 0) }
168        // Seventeen significant digits are exact for a Double; rounding to 12 follows.
169        let text = String(format: "%.16e", d)
170        let pieces = text.split(separator: "e")
171        let digitsText = pieces[0].replacingOccurrences(of: ".", with: "")
172        guard let mantissa = Int128(digitsText), let exponent = Int(pieces[1]) else { throw Unsupported("number") }
173        return try makeFloat(mantissa, exponent - 16)
174    }
175
176    static func add(_ a: Value, _ b: Value) throws -> Value {
177        switch (a, b) {
178        case (.nan, .vector), (.vector, .nan): throw Unsupported("vector arithmetic")
179        case (.nan, _), (_, .nan): return .nan
180        case (.date, .date): throw Unsupported("date arithmetic")
181        case (.date(let d), _): return .date(try add(d, b))
182        case (_, .date(let d)): return .date(try add(a, d))
183        default: break
184        }
185        if case .int(let x) = a, case .int(let y) = b {
186            let (sum, overflow) = x.addingReportingOverflow(y)
187            if overflow { throw Unsupported("integer size") }
188            return .int(sum)
189        }
190        if let sum = try exact(a, b, { n1, d1, n2, d2 in
191            let (p, o1) = n1.multipliedReportingOverflow(by: d2)
192            let (q, o2) = n2.multipliedReportingOverflow(by: d1)
193            let (n, o3) = p.addingReportingOverflow(q)
194            let (d, o4) = d1.multipliedReportingOverflow(by: d2)
195            return (n, d, o1 || o2 || o3 || o4)
196        }) { return sum }
197        let (m1, e1) = try parts(a)
198        let (m2, e2) = try parts(b)
199        if m1 == 0 { return try makeFloat(m2, e2) }
200        if m2 == 0 { return try makeFloat(m1, e1) }
201        let ediff = e1 - e2
202        if ediff >= 0 {
203            if ediff >= 2 * precision { return try makeFloat(m1, e1) }
204            return try makeFloat(try scaleRounding(m1, ediff) + m2, e2)
205        }
206        if -ediff >= 2 * precision { return try makeFloat(m2, e2) }
207        return try makeFloat(m1 + (try scaleRounding(m2, -ediff)), e1)
208    }
209
210    static func negate(_ a: Value) throws -> Value {
211        switch a {
212        case .int(let i):
213            guard i != .min else { throw Unsupported("integer size") }
214            return .int(-i)
215        case .float(let m, let e): return .float(-m, e)
216        case .frac(let n, let d): return .frac(-n, d)
217        case .vector: throw Unsupported("vector arithmetic")
218        case .date: throw Unsupported("date arithmetic")
219        case .nan: return .nan
220        }
221    }
222
223    /// `a - b`: a date less a date is the days between them.
224    static func subtract(_ a: Value, _ b: Value) throws -> Value {
225        if case .date(let x) = a, case .date(let y) = b { return try add(x, try negate(y)) }
226        if case .date(let x) = a, b != .nan { return .date(try add(x, try negate(b))) }
227        return try add(a, try negate(b))
228    }
229
230    static func isNaN(_ a: Value, _ b: Value) -> Bool {
231        if case .vector = a { return false }
232        if case .vector = b { return false }
233        return a == .nan || b == .nan
234    }
235
236    static func multiply(_ a: Value, _ b: Value) throws -> Value {
237        if isNaN(a, b) { return .nan }
238        if case .int(let x) = a, case .int(let y) = b {
239            let (product, overflow) = x.multipliedReportingOverflow(by: y)
240            if overflow { throw Unsupported("integer size") }
241            return .int(product)
242        }
243        if let product = try exact(a, b, { n1, d1, n2, d2 in
244            let (n, o1) = n1.multipliedReportingOverflow(by: n2)
245            let (d, o2) = d1.multipliedReportingOverflow(by: d2)
246            return (n, d, o1 || o2)
247        }) { return product }
248        let (m1, e1) = try parts(a)
249        let (m2, e2) = try parts(b)
250        let (product, overflow) = m1.multipliedReportingOverflow(by: m2)
251        if overflow { throw Unsupported("number size") }
252        return try makeFloat(product, e1 + e2)
253    }
254
255    static func divide(_ a: Value, _ b: Value, fractions: Bool = false) throws -> Value {
256        if isNaN(a, b) { return .nan }
257        if fractions || { if case .frac = a { return true }; if case .frac = b { return true }; return false }(),
258           let (n1, d1) = rational(a), let (n2, d2) = rational(b) {
259            guard n2 != 0 else { throw Unsupported("division by zero") }
260            let (n, o1) = n1.multipliedReportingOverflow(by: d2)
261            let (d, o2) = d1.multipliedReportingOverflow(by: n2)
262            if o1 || o2 { throw Unsupported("integer size") }
263            return try fraction(n, d)
264        }
265        if case .int(let x) = a, case .int(let y) = b {
266            guard y != 0 else { throw Unsupported("division by zero") }
267            if x % y == 0 { return .int(x / y) }
268        }
269        let (m1, e1) = try parts(a)
270        let (m2, e2) = try parts(b)
271        guard m2 != 0 else { throw Unsupported("division by zero") }
272        // `math-div-float`: scale so the truncated quotient has one digit more than needed.
273        let ldiff = max(precision + 1 - (digits(m1) - digits(m2)), 0)
274        let quotient = (try scaleRounding(m1, ldiff)) / m2
275        return try makeFloat(quotient, e1 - ldiff - e2)
276    }
277
278    static func isInteger(_ v: Value) -> Bool {
279        if case .int = v { return true }
280        return false
281    }
282
283    /// `math-ipow`.
284    static func integerPower(_ a: Value, _ n: Int, fractions: Bool = false) throws -> Value {
285        if n < 0 { return try integerPower(try divide(.int(1), a, fractions: fractions), -n) }
286        if n == 0 { return .int(1) }
287        if n == 1 { return a }
288        let square = try multiply(a, a)
289        if n % 2 == 0 { return try integerPower(square, n / 2) }
290        return try multiply(a, try integerPower(square, n / 2))
291    }
292
293    static func power(_ a: Value, _ b: Value, fractions: Bool = false) throws -> Value {
294        if isNaN(a, b) { return .nan }
295        if case .int(let n) = b { return try integerPower(a, n, fractions: fractions) }
296        let base = try double(a)
297        guard base >= 0 else { throw Unsupported("complex result") }
298        return try fromDouble(pow(base, try double(b)))
299    }
300
301    static func compare(_ a: Value, _ b: Value) throws -> Int {
302        if case .date(let x) = a, case .date(let y) = b { return try compare(x, y) }
303        if a == .nan || b == .nan { throw Unsupported("comparison with nan") }
304        let difference = try add(a, try negate(b))
305        let (m, _) = try parts(difference)
306        return m == 0 ? 0 : (m < 0 ? -1 : 1)
307    }
308
309    // MARK: - Display
310
311    /// `math-format-number`.
312    static func format(_ value: Value, _ floatFormat: FloatFormat) throws -> String {
313        switch value {
314        case .int(let i): return String(i)
315        case .frac(let n, let d): return "\(n):\(d)"
316        case .vector: throw Unsupported("vector result")
317        case .nan: return "nan"
318        case .date(let d): return "<" + (try formatDate(d)) + ">"
319        case .float(let m, _) where m < 0:
320            guard case .float(let m, let e) = value else { fatalError() }
321            return "-" + (try format(.float(-m, e), floatFormat))
322        case .float(let m, var exp):
323            var mant = Int128(m)
324            var figs: Int
325            let kind: String
326            switch floatFormat {
327            case .float(let n): (figs, kind) = (n, "float")
328            case .fix(let n): (figs, kind) = (n, "fix")
329            case .sci(let n): (figs, kind) = (n, "sci")
330            case .eng(let n): (figs, kind) = (n, "eng")
331            }
332            if kind == "fix", figs < 0 || exp + digits(mant) > -figs {
333                if figs < 0 { figs = -figs }
334                mant = try scaleRounding(mant, exp + figs)
335                var str = String(mant)
336                if str.count <= figs { str = String(repeating: "0", count: figs + 1 - str.count) + str }
337                if figs > 0 {
338                    return String(str.dropLast(figs)) + "." + String(str.suffix(figs))
339                }
340                return str + "."
341            }
342            if figs < 0 { figs += precision }
343            if figs > 0 {
344                let adj = figs - digits(mant)
345                if adj < 0 {
346                    mant = try scaleRounding(mant, adj)
347                    exp -= adj
348                }
349            }
350            var str = String(mant)
351            let len = str.count
352            let dpos = exp + len
353            // `calc-display-sci-high` 0, `calc-display-sci-low` -3.
354            if kind == "float", dpos <= precision, dpos >= -1 {
355                if dpos == 0 {
356                    str = "0." + str
357                } else if exp <= 0, dpos > 0 {
358                    str = String(str.prefix(dpos)) + "." + String(str.dropFirst(dpos))
359                } else if exp > 0 {
360                    str += String(repeating: "0", count: exp) + "."
361                } else {
362                    str = "0." + String(repeating: "0", count: -dpos) + str
363                }
364                return str
365            }
366            let eadj = exp + len
367            let scale = kind == "eng" ? 1 + ((eadj + 300002) % 3) : 1
368            if scale > str.count { str += String(repeating: "0", count: scale - str.count) }
369            if scale < str.count { str = String(str.prefix(scale)) + "." + String(str.dropFirst(scale)) }
370            return str + "e" + String(eadj - scale)
371        }
372    }
373
374    static let weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
375
376    /// `calc-date-format` as Org sets it: `YYYY-MM-DD Www`, then ` hh:mm` for a time of day.
377    static func formatDate(_ value: Value) throws -> String {
378        let days = try double(value)
379        let day = Int(days.rounded(.down))
380        let date = Days.date(day)
381        var text = String(format: "%04d-%02d-%02d ", date.year, date.month, date.day) + weekdays[Days.weekday(day)]
382        if case .float = value, days != Double(day) {
383            let seconds = Int(((days - Double(day)) * 86400).rounded())
384            text += String(format: " %02d:%02d", seconds / 3600, seconds / 60 % 60)
385        }
386        return text
387    }
388
389    /// A date form `<YYYY-MM-DD Www>` or `<YYYY-MM-DD Www hh:mm>`, from the text between
390    /// the brackets.
391    static func parseDate(_ text: String) throws -> Value {
392        guard let m = text.firstMatch(of: /^([0-9]{4})-([0-9]{2})-([0-9]{2})(?: +[A-Za-z]+)?(?: +([0-9]{1,2}):([0-9]{2})(?::([0-9]{2}))?)?$/) else {
393            throw Unsupported("date syntax")
394        }
395        let day = Days.absolute(year: Int(m.1)!, month: Int(m.2)!, day: Int(m.3)!)
396        guard let hour = m.4.flatMap({ Int($0) }), let minute = m.5.flatMap({ Int($0) }) else { return .date(.int(day)) }
397        let seconds = hour * 3600 + minute * 60 + (m.6.flatMap { Int($0) } ?? 0)
398        if seconds == 0 { return .date(.int(day)) }
399        return .date(try add(.int(day), try divide(.int(seconds), .int(86400))))
400    }
401
402    // MARK: - Parsing
403
404    indirect enum Node {
405        case number(Value)
406        case vector([Node])
407        case unary(Character, Node)
408        case binary(Character, Node, Node)
409        case call(String, [Node])
410    }
411
412    struct Parser {
413        let chars: [Character]
414        var i = 0
415
416        init(_ s: String) { chars = Array(s) }
417
418        mutating func skip() {
419            while i < chars.count, chars[i] == " " || chars[i] == "\t" { i += 1 }
420        }
421
422        mutating func peek() -> Character? {
423            skip()
424            return i < chars.count ? chars[i] : nil
425        }
426
427        mutating func parse() throws -> Node {
428            let node = try or()
429            guard peek() == nil else { throw Unsupported("formula syntax") }
430            return node
431        }
432
433        mutating func take(_ token: String) -> Bool {
434            guard peek() != nil else { return false }
435            let t = Array(token)
436            guard i + t.count <= chars.count, Array(chars[i..<(i + t.count)]) == t else { return false }
437            i += t.count
438            return true
439        }
440
441        // `||` < `&&` < `!` < comparisons < sums.
442        mutating func or() throws -> Node {
443            var left = try and()
444            while take("||") { left = .binary("|", left, try and()) }
445            return left
446        }
447
448        mutating func and() throws -> Node {
449            var left = try not()
450            while take("&&") { left = .binary("&", left, try not()) }
451            return left
452        }
453
454        mutating func not() throws -> Node {
455            if peek() == "!", i + 1 >= chars.count || chars[i + 1] != "=" {
456                i += 1
457                return .unary("!", try not())
458            }
459            return try comparison()
460        }
461
462        mutating func comparison() throws -> Node {
463            let left = try sum()
464            for (token, op) in [("==", "="), ("!=", "≠"), ("<=", "≤"), (">=", "≥"), ("<", "<"), (">", ">")] where take(token) {
465                let right = try sum()
466                if let next = peek(), "=!<>".contains(next) { throw Unsupported("chained comparison") }
467                return .binary(Character(op), left, right)
468            }
469            return left
470        }
471
472        // `+ -` < `/` < `*` < unary minus < `^`, as Calc's operator table orders them.
473        mutating func sum() throws -> Node {
474            var left = try quotient()
475            while let c = peek(), c == "+" || c == "-" {
476                i += 1
477                left = .binary(c, left, try quotient())
478            }
479            return left
480        }
481
482        mutating func quotient() throws -> Node {
483            var left = try product()
484            while let c = peek(), c == "/" || c == "%" || c == "\\" {
485                i += 1
486                left = .binary(c, left, try product())
487            }
488            return left
489        }
490
491        mutating func product() throws -> Node {
492            var left = try unary()
493            while peek() == "*" {
494                i += 1
495                left = .binary("*", left, try unary())
496            }
497            return left
498        }
499
500        mutating func unary() throws -> Node {
501            if let c = peek(), c == "-" || c == "+" {
502                i += 1
503                let operand = try unary()
504                return c == "-" ? .unary("-", operand) : operand
505            }
506            return try powerNode()
507        }
508
509        mutating func powerNode() throws -> Node {
510            let base = try primary()
511            if peek() == "^" {
512                i += 1
513                return .binary("^", base, try unary())
514            }
515            return base
516        }
517
518        mutating func primary() throws -> Node {
519            guard let c = peek() else { throw Unsupported("formula syntax") }
520            if c == "(" {
521                i += 1
522                let inner = try or()
523                guard peek() == ")" else { throw Unsupported("formula syntax") }
524                i += 1
525                return inner
526            }
527            if c == "[" {
528                i += 1
529                var items: [Node] = []
530                if peek() != "]" {
531                    items.append(try or())
532                    while peek() == "," {
533                        i += 1
534                        items.append(try or())
535                    }
536                }
537                guard peek() == "]" else { throw Unsupported("formula syntax") }
538                i += 1
539                return .vector(items)
540            }
541            if c.isASCII, c.isNumber || c == "." { return .number(try number()) }
542            if c == "<" {
543                guard let close = chars[i...].firstIndex(of: ">") else { throw Unsupported("formula syntax") }
544                let text = String(chars[(i + 1)..<close])
545                i = close + 1
546                return .number(try parseDate(text))
547            }
548            if c.isLetter {
549                var name = ""
550                while i < chars.count, chars[i].isLetter || chars[i].isNumber || chars[i] == "_" {
551                    name.append(chars[i])
552                    i += 1
553                }
554                guard peek() == "(" else {
555                    if name == "nan" { return .number(.nan) }
556                    throw Unsupported("variable \(name)")
557                }
558                i += 1
559                var args: [Node] = []
560                if peek() != ")" {
561                    args.append(try or())
562                    while peek() == "," {
563                        i += 1
564                        args.append(try or())
565                    }
566                }
567                guard peek() == ")" else { throw Unsupported("formula syntax") }
568                i += 1
569                return .call(name, args)
570            }
571            throw Unsupported("formula syntax")
572        }
573
574        /// Calc's number syntax: `7`, `007`, `1.5`, `.5`, `5.`, `1e3`, `1.5e-3`.
575        mutating func number() throws -> Value {
576            var whole = ""
577            while i < chars.count, chars[i].isASCII, chars[i].isNumber {
578                whole.append(chars[i])
579                i += 1
580            }
581            var fraction: String?
582            if i < chars.count, chars[i] == "." {
583                i += 1
584                var f = ""
585                while i < chars.count, chars[i].isASCII, chars[i].isNumber {
586                    f.append(chars[i])
587                    i += 1
588                }
589                fraction = f
590            }
591            var exponent: Int?
592            if i < chars.count, chars[i] == "e" || chars[i] == "E" {
593                var j = i + 1
594                var e = ""
595                if j < chars.count, chars[j] == "-" || chars[j] == "+" {
596                    e.append(chars[j])
597                    j += 1
598                }
599                var digitsSeen = false
600                while j < chars.count, chars[j].isASCII, chars[j].isNumber {
601                    e.append(chars[j])
602                    j += 1
603                    digitsSeen = true
604                }
605                if digitsSeen {
606                    exponent = Int(e)
607                    i = j
608                }
609            }
610            guard !whole.isEmpty || !(fraction ?? "").isEmpty else { throw Unsupported("formula syntax") }
611            if fraction == nil, exponent == nil {
612                guard let value = Int(whole) else { throw Unsupported("integer size") }
613                return .int(value)
614            }
615            let digitsText = whole + (fraction ?? "")
616            guard let mantissa = Int128(digitsText.isEmpty ? "0" : digitsText) else { throw Unsupported("number size") }
617            return try makeFloat(mantissa, (exponent ?? 0) - (fraction?.count ?? 0))
618        }
619    }
620
621    // MARK: - Evaluation
622
623    struct Evaluator {
624        let modes: Modes
625
626        func eval(_ node: Node) throws -> Value {
627            switch node {
628            case .number(let v): return v
629            case .vector(let items): return .vector(try items.map(eval))
630            case .unary("!", let operand): return .int(try truth(try eval(operand)) ? 0 : 1)
631            case .unary(_, let operand): return try negate(try eval(operand))
632            case .binary(let op, let l, let r):
633                let a = try eval(l)
634                let b = try eval(r)
635                switch op {
636                case "+": return try add(a, b)
637                case "-": return try subtract(a, b)
638                case "*": return try multiply(a, b)
639                case "/": return try divide(a, b, fractions: modes.preferFractions)
640                case "%": return try mod(a, b)
641                case "\\": return try idiv(a, b)
642                case "^": return try power(a, b, fractions: modes.preferFractions)
643                case "|": return .int(try truth(a) || truth(b) ? 1 : 0)
644                case "&": return .int(try truth(a) && truth(b) ? 1 : 0)
645                default:
646                    let order = try compare(a, b)
647                    let holds = switch op {
648                    case "=": order == 0
649                    case "≠": order != 0
650                    case "<": order < 0
651                    case ">": order > 0
652                    case "≤": order <= 0
653                    default: order >= 0
654                    }
655                    return .int(holds ? 1 : 0)
656                }
657            case .call(let name, let args):
658                return try call(name, try args.map(eval))
659            }
660        }
661
662        /// A condition's value: a number, nonzero for true.
663        func truth(_ v: Value) throws -> Bool {
664            switch v {
665            case .int(let i): return i != 0
666            case .float(let m, _): return m != 0
667            default: throw Unsupported("condition")
668            }
669        }
670
671        /// `math-mod`: the remainder with the divisor's sign.
672        func mod(_ a: Value, _ b: Value) throws -> Value {
673            if case .int(let x) = a, case .int(let y) = b {
674                guard y != 0 else { throw Unsupported("division by zero") }
675                let r = x % y
676                return .int(r != 0 && (r < 0) != (y < 0) ? r + y : r)
677            }
678            let q = try floorOf(try divide(a, b))
679            return try subtract(a, try multiply(b, q))
680        }
681
682        /// `calcFunc-idiv`: the quotient rounded down.
683        func idiv(_ a: Value, _ b: Value) throws -> Value {
684            if case .int(let x) = a, case .int(let y) = b {
685                guard y != 0 else { throw Unsupported("division by zero") }
686                let q = x / y
687                return .int(q * y != x && (x < 0) != (y < 0) ? q - 1 : q)
688            }
689            return try floorOf(try divide(a, b))
690        }
691
692        func floorOf(_ v: Value) throws -> Value {
693            if case .int = v { return v }
694            guard let i = Int(exactly: try double(v).rounded(.down)) else { throw Unsupported("integer size") }
695            return .int(i)
696        }
697
698        /// The sum of squared deviations from the mean, and the count.
699        func deviations(_ args: [Value]) throws -> (Value, Int) {
700            let items = try elements(args)
701            guard !items.isEmpty else { throw Unsupported("empty vector") }
702            let mean = try divide(try items.reduce(Value.int(0)) { try add($0, $1) }, .int(items.count), fractions: modes.preferFractions)
703            let squares = try items.reduce(Value.int(0)) { sum, x in
704                let d = try subtract(x, mean)
705                return try add(sum, try multiply(d, d))
706            }
707            return (squares, items.count)
708        }
709
710        func root(_ x: Value) throws -> Value {
711            try call("sqrt", [x])
712        }
713
714        func elements(_ args: [Value]) throws -> [Value] {
715            guard args.count == 1 else { throw Unsupported("arguments") }
716            if case .vector(let items) = args[0] { return items }
717            return [args[0]]
718        }
719
720        func one(_ args: [Value]) throws -> Value {
721            guard args.count == 1 else { throw Unsupported("arguments") }
722            if case .vector = args[0] { throw Unsupported("vector argument") }
723            return args[0]
724        }
725
726        func radians(_ x: Double) -> Double { modes.degrees ? x * .pi / 180 : x }
727        func angle(_ x: Double) -> Double { modes.degrees ? x * 180 / .pi : x }
728
729        func call(_ name: String, _ args: [Value]) throws -> Value {
730            switch name {
731            case "vsum":
732                return try elements(args).reduce(Value.int(0)) { try add($0, $1) }
733            case "vprod":
734                return try elements(args).reduce(Value.int(1)) { try multiply($0, $1) }
735            case "vcount":
736                return .int(try elements(args).count)
737            case "vmean":
738                let items = try elements(args)
739                guard !items.isEmpty else { throw Unsupported("empty vector") }
740                return try divide(try items.reduce(Value.int(0)) { try add($0, $1) }, .int(items.count), fractions: modes.preferFractions)
741            case "vmax", "vmin":
742                let items = try elements(args)
743                guard var best = items.first else { throw Unsupported("empty vector") }
744                for item in items.dropFirst() {
745                    let order = try compare(item, best)
746                    if name == "vmax" ? order > 0 : order < 0 { best = item }
747                }
748                return best
749            case "vmedian":
750                var items = try elements(args)
751                guard !items.isEmpty else { throw Unsupported("empty vector") }
752                try items.sort { try compare($0, $1) < 0 }
753                if items.count % 2 == 1 { return items[items.count / 2] }
754                return try divide(try add(items[items.count / 2 - 1], items[items.count / 2]), .int(2))
755            case "if":
756                guard args.count == 3 else { throw Unsupported("arguments") }
757                return try truth(args[0]) ? args[1] : args[2]
758            case "vvar", "vpvar", "vsdev", "vpsdev":
759                let (squares, n) = try deviations(args)
760                let population = name == "vpvar" || name == "vpsdev"
761                if !population, n < 2 { throw Unsupported("variance of one value") }
762                let variance = try divide(squares, .int(population ? n : n - 1), fractions: modes.preferFractions)
763                return name.hasSuffix("sdev") ? try root(variance) : variance
764            case "mod":
765                guard args.count == 2 else { throw Unsupported("arguments") }
766                return try mod(args[0], args[1])
767            case "idiv":
768                guard args.count == 2 else { throw Unsupported("arguments") }
769                return try idiv(args[0], args[1])
770            case "fact":
771                guard case .int(let n) = try one(args), n >= 0 else { throw Unsupported("arguments") }
772                var result = Value.int(1)
773                for k in stride(from: 2, through: n, by: 1) { result = try multiply(result, .int(k)) }
774                return result
775            case "max", "min":
776                guard !args.isEmpty else { throw Unsupported("arguments") }
777                var best = args[0]
778                for item in args.dropFirst() {
779                    let order = try compare(item, best)
780                    if name == "max" ? order > 0 : order < 0 { best = item }
781                }
782                return best
783            case "abs":
784                let x = try one(args)
785                return try compare(x, .int(0)) < 0 ? try negate(x) : x
786            case "floor", "ceil", "trunc":
787                var x = try one(args)
788                if case .frac(let n, let d) = x { x = try divide(.int(n), .int(d)) }
789                if case .int = x { return x }
790                let d = try double(x)
791                let r = name == "floor" ? d.rounded(.down) : name == "ceil" ? d.rounded(.up) : d.rounded(.towardZero)
792                guard let i = Int(exactly: r) else { throw Unsupported("integer size") }
793                return .int(i)
794            case "round":
795                guard args.count == 1 || args.count == 2 else { throw Unsupported("arguments") }
796                var x = args[0]
797                if case .frac(let n, let d) = x { x = try divide(.int(n), .int(d)) }
798                if args.count == 2 {
799                    guard case .int(let places) = args[1] else { throw Unsupported("arguments") }
800                    guard case .float(let m, let e) = x else { return x }
801                    return try makeFloat(try scaleRounding(Int128(m), e + places), -places)
802                }
803                guard case .float(let m, let e) = x else { return x }
804                guard let i = Int(exactly: try scaleRounding(Int128(m), e)) else { throw Unsupported("integer size") }
805                return .int(i)
806            case "sqrt":
807                let x = try one(args)
808                func exactRoot(_ i: Int) -> Int? {
809                    guard i >= 0 else { return nil }
810                    let root = Int(Double(i).squareRoot().rounded())
811                    return root.multipliedReportingOverflow(by: root) == (i, false) ? root : nil
812                }
813                if case .int(let i) = x {
814                    guard i >= 0 else { throw Unsupported("complex result") }
815                    if let root = exactRoot(i) { return .int(root) }
816                }
817                if case .frac(let n, let d) = x, let rn = exactRoot(n), let rd = exactRoot(d) { return try fraction(rn, rd) }
818                let d = try double(x)
819                guard d >= 0 else { throw Unsupported("complex result") }
820                return try fromDouble(d.squareRoot())
821            case "exp":
822                let x = try one(args)
823                if x == .int(0) { return .int(1) }
824                return try fromDouble(Foundation.exp(try double(x)))
825            case "ln":
826                let x = try one(args)
827                if x == .int(1) { return .int(0) }
828                let d = try double(x)
829                guard d > 0 else { throw Unsupported("logarithm domain") }
830                return try fromDouble(Foundation.log(d))
831            case "log10":
832                let x = try one(args)
833                if case .int(let i) = x, i > 0 {
834                    var n = i
835                    var k = 0
836                    while n % 10 == 0 {
837                        n /= 10
838                        k += 1
839                    }
840                    if n == 1 { return .int(k) }
841                }
842                let d = try double(x)
843                guard d > 0 else { throw Unsupported("logarithm domain") }
844                return try fromDouble(Foundation.log10(d))
845            case "sin", "cos", "tan":
846                let x = try one(args)
847                if modes.degrees, case .int(let i) = x, i % 90 == 0 {
848                    let quarter = ((i / 90) % 4 + 4) % 4
849                    switch name {
850                    case "sin": return .int([0, 1, 0, -1][quarter])
851                    case "cos": return .int([1, 0, -1, 0][quarter])
852                    default:
853                        guard quarter % 2 == 0 else { throw Unsupported("infinite result") }
854                        return .int(0)
855                    }
856                }
857                if !modes.degrees, x == .int(0) { return .int(name == "cos" ? 1 : 0) }
858                let r = radians(try double(x))
859                return try fromDouble(name == "sin" ? Foundation.sin(r) : name == "cos" ? Foundation.cos(r) : Foundation.tan(r))
860            case "arcsin", "arccos", "arctan":
861                let x = try one(args)
862                if modes.degrees, case .int(let i) = x {
863                    switch (name, i) {
864                    case ("arcsin", -1), ("arcsin", 0), ("arcsin", 1): return .int(90 * i)
865                    case ("arccos", -1), ("arccos", 0), ("arccos", 1): return .int(90 - 90 * i)
866                    case ("arctan", -1), ("arctan", 0), ("arctan", 1): return .int(45 * i)
867                    default: break
868                    }
869                }
870                let d = try double(x)
871                if name != "arctan", abs(d) > 1 { throw Unsupported("complex result") }
872                let r = name == "arcsin" ? Foundation.asin(d) : name == "arccos" ? Foundation.acos(d) : Foundation.atan(d)
873                return try fromDouble(angle(r))
874            default:
875                throw Unsupported("function \(name)")
876            }
877        }
878    }
879}