1use 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}