red: add eval/set/unset commands

This commit is contained in:
2026-05-28 17:50:07 +03:00
parent 4b98ec1ce2
commit 7909fa3808
6 changed files with 133 additions and 14 deletions
+25 -1
View File
@@ -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<Environment>) {
};
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()),
@@ -133,6 +133,12 @@ impl From<IdentifierValue> for Value {
}
}
impl From<StringValue> for Value {
fn from(value: StringValue) -> Self {
Value::String(value)
}
}
impl TryFromValue<'_> for IdentifierValue {
fn try_from_value(value: &'_ Value) -> Result<Self, ValueConversionError> {
match value {
+48
View File
@@ -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))))
)
)
)
+31 -8
View File
@@ -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))
+12 -5
View File
@@ -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();
+11
View File
@@ -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) {