diff --git a/crates/goth-cli/src/main.rs b/crates/goth-cli/src/main.rs index f09de82..d09be46 100644 --- a/crates/goth-cli/src/main.rs +++ b/crates/goth-cli/src/main.rs @@ -128,7 +128,13 @@ fn main() { if let Some(expr) = args.eval { // Evaluate expression from command line - run_expr(&expr, args.trace, args.parse_only, args.ast, args.check); + // Collect extra positional args as expression arguments + let mut expr_args: Vec = Vec::new(); + if let Some(ref file) = args.file { + expr_args.push(file.to_string_lossy().to_string()); + } + expr_args.extend(args.program_args.iter().cloned()); + run_expr(&expr, args.trace, args.parse_only, args.ast, args.check, &expr_args); return; } else if let Some(file) = args.file { // Run file @@ -141,8 +147,9 @@ fn main() { } } -fn run_expr(source: &str, trace: bool, parse_only: bool, show_ast: bool, check: bool) { +fn run_expr(source: &str, trace: bool, parse_only: bool, show_ast: bool, check: bool, program_args: &[String]) { use colored::Colorize; + use goth_ast::expr::Expr; // Parse let parsed = match parse_expr(source) { @@ -162,10 +169,29 @@ fn run_expr(source: &str, trace: bool, parse_only: bool, show_ast: bool, check: // Resolve let resolved = resolve_expr(parsed); + // Wrap expression as a lambda applied to CLI args (if any) + let final_expr = if program_args.is_empty() { + resolved + } else { + // Build: ((λ→ ... (λ→ body)) arg0 arg1 ...) + // Wrap the expression in N nested lambdas, then apply args + let arity = program_args.len(); + let mut lambda_expr = resolved; + for _ in 0..arity { + lambda_expr = Expr::Lam(Box::new(lambda_expr)); + } + // Apply each argument + for arg in program_args { + let arg_expr = parse_arg_to_expr(arg); + lambda_expr = Expr::app(lambda_expr, arg_expr); + } + lambda_expr + }; + // Type check if --check flag is set if check { let mut type_checker = TypeChecker::new(); - match type_checker.infer(&resolved) { + match type_checker.infer(&final_expr) { Ok(ty) => { println!("{}: {}", "Type".cyan(), ty); } @@ -187,7 +213,7 @@ fn run_expr(source: &str, trace: bool, parse_only: bool, show_ast: bool, check: Evaluator::new() }; - match evaluator.eval(&resolved) { + match evaluator.eval(&final_expr) { Ok(value) => { println!("{}", value); } @@ -354,11 +380,32 @@ fn run_module_with_main(module: &goth_ast::decl::Module, trace: bool, program_ar } } +/// Try to parse a CLI argument as an uncertain value (e.g. "1.0±0.05" or "1.0+-0.05") +fn try_parse_uncertain(arg: &str) -> Option { + use goth_ast::expr::Expr; + use goth_ast::literal::Literal; + + let (left, right) = arg.split_once('±') + .or_else(|| arg.split_once("+-"))?; + let val = left.parse::().ok()?; + let unc = right.parse::().ok()?; + Some(Expr::BinOp( + goth_ast::op::BinOp::PlusMinus, + Box::new(Expr::Lit(Literal::Float(val))), + Box::new(Expr::Lit(Literal::Float(unc))), + )) +} + /// Parse a CLI argument string into a Goth expression fn parse_arg_to_expr(arg: &str) -> goth_ast::expr::Expr { use goth_ast::expr::Expr; use goth_ast::literal::Literal; + // Try to parse as uncertain value (e.g. "1.0±0.05" or "1.0+-0.05") + if let Some(expr) = try_parse_uncertain(arg) { + return expr; + } + // Try to parse as integer first if let Ok(n) = arg.parse::() { return Expr::Lit(Literal::Int(n)); diff --git a/crates/goth-eval/src/prim.rs b/crates/goth-eval/src/prim.rs index 6cb787a..155e869 100644 --- a/crates/goth-eval/src/prim.rs +++ b/crates/goth-eval/src/prim.rs @@ -664,6 +664,12 @@ fn compare_lt(left: Value, right: Value) -> EvalResult { (Value::Int(a), Value::Float(b)) => Ok(Value::Bool((*a as f64) < b.0)), (Value::Float(a), Value::Int(b)) => Ok(Value::Bool(a.0 < *b as f64)), (Value::Char(a), Value::Char(b)) => Ok(Value::Bool(a < b)), + (Value::Uncertain { value: a, .. }, Value::Uncertain { value: b, .. }) => + compare_lt(*a.clone(), *b.clone()), + (Value::Uncertain { value: a, .. }, _) => + compare_lt(*a.clone(), right.clone()), + (_, Value::Uncertain { value: b, .. }) => + compare_lt(left.clone(), *b.clone()), _ => Err(EvalError::type_error_msg(format!("Cannot compare {} and {}", left.type_name(), right.type_name()))), } } diff --git a/examples/README.md b/examples/README.md index 6effdc0..eefd005 100644 --- a/examples/README.md +++ b/examples/README.md @@ -193,18 +193,18 @@ File and stream I/O using the `▷` (write) operator. ## uncertainty/ -First-class uncertain values with automatic error propagation. +First-class uncertain values with automatic error propagation. Uncertain values can be passed directly as CLI arguments using the `±` or `+-` notation (e.g. `"10.0±0.5"` or `"10.0+-0.5"`). | File | Description | Example | |------|-------------|---------| -| `measure.goth` | Create an uncertain value | `10.0 0.5 → 10 ± 0.5` | -| `add_uncertain.goth` | Addition with error propagation | `10 0.3 20 0.4 → 30 ± 0.5` | -| `mul_uncertain.goth` | Multiplication with relative error | `5 0.1 3 0.2 → 15 ± 1.04...` | -| `sqrt_uncertain.goth` | Square root with derivative propagation | `9 0.3 → 3 ± 0.05` | -| `sin_uncertain.goth` | Sine with derivative propagation | `1.0 0.1 → 0.841... ± 0.054...` | -| `chained_uncertain.goth` | Multi-step propagation: sin(√a + b) | `4 0.1 1 0.05 → ...` | - -**Demonstrates:** The `±` operator, automatic propagation through `+`, `-`, `×`, `/`, `√`, `sin`, `cos`, `exp`, `ln`, etc. +| `measure.goth` | Return an uncertain value | `"10.0±0.5" → 10 ± 0.5` | +| `add_uncertain.goth` | Addition with error propagation | `"10.0±0.3" "20.0±0.4" → 30 ± 0.5` | +| `mul_uncertain.goth` | Multiplication with relative error | `"5.0±0.1" "3.0±0.2" → 15 ± 1.04...` | +| `sqrt_uncertain.goth` | Square root with derivative propagation | `"9.0±0.3" → 3 ± 0.05` | +| `sin_uncertain.goth` | Sine with derivative propagation | `"1.0±0.1" → 0.841... ± 0.054...` | +| `chained_uncertain.goth` | Multi-step propagation: sin(√a + b) | `"4.0±0.1" "1.0±0.05" → ...` | + +**Demonstrates:** The `±` operator, automatic propagation through `+`, `-`, `×`, `/`, `√`, `sin`, `cos`, `exp`, `ln`, etc. Ordering comparisons (`<`, `>`, `≤`, `≥`) on uncertain values compare central values. --- diff --git a/examples/uncertainty/add_uncertain.goth b/examples/uncertainty/add_uncertain.goth index cd178d9..287a9db 100644 --- a/examples/uncertainty/add_uncertain.goth +++ b/examples/uncertainty/add_uncertain.goth @@ -1,8 +1,8 @@ # Add two uncertain values with error propagation -# Input: a da b db (value±uncertainty for each) +# Input: two uncertain values (value±uncertainty each) # Output: (a+b) ± √(da²+db²) -# De Bruijn: ₃ = a, ₂ = da, ₁ = b, ₀ = db +# De Bruijn: ₁ = first, ₀ = second # Postcondition: propagated uncertainty ≥ max(da, db) -╭─ main : F64 → F64 → F64 → F64 → (F64 ± F64) -╰─ (₃ ± ₂) + (₁ ± ₀) +╭─ main : (F64 ± F64) → (F64 ± F64) → (F64 ± F64) +╰─ ₁ + ₀ diff --git a/examples/uncertainty/chained_uncertain.goth b/examples/uncertainty/chained_uncertain.goth index a70ebe8..66bf1f7 100644 --- a/examples/uncertainty/chained_uncertain.goth +++ b/examples/uncertainty/chained_uncertain.goth @@ -1,9 +1,9 @@ # Chained uncertainty propagation through multiple operations -# Computes sin(√(a ± da) + (b ± db)) -# Input: a da b db +# Computes sin(√a + b) +# Input: two uncertain values # Output: result with fully propagated uncertainty -# De Bruijn: ₃ = a, ₂ = da, ₁ = b, ₀ = db +# De Bruijn: ₁ = first, ₀ = second # Property: uncertainty propagates through each step automatically -╭─ main : F64 → F64 → F64 → F64 → (F64 ± F64) -╰─ sin (√(₃ ± ₂) + (₁ ± ₀)) +╭─ main : (F64 ± F64) → (F64 ± F64) → (F64 ± F64) +╰─ sin (√₁ + ₀) diff --git a/examples/uncertainty/measure.goth b/examples/uncertainty/measure.goth index 20e69bb..c318ac8 100644 --- a/examples/uncertainty/measure.goth +++ b/examples/uncertainty/measure.goth @@ -1,10 +1,7 @@ -# Create an uncertain measurement value -# Input: value uncertainty +# Return an uncertain measurement value +# Input: value±uncertainty # Output: value ± uncertainty -# De Bruijn: ₁ = value, ₀ = uncertainty +# De Bruijn: ₀ = uncertain value -╭─ measure : F64 → F64 → (F64 ± F64) -╰─ ₁ ± ₀ - -╭─ main : F64 → F64 → (F64 ± F64) -╰─ measure ₁ ₀ +╭─ main : (F64 ± F64) → (F64 ± F64) +╰─ ₀ diff --git a/examples/uncertainty/mul_uncertain.goth b/examples/uncertainty/mul_uncertain.goth index cdb019d..194595c 100644 --- a/examples/uncertainty/mul_uncertain.goth +++ b/examples/uncertainty/mul_uncertain.goth @@ -1,8 +1,8 @@ # Multiply two uncertain values with relative error propagation -# Input: a da b db (value±uncertainty for each) +# Input: two uncertain values (value±uncertainty each) # Output: (a×b) ± |a×b|×√((da/a)²+(db/b)²) -# De Bruijn: ₃ = a, ₂ = da, ₁ = b, ₀ = db +# De Bruijn: ₁ = first, ₀ = second # Property: relative uncertainty adds in quadrature -╭─ main : F64 → F64 → F64 → F64 → (F64 ± F64) -╰─ (₃ ± ₂) × (₁ ± ₀) +╭─ main : (F64 ± F64) → (F64 ± F64) → (F64 ± F64) +╰─ ₁ × ₀ diff --git a/examples/uncertainty/sin_uncertain.goth b/examples/uncertainty/sin_uncertain.goth index 6d7fb5f..eb2b53e 100644 --- a/examples/uncertainty/sin_uncertain.goth +++ b/examples/uncertainty/sin_uncertain.goth @@ -1,8 +1,8 @@ # Sine of an uncertain value -# Input: x dx (value±uncertainty, x in radians) +# Input: uncertain value (value±uncertainty, x in radians) # Output: sin(x) ± |cos(x)|×dx -# De Bruijn: ₁ = x, ₀ = dx +# De Bruijn: ₀ = uncertain value # Property: derivative-based propagation δ(sin x) = |cos x| × δx -╭─ main : F64 → F64 → (F64 ± F64) -╰─ sin (₁ ± ₀) +╭─ main : (F64 ± F64) → (F64 ± F64) +╰─ sin ₀ diff --git a/examples/uncertainty/sqrt_uncertain.goth b/examples/uncertainty/sqrt_uncertain.goth index 6530bac..3e333ac 100644 --- a/examples/uncertainty/sqrt_uncertain.goth +++ b/examples/uncertainty/sqrt_uncertain.goth @@ -1,9 +1,9 @@ # Square root of an uncertain value -# Input: x dx (value±uncertainty) +# Input: uncertain value (value±uncertainty) # Output: √x ± dx/(2√x) -# De Bruijn: ₁ = x, ₀ = dx +# De Bruijn: ₀ = uncertain value # Precondition: x ≥ 0 # Property: derivative-based propagation δf = |f'(x)| × δx -╭─ main : F64 → F64 → (F64 ± F64) -╰─ √(₁ ± ₀) +╭─ main : (F64 ± F64) → (F64 ± F64) +╰─ √₀