Skip to content

Commit

Permalink
Emacs mode and an error builtin
Browse files Browse the repository at this point in the history
  • Loading branch information
0xekez committed Nov 2, 2020
1 parent 2d80328 commit a7343a7
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 24 deletions.
31 changes: 31 additions & 0 deletions emacs.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
(require 'generic-x)

(define-generic-mode
'lust-mode ;; name of the mode
'(";") ;; comments delimiter
'("fn" "macro" "if" "set" "let" "import") ;; some keywords
'(("car" . 'font-lock-function-name-face)
("cdr" . 'font-lock-function-name-face)
("cons" . 'font-lock-function-name-face)
("eval" . 'font-lock-function-name-face)
("macroexpand" . 'font-lock-function-name-face)
("println" . 'font-lock-function-name-face)
("negate" . 'font-lock-function-name-face)
("add" . 'font-lock-function-name-face)
("sub" . 'font-lock-function-name-face)
("mul" . 'font-lock-function-name-face)
("div" . 'font-lock-function-name-face)
("lt" . 'font-lock-function-name-face)
("gt" . 'font-lock-function-name-face)
("eq" . 'font-lock-function-name-face)
("#t" . 'font-lock-constant-face)
("#f" . 'font-lock-constant-face)
("'" . 'font-lock-preprocessor-face)
("quote" . 'font-lock-preprocessor-face)
("error" . 'font-lock-warning-face))
'("\\.lust$") ;; files that trigger this mode
nil ;; any other functions to call
"Lust mode"
)

(provide 'lust-mode)
6 changes: 6 additions & 0 deletions src/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ pub fn macroexpand(args: &[LustData], env: Rc<RefCell<LustEnv>>) -> Result<CallR
)?))
}

pub fn error(args: &[LustData], env: Rc<RefCell<LustEnv>>) -> Result<CallResult, String> {
check_arg_len("error", 1, args)?;
let message = Interpreter::eval_in_env(&args[0], env)?;
Err(format!("{}", message))
}

/// Takes on argument and prints it to stdout followed by a newline.
pub fn println_(args: &[LustData], env: Rc<RefCell<LustEnv>>) -> Result<CallResult, String> {
check_arg_len("println", 1, args)?;
Expand Down
41 changes: 32 additions & 9 deletions src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ impl Interpreter {
pub fn macroexpand(mut ast: LustData, env: Rc<RefCell<LustEnv>>) -> Result<LustData, String> {
loop {
if !Self::is_macro_call(&ast, env.clone()) {
break Ok(ast.clone());
break Ok(ast);
}
ast = Self::eval_without_expansion(ast, env.clone())?;
}
Expand All @@ -130,8 +130,8 @@ impl Interpreter {
let pred = Self::eval_in_env(list.first().unwrap(), env.clone())?;
match pred {
LustData::Builtin(ref f) => f(&list[1..], env),
LustData::Fn(ref lf) => Self::eval_funcall(lf, &list[1..], env),
LustData::Mac(ref lm) => Self::eval_funcall(lm, &list[1..], env),
LustData::Fn(ref lf) => Self::eval_funcall(lf, &list[1..], env, true),
LustData::Mac(ref lm) => Self::eval_funcall(lm, &list[1..], env, false),
_ => Err(format!("invalid list predicate: {}", pred)),
}
}
Expand All @@ -142,6 +142,7 @@ impl Interpreter {
func: &LustFn,
args: &[LustData],
env: Rc<RefCell<LustEnv>>,
eval_args: bool,
) -> Result<CallResult, String> {
if (func.is_varadic() && args.len() < func.get_min_param_count())
|| (!func.is_varadic() && args.len() != func.params.len())
Expand All @@ -161,6 +162,7 @@ impl Interpreter {
}
} else {
let fnenv = LustEnv::new();

for (i, param) in func.params.iter().enumerate() {
if param == "&" {
let bind = func.params[i + 1].clone();
Expand All @@ -169,19 +171,26 @@ impl Interpreter {
} else {
let mut res = Vec::with_capacity(args.len() - i);
for e in &args[i..] {
res.push(Self::eval_in_env(e, env.clone())?);
let arg = if eval_args {
Self::eval_in_env(e, env.clone())?
} else {
e.clone()
};
res.push(arg);
}
LustData::List(res)
};
fnenv.borrow_mut().data.insert(bind, val);
break;
}
let arg = &args[i];
fnenv
.borrow_mut()
.data
.insert(param.clone(), Self::eval_in_env(arg, env.clone())?);
let arg = if eval_args {
Self::eval_in_env(&args[i], env.clone())?
} else {
args[i].clone()
};
fnenv.borrow_mut().data.insert(param.clone(), arg);
}

fnenv.borrow_mut().outer = Some(env);
Ok(CallResult::Call(fnenv, func.body.clone()))
}
Expand All @@ -208,12 +217,22 @@ impl Expr {

#[derive(Clone)]
pub enum LustData {
/// A floating point number
Number(f32),
/// A list.
List(Vec<LustData>),
/// A symbol. Used to represent IDs and files in import
/// expressions.
Symbol(String),
/// A character. The building block of a string.
Char(char),
/// A builtin function.
Builtin(fn(&[LustData], Rc<RefCell<LustEnv>>) -> Result<CallResult, String>),
/// A user defined function.
Fn(Rc<LustFn>),
/// A user defined macro. Macros differ from functions in that
/// their arguments are implicitly quoted and that they are
/// evlauted at compile time.
Mac(Rc<LustFn>),
}

Expand Down Expand Up @@ -273,6 +292,9 @@ impl LustData {
pub fn stringify(&self) -> Option<String> {
match self {
LustData::List(l) => {
if l.len() == 0 {
return None;
}
let mut res = String::with_capacity(l.len());
for d in l {
let c = match d.expect_char() {
Expand Down Expand Up @@ -330,6 +352,7 @@ impl LustEnv {
me.install_builtin("set", builtins::set);
me.install_builtin("let", builtins::let_);
me.install_builtin("fn", builtins::fn_);
me.install_builtin("error", builtins::error);
me.install_builtin("macro", builtins::macro_);
me.install_builtin("macroexpand", builtins::macroexpand);
me.install_builtin("println", builtins::println_);
Expand Down
39 changes: 24 additions & 15 deletions std.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -101,20 +101,29 @@
;; When COND is true executes and returns BODY's value.
(set 'when (macro (cond body) (list 'if cond body ())))

;; Takes a value to switch on and a list of pairs where the first item
;; in each pair is a value to be compared and the second is an
;; expression to evaluate if the value to be compared is the same as
;; the value being switched on.
;;
;; For example:
;;
;; lust> (match 1 (1 'one) (2 'two))
;; => 'two
(set 'match (macro (switch & cases)
;; Much like Scheme's condition. Takes a list of pairs where the first
;; argument and the second is an expression to evaluate if the
;; argument is true. Iterates over that list and return's the body of
;; the first one that evaluates to true. Optionally there can be a
;; special pair with the form (else body) that must appear in the
;; terminal position. If no other pairs evaluate to true that arm will
;; run.
(set 'cond (macro (& cases)
(do
(let 'find-match (fn (l)
(if l
(if (eq (eval (car (car l))) switch)
(car (cdr (car l)))
(find-match (cdr l))) ())))
(let 'arm-matches (fn (arm)
(if (eval (car arm))
(cdr arm)
#f)))
(let 'is-else-arm (fn (arm)
(eq (car arm) 'else)))
(let 'find-match (fn (cases)
(if cases
(if (is-else-arm (car cases))
(if (cdr cases)
(error '"else branch in non-terminal position")
(car (cdr (car cases))))
(if (arm-matches (car cases))
(car (cdr (car cases)))
(find-match (cdr cases))))
())))
(find-match cases))))

0 comments on commit a7343a7

Please sign in to comment.