1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
//! Prolog results.
//!
//! Functions in swipl-rs that interact with SWI-Prolog generally
//! return a [PrologResult]. This allows you to use them with the `?`
//! syntax in situations where you need to call multiple such
//! functions, and each failure or exception is a reason to exit
//! early.
//!
//! This module also provides some transformations on prolog results.
use thiserror::Error;

use crate::context::{Context, QueryableContextType};

/// A prolog error.
///
/// This is either a failure or an exception. In case of an exception,
/// whowever returned the exception was also supposed to raise an
/// exception on the context.
#[derive(Error, Debug, PartialEq, Eq)]
pub enum PrologError {
    #[error("prolog function failed")]
    Failure,
    #[error("prolog function threw an exception")]
    Exception,
}

impl PrologError {
    /// Returns true if this error is a failure.
    pub fn is_failure(&self) -> bool {
        matches!(self, PrologError::Failure)
    }

    /// Returns true if this error is an exception.
    pub fn is_exception(&self) -> bool {
        matches!(self, PrologError::Exception)
    }
}

/// Unit type for errors which can only be an exception.
#[derive(Debug)]
pub struct PrologException;

impl From<PrologException> for PrologError {
    fn from(_val: PrologException) -> PrologError {
        PrologError::Exception
    }
}

/// The main result type that most interface functions return.
pub type PrologResult<R> = Result<R, PrologError>;
/// Result type for operations that cannot fail, but can throw an exception.
pub type NonFailingPrologResult<R> = Result<R, PrologException>;
/// Result type for expressing failure as a boolean instead of an Err.
pub type BoolPrologResult = Result<bool, PrologException>;
/// result type for expressing failure as an Option type instead of an Err.
pub type OptPrologResult<R> = Result<Option<R>, PrologException>;

/// Transforms a `PrologResult<()>` into a [BoolPrologResult], allowing more easy use from an if block.
///
/// Example:
/// ```
///# use swipl::prelude::*;
///# fn main() -> PrologResult<()> {
///#    let engine = Engine::new();
///#    let activation = engine.activate();
///#    let context: Context<_> = activation.into();
///#
///#    let term = context.new_term_ref();
///     if attempt(term.unify(42_u64))? {
///         // the unification succeeded
///     } else {
///         // the unification failed
///     }
///#    Ok(())
///# }
/// ```
pub fn attempt(r: PrologResult<()>) -> BoolPrologResult {
    match r {
        Ok(()) => Ok(true),
        Err(PrologError::Failure) => Ok(false),
        Err(PrologError::Exception) => Err(PrologException),
    }
}

/// Transforms a [PrologResult] into an [OptPrologResult], allowing more easy use from an if block.
///
/// Example:
/// ```
///# use swipl::prelude::*;
///# fn main() -> PrologResult<()> {
///#    let engine = Engine::new();
///#    let activation = engine.activate();
///#    let context: Context<_> = activation.into();
///#
///#    let term = context.new_term_ref();
///     if let Some(num) = attempt_opt(term.get::<u64>())? {
///         // term contained an u64
///     } else {
///         // term did not contain an u64
///     }
///#    Ok(())
///# }
/// ```
pub fn attempt_opt<R>(r: PrologResult<R>) -> OptPrologResult<R> {
    match r {
        Ok(r) => Ok(Some(r)),
        Err(PrologError::Failure) => Ok(None),
        Err(PrologError::Exception) => Err(PrologException),
    }
}

/// Turn a boolean into a prolog result.
///
/// True will become `Ok(())`, and false will become
/// `Err(PrologError::Failure)`.
pub fn into_prolog_result(b: bool) -> PrologResult<()> {
    match b {
        true => Ok(()),
        false => Err(PrologError::Failure),
    }
}

/// Return a failure.
///
/// This is a shorthand for `Err(PrologError::Failure)`.
pub fn fail() -> PrologResult<()> {
    Err(PrologError::Failure)
}

pub enum PrologStringError {
    Failure,
    Exception(String),
}

pub type PrologStringResult<T> = Result<T, PrologStringError>;

pub fn result_to_string_result<C: QueryableContextType, T>(
    c: &Context<C>,
    r: PrologResult<T>,
) -> PrologStringResult<T> {
    match r {
        Ok(r) => Ok(r),
        Err(PrologError::Failure) => Err(PrologStringError::Failure),
        Err(PrologError::Exception) => {
            let r = c.with_exception(|e| {
                let e = e.expect("prolog exception but no exception in prolog engine");
                c.string_from_term(e)
            });

            c.clear_exception();

            match r {
                Ok(s) => Err(PrologStringError::Exception(format!(
                    "prolog had the following exception: {}",
                    s
                ))),
                Err(PrologError::Failure) => Err(PrologStringError::Exception(
                    "prolog failed while retrieving string from previous error".to_string(),
                )),
                Err(PrologError::Exception) => Err(PrologStringError::Exception(
                    "prolog threw exception while retrieving string from previous error"
                        .to_string(),
                )),
            }
        }
    }
}

pub fn unwrap_result<C: QueryableContextType, T>(c: &Context<C>, r: PrologResult<T>) -> T {
    match result_to_string_result(c, r) {
        Ok(r) => r,
        Err(PrologStringError::Failure) => panic!("prolog failed"),
        Err(PrologStringError::Exception(s)) => panic!("{}", s),
    }
}