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.
This commit is contained in:
62
calcpad-macos/Sources/CalcPad/Engine/CalculationEngine.swift
Normal file
62
calcpad-macos/Sources/CalcPad/Engine/CalculationEngine.swift
Normal file
@@ -0,0 +1,62 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user