sjfl/
lib.rs

1mod bsjfl;
2mod error;
3mod file;
4mod parse;
5mod session;
6mod statement;
7mod ty;
8mod value;
9
10#[cfg(test)]
11mod tests;
12
13pub use error::SJFLError;
14pub use session::Session;
15pub use ty::SJFLType;
16pub use value::{
17    Datetime,
18    InternedString,
19    List,
20    Table,
21    Value,
22};
23
24use bsjfl::decode_timestamp;
25use parse::{ Parse, skip_whitespaces_and_comments };
26use statement::Statement;
27use std::collections::HashMap;
28
29/// `s` shall not contain any statement. But it can use variables in the `session`.
30///
31/// The second field of the error variant is an error message.
32pub fn evaluate(s: &str, session: &mut Session) -> Result<Value, (SJFLError, String)> {
33    let mut index = 0;
34    let s = s.as_bytes();
35
36    index = skip_whitespaces_and_comments(s, index).map_err(|e| (e.clone(), e.to_string(session, s)))?;
37
38    let (mut v, _) = Value::parse(s, index, session).map_err(|e| (e.clone(), e.to_string(session, s)))?;
39    v.reduce_vars(session).map_err(|e| (e.clone(), e.to_string(session, s)))?;
40
41    Ok(v)
42}
43
44/// `file_name` should not contain a file extension.
45/// If `file_name` is `foo`, it looks for `foo.sjfl` and `foo.bsjfl` in the current directory.
46/// It tries to use the binary one if available. You cannot access files in another directory.
47///
48/// The file shall not contain any statement. Variables that are defined in `session` are ignored.
49/// That means the content of the file cannot use any variable, at all.
50///
51/// It would be useful if you want to parse a json file.
52///
53/// The second field of the error variant is an error message.
54pub fn evaluate_file(file_name: &str, session: &mut Session) -> Result<Value, (SJFLError, String)> {
55    let session_vars = session.vars.clone();
56    session.vars = HashMap::new();
57
58    let result = evaluate_file_(file_name, session);
59    session.vars = session_vars;
60
61    result
62}
63
64fn evaluate_file_(file_name: &str, session: &mut Session) -> Result<Value, (SJFLError, String)> {
65    let mut candidates = (None, None, None);  // (sjfl, json, bsjfl)
66
67    for file in session.files.iter() {
68        if file.get_file_name() == file_name {
69            if file.is_json {
70                candidates.1 = Some(file.clone());
71            }
72
73            else if file.is_binary {
74                candidates.2 = Some(file.clone());
75            }
76
77            else {
78                candidates.0 = Some(file.clone());
79            }
80        }
81    }
82
83    // (sjfl, json, bsjfl)
84    match candidates {
85        (None, None, None) => Err(
86            SJFLError::FileError {
87                path: Some(file_name.to_string()),
88                msg: format!("failed to evaluate `{file_name}`! make sure that it does not contain extension (eg: `foo`, not `foo.sjfl`), and the session is properly initialized (call `session.init_dir` or `Session::new_dir`)"),
89            }
90        ).map_err(|e| (e.clone(), e.to_string(session, &[]))),
91        (Some(f), None, None) | (None, Some(f), None) | (Some(f), Some(_), None) => match f.get_content() {
92            Ok(b) => {
93                let result = evaluate(&String::from_utf8_lossy(&b), session)?;
94                let new_binary_file = f.save_binary(result.clone(), session, f.timestamp);
95                session.files.push(new_binary_file);
96
97                Ok(result)
98            },
99            Err(e) => Err(SJFLError::FileError {
100                path: Some(f.path.clone()),
101                msg: format!("{e:?}"),
102            }).map_err(|e| (e.clone(), e.to_string(session, &[]))),
103        },
104        (None, None, Some(f)) => {
105            let mut cursor = 0;
106
107            match f.get_content() {
108                Ok(b) => Value::from_bin(&b, &mut cursor, session).map_err(|e| (e.clone(), e.to_string(session, &[]))),
109                Err(e) => Err(SJFLError::FileError {
110                    path: Some(f.path.clone()),
111                    msg: format!("{e:?}"),
112                }).map_err(|e| (e.clone(), e.to_string(session, &[]))),
113            }
114        },
115        (Some(f), None, Some(bf)) | (None, Some(f), Some(bf)) | (Some(f), Some(_), Some(bf)) => {
116            let (bf_content, has_to_read_raw_text) = if let Ok(bf_content) = bf.get_content() {
117                if let Ok(timestamp) = decode_timestamp(&bf_content, session) {
118                    if timestamp == f.timestamp {
119                        (bf_content, false)
120                    }
121
122                    else {
123                        (vec![], true)
124                    }
125                }
126
127                else {
128                    (vec![], true)
129                }
130            }
131
132            else {
133                (vec![], true)
134            };
135
136            if has_to_read_raw_text {
137                match f.get_content() {
138                    Ok(f_content) => {
139                        let result = evaluate(&String::from_utf8_lossy(&f_content), session)?;
140                        let new_binary_file = f.save_binary(result.clone(), session, f.timestamp);
141                        session.files.push(new_binary_file);
142    
143                        Ok(result)
144                    },
145                    Err(e) => Err(SJFLError::FileError {
146                        path: Some(f.path.clone()),
147                        msg: format!("Binary file is outdated, and raw-text file is broken: {e:?}"),
148                    }).map_err(|e| (e.clone(), e.to_string(session, &[]))),
149                }
150            }
151
152            else {
153                let mut cursor = 0;
154
155                Value::from_bin(&bf_content, &mut cursor, session).map_err(|e| (e.clone(), e.to_string(session, &[])))
156            }
157        },
158    }
159}
160
161/// Instead of returning the result, it mutates the `session`. It only returns `Err` when it fails.
162///
163/// The second field of the error variant is an error message.
164pub fn execute(s: &str, session: &mut Session) -> Result<(), (SJFLError, String)> {
165    let mut index = 0;
166    let s = s.as_bytes();
167
168    loop {
169        index = match skip_whitespaces_and_comments(s, index) {
170            Err(SJFLError::UnexpectedEof(_)) => { return Ok(()); },  // it's not unexpected!
171            Err(e) => { return Err((e.clone(), e.to_string(session, s))); }
172            Ok(n) => n
173        };
174
175        let (st, new_index) = Statement::parse(s, index, session).map_err(|e| (e.clone(), e.to_string(session, s)))?;
176        index = new_index;
177
178        st.execute(session).map_err(|e| (e.clone(), e.to_string(session, s)))?;
179    }
180}
181
182/// The second field of the error variant is an error message.
183///
184/// TODO: this is not implemented yet.
185pub fn execute_file(file_name: &str, session: &mut Session) -> Result<(), (SJFLError, String)> {
186    todo!()
187}