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.
63 lines
2.5 KiB
Swift
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)
|
|
}
|
|
}
|