Files
calctext/calcpad-macos/Sources/CalcPad/Engine/CalculationEngine.swift
C. Cassel 806e2f1ec6 feat: add platform shells, CLI, formatting, plugins, tests, and benchmarks
Phase 4 — Platform shells:
- calcpad-macos/: SwiftUI two-column editor with Rust FFI bridge (16 files)
- calcpad-windows/: iced GUI with Windows 11 Fluent theme (7 files, 13 tests)
- calcpad-web/: React 18 + CodeMirror 6 + WASM Worker + PWA (20 files)
- calcpad-cli/: clap-based CLI with expression eval, pipe/stdin, JSON/CSV
  output, and interactive REPL with rustyline history

Phase 5 — Engine modules:
- formatting/: answer formatting (decimal/scientific/SI notation, thousands
  separators, currency), line type classification, clipboard values (93 tests)
- plugins/: CalcPadPlugin trait, PluginRegistry, Rhai scripting stub (43 tests)
- benches/: criterion benchmarks (single-line, 100/500-line sheets, DAG, incremental)
- tests/sheet_scenarios.rs: 20 real-world integration tests
- tests/proptest_fuzz.rs: 12 property-based fuzz tests

771 tests passing across workspace, 0 failures.
2026-03-17 09:46:40 -04:00

63 lines
2.5 KiB
Swift

import Foundation
/// Protocol for a calculation engine that evaluates text expressions.
/// The primary implementation is RustCalculationEngine (FFI bridge to calcpad-engine).
/// StubCalculationEngine is provided for testing without the Rust library.
protocol CalculationEngine: Sendable {
/// Evaluate a single line expression and return the result string, or nil for blank/comment lines.
func evaluateLine(_ line: String) -> LineResult
/// Evaluate an entire sheet (multi-line text) and return results for each line.
func evaluateSheet(_ text: String) -> [LineResult]
}
/// Stub engine for testing that handles basic arithmetic (+, -, *, /).
/// Used when the Rust engine library is not available.
final class StubCalculationEngine: CalculationEngine {
func evaluateLine(_ line: String) -> LineResult {
evaluateSingleLine(line, lineNumber: 1)
}
func evaluateSheet(_ text: String) -> [LineResult] {
let lines = text.components(separatedBy: "\n")
return lines.enumerated().map { index, line in
evaluateSingleLine(line, lineNumber: index + 1)
}
}
private func evaluateSingleLine(_ line: String, lineNumber: Int) -> LineResult {
let trimmed = line.trimmingCharacters(in: .whitespaces)
// Blank lines produce no result
guard !trimmed.isEmpty else {
return LineResult(id: lineNumber, expression: line, result: nil, isError: false)
}
// Comment lines (starting with // or #) produce no result
if trimmed.hasPrefix("//") || trimmed.hasPrefix("#") {
return LineResult(id: lineNumber, expression: line, result: nil, isError: false)
}
// Convert integer literals to floating-point so division produces decimals.
// NSExpression does integer division for "7 / 2" -> 3, but we want 3.5.
let floatExpr = trimmed.replacingOccurrences(
of: #"\b(\d+)\b"#,
with: "$1.0",
options: .regularExpression
)
// Try to evaluate as a basic arithmetic expression
do {
let expr = NSExpression(format: floatExpr)
if let value = expr.expressionValue(with: nil, context: nil) as? NSNumber {
let doubleValue = value.doubleValue
let formatted = String(format: "%g", doubleValue)
return LineResult(id: lineNumber, expression: line, result: formatted, isError: false)
}
}
return LineResult(id: lineNumber, expression: line, result: "Error", isError: true)
}
}