rune/
lread.rs

1//! Loading elisp from files and strings.
2use crate::core::cons::Cons;
3use crate::core::env::{Env, sym};
4use crate::core::error::{Type, TypeError};
5use crate::core::gc::{Context, Rt, Rto};
6use crate::core::object::{
7    Function, Gc, LispString, NIL, Object, ObjectType, OptionalFlag, Symbol, TRUE, TagType,
8    WithLifetime,
9};
10use crate::reader;
11use crate::{interpreter, rooted_iter};
12use anyhow::{Context as _, anyhow};
13use anyhow::{Result, bail, ensure};
14use fallible_streaming_iterator::FallibleStreamingIterator;
15use rune_core::macros::{call, rebind, root};
16use rune_macros::defun;
17use std::fs;
18use std::path::{Path, PathBuf};
19
20fn check_lower_bounds(idx: Option<i64>, len: usize) -> Result<usize> {
21    let len = len as i64;
22    let idx = idx.unwrap_or(0);
23    ensure!(
24        -len <= idx && idx < len,
25        "start index of {idx} is out of bounds for string of length {len}"
26    );
27    let idx = if idx < 0 { len + idx } else { idx };
28    Ok(idx as usize)
29}
30
31fn check_upper_bounds(idx: Option<i64>, len: usize) -> Result<usize> {
32    let len = len as i64;
33    let idx = idx.unwrap_or(len);
34    ensure!(
35        -len <= idx && idx <= len,
36        "end index of {idx} is out of bounds for string of length {len}"
37    );
38    let idx = if idx < 0 { len + idx } else { idx };
39    Ok(idx as usize)
40}
41
42#[defun]
43pub(crate) fn read_from_string<'ob>(
44    string: &str,
45    start: Option<i64>,
46    end: Option<i64>,
47    cx: &'ob Context,
48) -> Result<Object<'ob>> {
49    let len = string.len();
50    let start = check_lower_bounds(start, len)?;
51    let end = check_upper_bounds(end, len)?;
52
53    let (obj, new_pos) = match reader::read(&string[start..end], cx) {
54        Ok((obj, pos)) => (obj, pos),
55        Err(mut e) => {
56            e.update_pos(start);
57            bail!(e);
58        }
59    };
60    Ok(Cons::new(obj, new_pos as i64, cx).into())
61}
62
63pub(crate) fn load_internal(contents: &str, cx: &mut Context, env: &mut Rt<Env>) -> Result<bool> {
64    let mut pos = 0;
65    let macroexpand: Option<Function> = None;
66    root!(macroexpand, cx);
67    if let Some(fun) = sym::INTERNAL_MACROEXPAND_FOR_LOAD.func(cx) {
68        macroexpand.set(Some(fun));
69    }
70    loop {
71        let (obj, new_pos) = match reader::read(&contents[pos..], cx) {
72            Ok((obj, pos)) => (obj, pos),
73            Err(reader::Error::EmptyStream) => return Ok(true),
74            Err(mut e) => {
75                e.update_pos(pos);
76                bail!(e);
77            }
78        };
79        if crate::debug::debug_enabled() {
80            let content = &contents[pos..(new_pos + pos)];
81            println!("-----READ START-----\n {content}");
82            println!("-----READ END-----");
83        }
84        root!(obj, cx);
85        let result = if let Some(fun) = macroexpand.as_ref() {
86            eager_expand(obj, fun, env, cx)
87        } else {
88            interpreter::eval(obj, None, env, cx)
89        };
90        if let Err(e) = result {
91            let content = &contents[pos..(new_pos + pos)];
92            println!("-----LOAD ERROR START-----\n {content}");
93            println!("-----LOAD ERROR END-----");
94            return Err(e);
95        }
96        assert_ne!(new_pos, 0);
97        pos += new_pos;
98    }
99}
100
101fn eager_expand<'ob>(
102    obj: &Rto<Object>,
103    macroexpand: &Rto<Function>,
104    env: &mut Rt<Env>,
105    cx: &'ob mut Context,
106) -> Result<Object<'ob>, anyhow::Error> {
107    let name = "internal-macroexpand-for-load";
108    let val = call!(macroexpand, obj, NIL; name, env, cx)?;
109    let val = rebind!(val, cx);
110    if let Ok((sym::PROGN, forms)) = val.as_cons_pair() {
111        root!(val, NIL, cx);
112        rooted_iter!(forms, forms.tag(), cx);
113        while let Some(form) = forms.next()? {
114            let result = eager_expand(form, macroexpand, env, cx)?;
115            val.set(result);
116        }
117        return Ok(val.bind(cx));
118    }
119    let result = call!(macroexpand, val, TRUE; name, env, cx)?;
120    root!(result, cx);
121    interpreter::eval(result, None, env, cx)
122}
123
124fn file_in_path(file: &str, path: &str) -> Option<PathBuf> {
125    let path = Path::new(path).join(file);
126    if path.exists() {
127        Some(path)
128    } else {
129        let with_ext = path.with_extension("el");
130        with_ext.exists().then_some(with_ext)
131    }
132}
133
134fn find_file_in_load_path(file: &str, cx: &Context, env: &Rt<Env>) -> Result<PathBuf> {
135    let load_path = env.vars.get(sym::LOAD_PATH).unwrap();
136    let paths = load_path.bind(cx).as_list().context("`load-path' was not a list")?;
137    let mut final_file = None;
138    for path in paths {
139        match path?.untag() {
140            ObjectType::String(path) => {
141                if let Some(x) = file_in_path(file, path) {
142                    final_file = Some(x);
143                    break;
144                }
145            }
146            x => {
147                return Err(TypeError::new(Type::String, x))
148                    .context("Found non-string in `load-path'");
149            }
150        }
151    }
152    final_file.ok_or_else(|| anyhow!("Unable to find file `{file}' in load-path"))
153}
154
155#[defun]
156pub(crate) fn load(
157    file: &Rto<Gc<&LispString>>,
158    noerror: OptionalFlag,
159    nomessage: OptionalFlag,
160    cx: &mut Context,
161    env: &mut Rt<Env>,
162) -> Result<bool> {
163    let noerror = noerror.is_some();
164    let nomessage = nomessage.is_some();
165    let file: &str = file.untag(cx);
166    let final_file = if Path::new(file).exists() {
167        PathBuf::from(file)
168    } else {
169        match find_file_in_load_path(file, cx, env) {
170            Ok(x) => x,
171            Err(e) => {
172                return if noerror { Ok(false) } else { Err(e) };
173            }
174        }
175    };
176
177    let filename = String::from(file);
178    if !nomessage {
179        println!("Loading {filename}...");
180    }
181    let new_load_file = cx.add(final_file.to_string_lossy().to_string());
182    let prev_load_file = match env.vars.get_mut(sym::LOAD_FILE_NAME) {
183        Some(val) => {
184            let prev = val.bind(cx);
185            val.set(new_load_file);
186            prev
187        }
188        None => NIL,
189    };
190    root!(prev_load_file, cx);
191    let result = match fs::read_to_string(&final_file)
192        .with_context(|| format!("Couldn't open file {:?}", final_file.as_os_str()))
193    {
194        Ok(content) => load_internal(&content, cx, env),
195        Err(e) => match noerror {
196            true => Ok(false),
197            false => Err(e),
198        },
199    };
200
201    if !nomessage && result.is_ok() {
202        println!("Loading {filename} Done");
203    }
204    env.vars.insert(sym::LOAD_FILE_NAME, &*prev_load_file);
205    result
206}
207
208#[defun]
209pub(crate) fn intern<'ob>(string: &str, cx: &'ob Context) -> Symbol<'ob> {
210    crate::core::env::intern(string, cx)
211}
212
213#[defun]
214pub(crate) fn intern_soft(string: Object, obarray: OptionalFlag) -> Result<Symbol> {
215    ensure!(obarray.is_none(), "intern-soft obarray not implemented");
216    match string.untag() {
217        ObjectType::Symbol(sym) => {
218            if sym.interned() {
219                Ok(sym)
220            } else {
221                Ok(sym::NIL)
222            }
223        }
224        ObjectType::String(string) => {
225            let map = crate::core::env::INTERNED_SYMBOLS.lock().unwrap();
226            match map.get(string) {
227                Some(sym) => Ok(unsafe { sym.with_lifetime() }),
228                None => Ok(sym::NIL),
229            }
230        }
231        x => Err(TypeError::new(Type::String, x).into()),
232    }
233}
234
235defsym!(INTERNAL_MACROEXPAND_FOR_LOAD);
236defvar!(LEXICAL_BINDING, true);
237defvar!(CURRENT_LOAD_LIST);
238defvar!(LOAD_HISTORY);
239defvar!(LOAD_PATH, list![format!("{}/lisp", env!("CARGO_MANIFEST_DIR"))]);
240defvar!(LOAD_FILE_NAME);
241defvar!(BYTE_BOOLEAN_VARS);
242defvar!(MACROEXP__DYNVARS);
243defvar!(AFTER_LOAD_ALIST);
244
245#[cfg(test)]
246mod test {
247
248    use super::*;
249    use crate::core::gc::RootSet;
250    use rune_core::macros::root;
251
252    #[test]
253    fn test_load() {
254        let roots = &RootSet::default();
255        let cx = &mut Context::new(roots);
256        sym::init_symbols();
257        root!(env, new(Env), cx);
258        load_internal("(setq foo 1) (setq bar 2) (setq baz 1.5)", cx, env).unwrap();
259
260        let obj = reader::read("(+ foo bar baz)", cx).unwrap().0;
261        root!(obj, cx);
262        let val = interpreter::eval(obj, None, env, cx).unwrap();
263        assert_eq!(val, 4.5);
264    }
265}