diff --git a/userspace/lib/lysp/src/vm/prelude/eval.rs b/userspace/lib/lysp/src/vm/prelude/eval.rs index cae0c668..b3517b98 100644 --- a/userspace/lib/lysp/src/vm/prelude/eval.rs +++ b/userspace/lib/lysp/src/vm/prelude/eval.rs @@ -6,7 +6,7 @@ use crate::{ vm::{ Value, env::Environment, - value::{IdentifierValue, NativeObject, convert::TryFromValue}, + value::{IdentifierValue, NativeObject, StringValue, convert::TryFromValue}, }, }; @@ -60,6 +60,30 @@ pub fn load(env: &Rc) { }; Ok(outcome) }); + env.defun_native( + "read-eval", + "Evaluates the given expression, passed as a string", + |vm, env, args| { + let (env, input) = match args { + [input] => (env.clone(), input), + [env, input] => (env.as_native()?, input), + _ => return Err(MachineError::InvalidArgumentCount), + }; + let input = StringValue::try_from_value(input)?; + let mut input = String::from(&*input); + input.push('\n'); + let outcome = + match vm.evaluate_str(Default::default(), Some("eval".into()), &env, &input) { + Ok(result) => Value::list_or_nil([Value::Identifier("ok".into()), result]), + Err(error) => Value::list_or_nil([ + Value::Identifier("err".into()), + Value::String(format!("{error}").into()), + ]), + }; + + Ok(outcome) + }, + ); env.defun_native("env/create", "Create a new environment", |_, env, args| { let parent = match args { [] => Some(env.clone()), diff --git a/userspace/lib/lysp/src/vm/value/convert.rs b/userspace/lib/lysp/src/vm/value/convert.rs index 09a15f89..297fc7ef 100644 --- a/userspace/lib/lysp/src/vm/value/convert.rs +++ b/userspace/lib/lysp/src/vm/value/convert.rs @@ -133,6 +133,12 @@ impl From for Value { } } +impl From for Value { + fn from(value: StringValue) -> Self { + Value::String(value) + } +} + impl TryFromValue<'_> for IdentifierValue { fn try_from_value(value: &'_ Value) -> Result { match value { diff --git a/userspace/tools/red/runtime/command.lysp b/userspace/tools/red/runtime/command.lysp index ba4e0cd0..f4da6efa 100644 --- a/userspace/tools/red/runtime/command.lysp +++ b/userspace/tools/red/runtime/command.lysp @@ -1,3 +1,17 @@ +;; TODO prelude +(defun string/join (xs) + (let (accumulator "") + (while (cons? xs) + (if accumulator + (setq accumulator (+ accumulator " " (car xs))) + (setq accumulator (car xs)) + ) + (setq xs (cdr xs)) + ) + accumulator + ) + ) + (declare-command "q" () (red/quit)) (declare-command "q!" () (red/quit #t)) (declare-command @@ -28,3 +42,37 @@ "source" (filename) (import filename) ) + +;; TODO only allow some keys to be set +;; TODO at least check that the key is defined +(declare-command + "set" (key &rest value) + (let* ( + key-symbol (symbol (+ "red/" key)) + expression (string/join value) + eval-result (if (nil? value) '(ok #t) (read-eval expression)) + ) + (cond + ((result/ok? eval-result) (set key-symbol (cadr eval-result))) + (&otherwise (red/message (+ "error: " (cadr eval-result)))) + ) + ) + ) +(declare-command + "unset" (key) + (let (key-symbol (symbol (+ "red/" key))) + (set key-symbol nil) + ) + ) +(declare-command + "eval" (&rest expressions) + (let* ( + expression (string/join expressions) + eval-result (read-eval expression) + ) + (cond + ((result/ok? eval-result) (red/status (+ "ok: " (cadr eval-result)))) + (&otherwise (red/message (+ "error: " (cadr eval-result)))) + ) + ) + ) diff --git a/userspace/tools/red/runtime/core.lysp b/userspace/tools/red/runtime/core.lysp index 053e22fd..9845ec55 100644 --- a/userspace/tools/red/runtime/core.lysp +++ b/userspace/tools/red/runtime/core.lysp @@ -9,16 +9,39 @@ ) (defmacro declare-command - (command args body-head &rest body-tail) - ;; TODO auto-rest? - `(setq - _red/command-table - (cons - (list ,command (lambda (,@args &rest _) ,body-head ,@body-tail)) - _red/command-table - ) + (command args &rest body) + (unless (cons? body) + (error "No body provided in declare-command")) + (unless (list? args) + (error "Argument list must either be a NIL or a list")) + (let* + ( + args-has-rest (find (lambda (x) (= x '&rest)) args) + lambda-args (if args-has-rest + `(,@args) + `(,@args &rest _) + ) ) + `(setq + _red/command-table + (cons + (list ,command (lambda ,lambda-args ,@body)) + _red/command-table + ) + ) + ) ) +;; (defmacro declare-command +;; (command args body-head &rest body-tail) +;; ;; TODO auto-rest? +;; `(setq +;; _red/command-table +;; (cons +;; (list ,command (lambda (,@args &rest _) ,body-head ,@body-tail)) +;; _red/command-table +;; ) +;; ) +;; ) (defun try-import (path) (when (fs/file? path) (import path) #t)) diff --git a/userspace/tools/red/src/main.rs b/userspace/tools/red/src/main.rs index a6ed89f7..afe423f9 100644 --- a/userspace/tools/red/src/main.rs +++ b/userspace/tools/red/src/main.rs @@ -6,7 +6,7 @@ use std::{ cell::RefCell, env, io::{self, IsTerminal}, - path::Path, + path::{Path, PathBuf}, rc::Rc, }; @@ -20,7 +20,7 @@ use lysp::{ Value, env::{Environment, EnvironmentAccessHook}, machine::Machine, - value::{IdentifierValue, convert::AnyFunction}, + value::{IdentifierValue, StringValue, convert::AnyFunction}, }, }; @@ -148,15 +148,22 @@ impl Editor { pub fn run(&mut self) -> Result<(), Error> { script::setup_env(self); - #[cfg(any(feature = "runtime", rust_analyzer))] + let runtime_directory: PathBuf; + #[cfg(feature = "runtime")] { - self.evaluate_file("runtime/core.lysp"); + runtime_directory = PathBuf::from("runtime").canonicalize().unwrap(); } #[cfg(any(not(feature = "runtime"), rust_analyzer))] { - self.evaluate_file("/usr/share/red/runtime/core.lysp"); + runtime_directory = "/usr/share/red/runtime".into(); } + self.env.set_global_value( + "red/runtime-directory", + StringValue::from(format!("{}", runtime_directory.display())), + ); + self.evaluate_file(runtime_directory.join("core.lysp")); + loop { let exited = self.state.borrow().exited(); diff --git a/userspace/tools/red/src/script/mod.rs b/userspace/tools/red/src/script/mod.rs index f18d79a1..d30abecd 100644 --- a/userspace/tools/red/src/script/mod.rs +++ b/userspace/tools/red/src/script/mod.rs @@ -80,6 +80,17 @@ fn editor_api(editor: &mut Editor) { state.borrow_mut().set_message(message); Ok(Value::Nil) }); + editor.defun("red/status", |_, _, state, args| { + let mut message = String::new(); + for (i, arg) in args.iter().enumerate() { + if i != 0 { + message.push(' '); + } + message.push_str(&format!("{arg}")); + } + state.borrow_mut().set_status(message); + Ok(Value::Nil) + }); } fn syntax_api(editor: &mut Editor) {