Переглянути джерело

Add support for user errors

David Peter 2 роки тому
батько
коміт
c1ebdc8d29

+ 3 - 0
numbat/modules/core/error.nbt

@@ -0,0 +1,3 @@
+# TODO: Ideally, this function should have a '-> !' return type such that
+# it can be used everywhere. Not just instead of a Scalar.
+fn error(message: str) -> 1

+ 1 - 0
numbat/modules/prelude.nbt

@@ -2,6 +2,7 @@ use core::scalar
 use core::quantities
 use core::dimensions
 use core::strings
+use core::error
 
 use math::constants
 use math::functions

+ 97 - 67
numbat/src/ffi.rs

@@ -13,7 +13,9 @@ type ControlFlow = std::ops::ControlFlow<RuntimeError>;
 
 pub(crate) type ArityRange = std::ops::RangeInclusive<usize>;
 
-type BoxedFunction = Box<dyn Fn(&[Value]) -> Value + Send + Sync>;
+type Result<T> = std::result::Result<T, RuntimeError>;
+
+type BoxedFunction = Box<dyn Fn(&[Value]) -> Result<Value> + Send + Sync>;
 
 pub(crate) enum Callable {
     Function(BoxedFunction),
@@ -67,6 +69,14 @@ pub(crate) fn functions() -> &'static HashMap<String, ForeignFunction> {
     FFI_FUNCTIONS.get_or_init(|| {
         let mut m = HashMap::new();
 
+        m.insert(
+            "error".to_string(),
+            ForeignFunction {
+                name: "error".into(),
+                arity: 1..=1,
+                callable: Callable::Function(Box::new(error)),
+            },
+        );
         m.insert(
             "unit_of".to_string(),
             ForeignFunction {
@@ -386,106 +396,124 @@ fn assert_eq(_: &mut ExecutionContext, args: &[Value]) -> ControlFlow {
     }
 }
 
-fn unit_of(args: &[Value]) -> Value {
+fn error(args: &[Value]) -> Result<Value> {
+    assert!(args.len() == 1);
+
+    Err(RuntimeError::UserError(args[0].unsafe_as_string().into()))
+}
+
+fn unit_of(args: &[Value]) -> Result<Value> {
     assert!(args.len() == 1);
 
-    Value::Quantity(Quantity::new_f64(
+    Ok(Value::Quantity(Quantity::new_f64(
         1.0,
         args[0].unsafe_as_quantity().unit().clone(),
-    ))
+    )))
 }
 
-fn abs(args: &[Value]) -> Value {
+fn abs(args: &[Value]) -> Result<Value> {
     assert!(args.len() == 1);
 
     let arg = args[0].unsafe_as_quantity();
 
     let value = arg.unsafe_value().to_f64();
-    Value::Quantity(Quantity::new_f64(value.abs(), arg.unit().clone()))
+    Ok(Value::Quantity(Quantity::new_f64(
+        value.abs(),
+        arg.unit().clone(),
+    )))
 }
 
-fn round(args: &[Value]) -> Value {
+fn round(args: &[Value]) -> Result<Value> {
     assert!(args.len() == 1);
 
     let arg = args[0].unsafe_as_quantity();
 
     let value = arg.unsafe_value().to_f64();
-    Value::Quantity(Quantity::new_f64(value.round(), arg.unit().clone()))
+    Ok(Value::Quantity(Quantity::new_f64(
+        value.round(),
+        arg.unit().clone(),
+    )))
 }
 
-fn floor(args: &[Value]) -> Value {
+fn floor(args: &[Value]) -> Result<Value> {
     assert!(args.len() == 1);
 
     let arg = args[0].unsafe_as_quantity();
 
     let value = arg.unsafe_value().to_f64();
-    Value::Quantity(Quantity::new_f64(value.floor(), arg.unit().clone()))
+    Ok(Value::Quantity(Quantity::new_f64(
+        value.floor(),
+        arg.unit().clone(),
+    )))
 }
 
-fn ceil(args: &[Value]) -> Value {
+fn ceil(args: &[Value]) -> Result<Value> {
     assert!(args.len() == 1);
 
     let arg = args[0].unsafe_as_quantity();
 
     let value = arg.unsafe_value().to_f64();
-    Value::Quantity(Quantity::new_f64(value.ceil(), arg.unit().clone()))
+    Ok(Value::Quantity(Quantity::new_f64(
+        value.ceil(),
+        arg.unit().clone(),
+    )))
 }
 
-fn sin(args: &[Value]) -> Value {
+fn sin(args: &[Value]) -> Result<Value> {
     assert!(args.len() == 1);
 
     let arg = args[0].unsafe_as_quantity();
 
     let input = arg.as_scalar().unwrap().to_f64();
-    Value::Quantity(Quantity::from_scalar(input.sin()))
+    Ok(Value::Quantity(Quantity::from_scalar(input.sin())))
 }
 
-fn cos(args: &[Value]) -> Value {
+fn cos(args: &[Value]) -> Result<Value> {
     assert!(args.len() == 1);
 
     let arg = args[0].unsafe_as_quantity();
 
     let input = arg.as_scalar().unwrap().to_f64();
-    Value::Quantity(Quantity::from_scalar(input.cos()))
+    Ok(Value::Quantity(Quantity::from_scalar(input.cos())))
 }
 
-fn tan(args: &[Value]) -> Value {
+fn tan(args: &[Value]) -> Result<Value> {
     assert!(args.len() == 1);
 
     let arg = args[0].unsafe_as_quantity();
 
     let input = arg.as_scalar().unwrap().to_f64();
-    Value::Quantity(Quantity::from_scalar(input.tan()))
+    Ok(Value::Quantity(Quantity::from_scalar(input.tan())))
 }
 
-fn asin(args: &[Value]) -> Value {
+fn asin(args: &[Value]) -> Result<Value> {
     assert!(args.len() == 1);
 
     let arg = args[0].unsafe_as_quantity();
 
     let input = arg.as_scalar().unwrap().to_f64();
-    Value::Quantity(Quantity::from_scalar(input.asin()))
+    Ok(Value::Quantity(Quantity::from_scalar(input.asin())))
 }
 
-fn acos(args: &[Value]) -> Value {
+fn acos(args: &[Value]) -> Result<Value> {
     assert!(args.len() == 1);
 
     let arg = args[0].unsafe_as_quantity();
 
     let input = arg.as_scalar().unwrap().to_f64();
-    Value::Quantity(Quantity::from_scalar(input.acos()))
+    Ok(Value::Quantity(Quantity::from_scalar(input.acos())))
 }
 
-fn atan(args: &[Value]) -> Value {
+fn atan(args: &[Value]) -> Result<Value> {
     assert!(args.len() == 1);
 
     let arg = args[0].unsafe_as_quantity();
 
     let input = arg.as_scalar().unwrap().to_f64();
-    Value::Quantity(Quantity::from_scalar(input.atan()))
+    Ok(Value::Quantity(Quantity::from_scalar(input.atan())))
 }
 
-fn atan2(args: &[Value]) -> Value {
+fn atan2(args: &[Value]) -> Result<Value> {
     assert!(args.len() == 2);
 
     let y = args[0].unsafe_as_quantity();
@@ -493,64 +521,64 @@ fn atan2(args: &[Value]) -> Value {
 
     let input0 = y.unsafe_value().to_f64();
     let input1 = x.convert_to(y.unit()).unwrap().unsafe_value().to_f64();
-    Value::Quantity(Quantity::from_scalar(input0.atan2(input1)))
+    Ok(Value::Quantity(Quantity::from_scalar(input0.atan2(input1))))
 }
 
-fn sinh(args: &[Value]) -> Value {
+fn sinh(args: &[Value]) -> Result<Value> {
     assert!(args.len() == 1);
 
     let arg = args[0].unsafe_as_quantity();
 
     let input = arg.as_scalar().unwrap().to_f64();
-    Value::Quantity(Quantity::from_scalar(input.sinh()))
+    Ok(Value::Quantity(Quantity::from_scalar(input.sinh())))
 }
 
-fn cosh(args: &[Value]) -> Value {
+fn cosh(args: &[Value]) -> Result<Value> {
     assert!(args.len() == 1);
 
     let arg = args[0].unsafe_as_quantity();
 
     let input = arg.as_scalar().unwrap().to_f64();
-    Value::Quantity(Quantity::from_scalar(input.cosh()))
+    Ok(Value::Quantity(Quantity::from_scalar(input.cosh())))
 }
 
-fn tanh(args: &[Value]) -> Value {
+fn tanh(args: &[Value]) -> Result<Value> {
     assert!(args.len() == 1);
 
     let arg = args[0].unsafe_as_quantity();
 
     let input = arg.as_scalar().unwrap().to_f64();
-    Value::Quantity(Quantity::from_scalar(input.tanh()))
+    Ok(Value::Quantity(Quantity::from_scalar(input.tanh())))
 }
 
-fn asinh(args: &[Value]) -> Value {
+fn asinh(args: &[Value]) -> Result<Value> {
     assert!(args.len() == 1);
 
     let arg = args[0].unsafe_as_quantity();
 
     let input = arg.as_scalar().unwrap().to_f64();
-    Value::Quantity(Quantity::from_scalar(input.asinh()))
+    Ok(Value::Quantity(Quantity::from_scalar(input.asinh())))
 }
 
-fn acosh(args: &[Value]) -> Value {
+fn acosh(args: &[Value]) -> Result<Value> {
     assert!(args.len() == 1);
 
     let arg = args[0].unsafe_as_quantity();
 
     let input = arg.as_scalar().unwrap().to_f64();
-    Value::Quantity(Quantity::from_scalar(input.acosh()))
+    Ok(Value::Quantity(Quantity::from_scalar(input.acosh())))
 }
 
-fn atanh(args: &[Value]) -> Value {
+fn atanh(args: &[Value]) -> Result<Value> {
     assert!(args.len() == 1);
 
     let arg = args[0].unsafe_as_quantity();
 
     let input = arg.as_scalar().unwrap().to_f64();
-    Value::Quantity(Quantity::from_scalar(input.atanh()))
+    Ok(Value::Quantity(Quantity::from_scalar(input.atanh())))
 }
 
-fn mod_(args: &[Value]) -> Value {
+fn mod_(args: &[Value]) -> Result<Value> {
     assert!(args.len() == 2);
 
     let x = args[0].unsafe_as_quantity();
@@ -558,62 +586,64 @@ fn mod_(args: &[Value]) -> Value {
 
     let input0 = x.unsafe_value().to_f64();
     let input1 = y.convert_to(x.unit()).unwrap().unsafe_value().to_f64();
-    Value::Quantity(Quantity::new_f64(
+    Ok(Value::Quantity(Quantity::new_f64(
         input0.rem_euclid(input1),
         x.unit().clone(),
-    ))
+    )))
 }
 
-fn exp(args: &[Value]) -> Value {
+fn exp(args: &[Value]) -> Result<Value> {
     assert!(args.len() == 1);
 
     let arg = args[0].unsafe_as_quantity();
 
     let input = arg.as_scalar().unwrap().to_f64();
-    Value::Quantity(Quantity::from_scalar(input.exp()))
+    Ok(Value::Quantity(Quantity::from_scalar(input.exp())))
 }
 
-fn ln(args: &[Value]) -> Value {
+fn ln(args: &[Value]) -> Result<Value> {
     assert!(args.len() == 1);
 
     let arg = args[0].unsafe_as_quantity();
 
     let input = arg.as_scalar().unwrap().to_f64();
-    Value::Quantity(Quantity::from_scalar(input.ln()))
+    Ok(Value::Quantity(Quantity::from_scalar(input.ln())))
 }
 
-fn log10(args: &[Value]) -> Value {
+fn log10(args: &[Value]) -> Result<Value> {
     assert!(args.len() == 1);
 
     let arg = args[0].unsafe_as_quantity();
 
     let input = arg.as_scalar().unwrap().to_f64();
-    Value::Quantity(Quantity::from_scalar(input.log10()))
+    Ok(Value::Quantity(Quantity::from_scalar(input.log10())))
 }
 
-fn log2(args: &[Value]) -> Value {
+fn log2(args: &[Value]) -> Result<Value> {
     assert!(args.len() == 1);
 
     let arg = args[0].unsafe_as_quantity();
 
     let input = arg.as_scalar().unwrap().to_f64();
-    Value::Quantity(Quantity::from_scalar(input.log2()))
+    Ok(Value::Quantity(Quantity::from_scalar(input.log2())))
 }
 
-fn gamma(args: &[Value]) -> Value {
+fn gamma(args: &[Value]) -> Result<Value> {
     assert!(args.len() == 1);
 
     let arg = args[0].unsafe_as_quantity();
 
     let input = arg.as_scalar().unwrap().to_f64();
-    Value::Quantity(Quantity::from_scalar(crate::gamma::gamma(input)))
+    Ok(Value::Quantity(Quantity::from_scalar(crate::gamma::gamma(
+        input,
+    ))))
 }
 
-fn mean(args: &[Value]) -> Value {
+fn mean(args: &[Value]) -> Result<Value> {
     assert!(!args.is_empty());
 
     let output_unit = args[0].unsafe_as_quantity().unit();
-    Value::Quantity(Quantity::new_f64(
+    Ok(Value::Quantity(Quantity::new_f64(
         args.iter()
             .map(|q| {
                 q.unsafe_as_quantity()
@@ -625,14 +655,14 @@ fn mean(args: &[Value]) -> Value {
             .sum::<f64>()
             / (args.len() as f64),
         output_unit.clone(),
-    ))
+    )))
 }
 
-fn maximum(args: &[Value]) -> Value {
+fn maximum(args: &[Value]) -> Result<Value> {
     assert!(!args.is_empty());
 
     let output_unit = args[0].unsafe_as_quantity().unit();
-    Value::Quantity(Quantity::new(
+    Ok(Value::Quantity(Quantity::new(
         args.iter()
             .map(|q| {
                 *q.unsafe_as_quantity()
@@ -643,14 +673,14 @@ fn maximum(args: &[Value]) -> Value {
             .max_by(|l, r| l.partial_cmp(r).unwrap())
             .unwrap(),
         output_unit.clone(),
-    ))
+    )))
 }
 
-fn minimum(args: &[Value]) -> Value {
+fn minimum(args: &[Value]) -> Result<Value> {
     assert!(!args.is_empty());
 
     let output_unit = args[0].unsafe_as_quantity().unit();
-    Value::Quantity(Quantity::new(
+    Ok(Value::Quantity(Quantity::new(
         args.iter()
             .map(|q| {
                 *q.unsafe_as_quantity()
@@ -661,26 +691,26 @@ fn minimum(args: &[Value]) -> Value {
             .min_by(|l, r| l.partial_cmp(r).unwrap())
             .unwrap(),
         output_unit.clone(),
-    ))
+    )))
 }
 
 fn exchange_rate(rate: &'static str) -> BoxedFunction {
-    Box::new(|_args: &[Value]| -> Value {
+    Box::new(|_args: &[Value]| -> Result<Value> {
         let exchange_rates = ExchangeRatesCache::new();
-        Value::Quantity(Quantity::from_scalar(
+        Ok(Value::Quantity(Quantity::from_scalar(
             exchange_rates.get_rate(rate).unwrap_or(f64::NAN),
-        ))
+        )))
     })
 }
 
-fn str_length(args: &[Value]) -> Value {
+fn str_length(args: &[Value]) -> Result<Value> {
     assert!(args.len() == 1);
 
     let len = args[0].unsafe_as_string().len();
-    Value::Quantity(Quantity::from_scalar(len as f64))
+    Ok(Value::Quantity(Quantity::from_scalar(len as f64)))
 }
 
-fn str_slice(args: &[Value]) -> Value {
+fn str_slice(args: &[Value]) -> Result<Value> {
     assert!(args.len() == 3);
 
     let input = args[0].unsafe_as_string();
@@ -689,5 +719,5 @@ fn str_slice(args: &[Value]) -> Value {
 
     let output = input.get(start..end).unwrap_or_default();
 
-    Value::String(output.into())
+    Ok(Value::String(output.into()))
 }

+ 2 - 0
numbat/src/interpreter.rs

@@ -31,6 +31,8 @@ pub enum RuntimeError {
     AssertEq3Failed(Quantity, Quantity, Quantity),
     #[error("Could not load exchange rates from European Central Bank.")]
     CouldNotLoadExchangeRates,
+    #[error("User error: {0}")]
+    UserError(String),
 }
 
 #[derive(Debug, PartialEq, Eq)]

+ 1 - 1
numbat/src/vm.rs

@@ -674,7 +674,7 @@ impl Vm {
                     match &self.ffi_callables[function_idx].callable {
                         Callable::Function(function) => {
                             let result = (function)(&args[..]);
-                            self.push(result);
+                            self.push(result?);
                         }
                         Callable::Procedure(procedure) => {
                             let result = (procedure)(ctx, &args[..]);