Skip to content

Commit

Permalink
Add quaziquote following Emacs documentation.
Browse files Browse the repository at this point in the history
Closes #8
  • Loading branch information
0xekez committed Nov 3, 2020
1 parent a7343a7 commit 7fb6402
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 49 deletions.
3 changes: 2 additions & 1 deletion emacs.el
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@
("eq" . 'font-lock-function-name-face)
("#t" . 'font-lock-constant-face)
("#f" . 'font-lock-constant-face)
("[0-9]+" . 'font-lock-negation-char-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
(rainbow-delimiters-mode) ;; any other functions to call
"Lust mode"
)

Expand Down
51 changes: 51 additions & 0 deletions src/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,57 @@ pub fn eq(args: &[LustData], env: Rc<RefCell<LustEnv>>) -> Result<CallResult, St
Ok(CallResult::Ret(get_truthy_equiv(l == r)))
}

// Evaluate each argument in a comma expression, ignore all others.
pub fn quaziquote(args: &[LustData], env: Rc<RefCell<LustEnv>>) -> Result<CallResult, String> {
check_arg_len("quaziquote", 1, args)?;
let l = args[0].expect_list()?;
let mut res = Vec::with_capacity(l.len());
for item in l {
res.push(eval_commas(item, env.clone())?);
}
Ok(CallResult::Ret(LustData::List(res)))
}

fn eval_commas(data: &LustData, env: Rc<RefCell<LustEnv>>) -> Result<LustData, String> {
// If it's a comma, evaluate and return its argument. If it's a
// non-list type return it. If it's a list return a new list that
// is the result of calling eval_commas on each of its items.
match data {
LustData::List(ref l) => {
if is_comma(l) {
// We know that we have at least one element because
// is_comma returned true.
eval_comma(&l[1..], env)
} else {
let mut res = Vec::with_capacity(l.len());
for d in l {
res.push(eval_commas(d, env.clone())?);
}
Ok(LustData::List(res))
}
}
_ => Ok(data.clone()),
}
}

fn is_comma(data: &[LustData]) -> bool {
match data.first() {
Some(ref d) => {
if let Ok(s) = d.expect_symbol() {
s == "comma"
} else {
false
}
}
None => false,
}
}

fn eval_comma(args: &[LustData], env: Rc<RefCell<LustEnv>>) -> Result<LustData, String> {
check_arg_len("<comma builtin>", 1, args)?;
Ok(Interpreter::eval_in_env(&args[0], env)?)
}

/// Verifies that the function called NAME has received the expected
/// number of arguments.
fn check_arg_len(name: &str, expected: usize, args: &[LustData]) -> Result<(), String> {
Expand Down
3 changes: 2 additions & 1 deletion src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ impl Interpreter {
Err(format!(
"wrong number of arguments for function call. got {} and expected at least {}",
args.len(),
func.params.len()
func.params.len() - 1 // Minus one to offset for & argument
))
} else {
Err(format!(
Expand Down Expand Up @@ -344,6 +344,7 @@ impl LustEnv {
};

me.install_builtin("quote", builtins::quote);
me.install_builtin("quaziquote", builtins::quaziquote);
me.install_builtin("car", builtins::car);
me.install_builtin("cdr", builtins::cdr);
me.install_builtin("cons", builtins::cons);
Expand Down
76 changes: 34 additions & 42 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,28 @@ impl<'a> Parser<'a> {
self.tokbuffer.has_next()
}

fn expand(&mut self, name: &str, startloc: Location) -> ParseResult {
let id = ExprVal::Id(name.to_string());
let mut bodyres = self.parse_expr();
let call = match bodyres.expr {
Some(e) => ExprVal::List(vec![
Expr {
val: id,
loc: startloc.clone(),
},
e,
]),
None => id,
};
let expr = Expr {
val: call,
loc: Location::union(&startloc, &self.tokbuffer.loc()),
};
let mut parseres = ParseResult::from_expr(expr);
parseres.errors.append(&mut bodyres.errors);
parseres
}

/// Parses an expression from the tokenbuffer.
pub fn parse_expr(&mut self) -> ParseResult {
match self.tokbuffer.peek_token() {
Expand Down Expand Up @@ -189,51 +211,21 @@ impl<'a> Parser<'a> {
}),

TokenType::Quote => {
let quote = ExprVal::Id("quote".to_string());
let startloc = buffer.advance().loc;
let mut bodyres = self.parse_expr();
let call = match bodyres.expr {
Some(e) => ExprVal::List(vec![
Expr {
val: quote,
loc: startloc.clone(),
},
e,
]),
None => quote,
};
let expr = Expr {
val: call,
loc: Location::union(&startloc, &self.tokbuffer.loc()),
};
let mut parseres = ParseResult::from_expr(expr);
parseres.errors.append(&mut bodyres.errors);
parseres
let loc = buffer.advance().loc;
self.expand("quote", loc)
}

TokenType::Negate => {
let negate = ExprVal::Id("negate".to_string());
let startloc = buffer.advance().loc;
let mut bodyres = self.parse_expr();
let call = match bodyres.expr {
Some(e) => ExprVal::List(vec![
Expr {
val: negate,
loc: startloc.clone(),
},
e,
]),
None => negate,
};
let expr = Expr {
val: call,
loc: Location::union(&startloc, &self.tokbuffer.loc()),
};
let mut parseres = ParseResult::from_expr(expr);
parseres.errors.append(&mut bodyres.errors);
parseres
let loc = buffer.advance().loc;
self.expand("negate", loc)
}
TokenType::Quaziquote => {
let loc = buffer.advance().loc;
self.expand("quaziquote", loc)
}
TokenType::Comma => {
let loc = buffer.advance().loc;
self.expand("comma", loc)
}

TokenType::Unrecognized(s, _) => ParseResult::from_err(Error::on_tok(
&format!("malformed token: {}", s),
&buffer.advance(),
Expand Down
6 changes: 6 additions & 0 deletions src/tokenizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ pub enum TokenType {
Cparen,
/// A quote '
Quote,
// A quaziquote `
Quaziquote,
// A comma
Comma,
// A - sign
Negate,
/// An identifier. This is any sequence of characters not matched
Expand Down Expand Up @@ -78,6 +82,8 @@ impl<'a> Tokenizer<'a> {
'(' => self.eat_token_at_point(TokenType::Oparen),
')' => self.eat_token_at_point(TokenType::Cparen),
'\'' => self.eat_token_at_point(TokenType::Quote),
'`' => self.eat_token_at_point(TokenType::Quaziquote),
',' => self.eat_token_at_point(TokenType::Comma),
'-' => match self.reader.peek_2() {
Some('0'..='9') => self.eat_token_at_point(TokenType::Negate),
_ => self.eat_token_at_point(TokenType::Id("-".to_string())),
Expand Down
36 changes: 31 additions & 5 deletions std.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,20 @@
;; to that.
(set '#f ())

;; Quoted versions of set and let using Lust's quaziquote syntax.
(set 'setq (macro (symbol value) `(set ',symbol ,value)))
(set 'letq (macro (symbol value) `(set ',symbol ,value)))

;; Folds a list of values with a function and accumulator.
(set 'fold (fn (func list accum)
(if (eq list ())
accum
(fold func (cdr list) (func accum (car list))))))

;; Get's the length of a list
(set 'len (fn (list)
(fold (fn (a i) (add a 1)) list 0)))

;; Calculates the sum of a list.
(set 'sum (fn (list)
(fold (fn (a i) (add a i)) list 0)))
Expand Down Expand Up @@ -99,7 +107,8 @@
(set 'do (fn (& ops) (last ops)))

;; When COND is true executes and returns BODY's value.
(set 'when (macro (cond body) (list 'if cond body ())))
;; WARNING: I beleive that this is broken
(set 'when (macro (cond body) (if (eval cond) (eval body) ())))

;; Much like Scheme's condition. Takes a list of pairs where the first
;; argument and the second is an expression to evaluate if the
Expand All @@ -109,6 +118,9 @@
;; terminal position. If no other pairs evaluate to true that arm will
;; run.
(set 'cond (macro (& cases)
;; Evaluate the chosen match arm as the return value
;; of the cond expression.
(eval
(do
(let 'arm-matches (fn (arm)
(if (eval (car arm))
Expand All @@ -117,13 +129,27 @@
(let 'is-else-arm (fn (arm)
(eq (car arm) 'else)))
(let 'find-match (fn (cases)
(if cases
(when 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))))
(find-match (cdr cases)))))))
(find-match cases)))))

;; Define special form with the same form as scheme's. Allows for
;; defining functions and variables as follows:
;;
;; lust> (define one 1)
;; => 1
;; lust> (define I (x) x)
;; => (fn (x) x)
;;
;; Demonstrates use of the quaziquote form, cond, and errors.
(set 'define (macro (symbol & args)
(cond
((eq (len args) 1) `(setq ,symbol ,(car args)))
((eq (len args) 2) `(setq ,symbol ,`(fn ,(car args) ,(car (cdr args)))))
(else (error '"wrong number of arguments for define macro")))))

0 comments on commit 7fb6402

Please sign in to comment.