use glsp::{
arr, Arr, bail, bail_at, Callable, DequeAccess, DequeOps,
ensure, EnvMode, error, FromVal, GResult, macro_no_op,
Rest, RGlobal, Root, Span, Sym, stock_syms::*, str, Val
};
use glsp_proc_macros::{backquote};
use smallvec::{SmallVec};
use std::{fs};
use std::collections::{HashMap, hash_map::Entry::{Occupied, Vacant}, HashSet};
use std::default::{Default};
use std::iter::FromIterator;
use super::{Std};
use super::pat::{
AssignStrategy, Matcher, MismatchStrategy, Pat, pat_from_forms, PlaceStrategy, SetStrategy
};
pub fn init(sandboxed: bool) -> GResult<()> {
if !sandboxed {
glsp::bind_rfn_macro("include", &include)?;
}
glsp::bind_rfn_macro("+", &add)?;
glsp::bind_rfn_macro("-", &sub)?;
glsp::bind_rfn_macro("*", &mul)?;
glsp::bind_rfn_macro("/", &div)?;
glsp::bind_rfn_macro("min", &min)?;
glsp::bind_rfn_macro("max", &max)?;
glsp::bind_rfn_macro("bitand", &bitand)?;
glsp::bind_rfn_macro("bitor", &bitor)?;
glsp::bind_rfn_macro("bitxor", &bitxor)?;
glsp::bind_rfn_macro("def", &def)?;
glsp::bind_rfn_macro("defn", &defn)?;
glsp::bind_rfn_macro("defmacro", &defmacro)?;
glsp::bind_rfn_macro("with-global", &with_global)?;
glsp::bind_rfn_macro("global", &global)?;
glsp::bind_rfn_macro("global=", &set_global)?;
glsp::bind_rfn_macro("macro", &get_macro)?;
glsp::bind_rfn_macro("macro=", &set_macro)?;
glsp::bind_rfn_macro("access", &access)?;
glsp::bind_rfn_macro("access=", &set_access)?;
glsp::bind_rfn_macro("del!", &del)?;
glsp::bind_rfn_macro("remove!", &remove)?;
glsp::bind_rfn_macro("call-met", &call_met)?;
glsp::bind_rfn_macro("ensure", &ensure)?;
glsp::bind_rfn_macro("dbg", &dbg)?;
glsp::bind_rfn_macro("todo", &todo)?;
glsp::bind_rfn_macro("try", &try_)?;
glsp::bind_rfn_macro("try-verbose", &try_verbose)?;
glsp::bind_rfn_macro("when", &when)?;
glsp::bind_rfn_macro("unless", &unless)?;
glsp::bind_rfn_macro("while", &while_)?;
glsp::bind_rfn_macro("until", &until)?;
glsp::bind_rfn_macro("cond", &cond)?;
glsp::bind_rfn_macro("and", &and)?;
glsp::bind_rfn_macro("or", &or)?;
glsp::bind_rfn_macro("do-0", &do_0)?;
glsp::bind_rfn_macro("cond==", &cond_num_eq)?;
glsp::bind_rfn_macro("cond-same?", &cond_same)?;
glsp::bind_rfn_macro("cond-eq?", &cond_eq)?;
glsp::bind_rfn_macro("backquote", &backquote)?;
glsp::bind_rfn_macro("let", &let_)?;
glsp::bind_rfn_macro("let-fn", &let_fn)?;
glsp::bind_rfn_macro("match", &match_)?;
glsp::bind_rfn_macro("matches?", &matchesp)?;
glsp::bind_rfn_macro("when-let", &when_let)?;
glsp::bind_rfn_macro("fn", &fn_)?;
glsp::bind_rfn_macro("%met-fn", &met_fn)?;
glsp::bind_rfn_macro("fn0", &fn0)?;
glsp::bind_rfn_macro("fn1", &fn1)?;
glsp::bind_rfn_macro("->", &arrow_first)?;
glsp::bind_rfn_macro("->>", &arrow_last)?;
glsp::bind_rfn_macro("?", &question_mark)?;
glsp::bind_rfn_macro("tab", &tab)?;
glsp::bind_rfn_macro("for", &for_)?;
glsp::bind_rfn_macro("forn", &forn)?;
glsp::bind_rfn_macro("forni", &forni)?;
glsp::bind_rfn_macro("break", &break_)?;
glsp::bind_rfn_macro("continue", &continue_)?;
glsp::bind_rfn_macro("loop", &loop_)?;
glsp::bind_rfn_macro("yield-from", &yield_from)?;
glsp::bind_rfn_macro("defplace", &defplace)?;
glsp::bind_rfn("bind-place!", &bind_place)?;
glsp::bind_rfn_macro("=", &set)?;
static SETTERS: [(&str, &str, Option<bool>); 7] = [
("access", "access=", None),
("access-opt", "access-opt=", None),
("access-slice", "access-slice=", None),
("global", "global=", None),
("macro", "macro=", None),
("gc-value", "gc-value=", None),
("atsign", "atsign=", Some(false))
];
static OPT_SETTERS: [(&str, &str, Option<bool>); 1] = [
("atsign", "atsign-opt=", Some(false))
];
let mut std = Std::borrow_mut();
for (accessor, setter, memoize_args) in &SETTERS {
let memoize_args = memoize_args.unwrap_or(true);
std.setters.insert(glsp::sym(accessor)?, (glsp::sym(setter)?, memoize_args));
}
for (accessor, setter, memoize_args) in &OPT_SETTERS {
let memoize_args = memoize_args.unwrap_or(true);
std.opt_setters.insert(glsp::sym(accessor)?, (glsp::sym(setter)?, memoize_args));
}
glsp::bind_rfn_macro("inc!", &inc_assign)?;
glsp::bind_rfn_macro("dec!", &dec_assign)?;
glsp::bind_rfn_macro("mul!", &mul_assign)?;
glsp::bind_rfn_macro("div!", &div_assign)?;
glsp::bind_rfn_macro("div-euclid!", &div_euclid_assign)?;
glsp::bind_rfn_macro("rem!", &rem_assign)?;
glsp::bind_rfn_macro("rem-euclid!", &rem_euclid_assign)?;
glsp::bind_rfn_macro("abs!", &abs_assign)?;
glsp::bind_rfn_macro("neg!", &neg_assign)?;
glsp::bind_rfn_macro("seek!", &seek_assign)?;
glsp::bind_rfn_macro("antiseek!", &antiseek_assign)?;
glsp::bind_rfn_macro("clamp!", &clamp_assign)?;
glsp::bind_rfn_macro("swap!", &swap_assign)?;
Ok(())
}
fn is_splay_form(val: &Val) -> bool {
match val {
Val::Arr(arr) => arr.len() >= 1 && arr.get::<Val>(0).unwrap() == Val::Sym(SPLAY_SYM),
_ => false
}
}
fn binarize(
callee: Sym,
identity_val: Option<Val>,
replaceable: bool,
args: &[Val]
) -> Option<Val> {
for arg in args {
if is_splay_form(arg) {
return None
}
}
match args.len() {
0 => {
if replaceable {
Some(identity_val.unwrap())
} else {
None
}
}
1 => {
if let Some(identity_val) = identity_val {
Some(Val::Arr(arr![callee, identity_val, &args[0]]))
} else {
None
}
}
2 => {
None
}
_ => {
let result = arr![..args];
while result.len() > 2 {
let first: Val = result.pop_start().unwrap();
let second: Val = result.pop_start().unwrap();
result.push_start(arr![callee, first, second]).unwrap();
}
result.push_start(callee).unwrap();
Some(Val::Arr(result))
}
}
}
fn constant_fold(callee: Sym, args: &[Val]) -> Option<Val> {
if !args.iter().all(|arg| arg.is_num()) {
return None
}
if !glsp::has_global(callee).unwrap() {
return None
}
let callable = match glsp::global::<_, Callable>(callee) {
Ok(callable) => callable,
_ => return None
};
match glsp::try_call(false, &callable, args) {
Ok(val) => Some(val),
Err(_) => None
}
}
macro_rules! arithmetic_macro {
($name:ident, $callee:expr, $identity_val:expr, $replaceable:expr) => (
fn $name(args: Rest<Val>) -> GResult<Val> {
if let Some(result) = constant_fold($callee, &args) {
return Ok(result)
}
if let Some(result) = binarize($callee, $identity_val, $replaceable, &args) {
return Ok(result)
}
macro_no_op!()
}
);
}
arithmetic_macro!(add, ADD_SYM, Some(Val::Int(0)), true);
fn sub(args: Rest<Val>) -> GResult<Val> {
if let Some(result) = constant_fold(SUB_SYM, &args) {
return Ok(result)
}
if let Some(result) = binarize(SUB_SYM, None, false, &args) {
return Ok(result)
}
macro_no_op!()
}
arithmetic_macro!(mul, MUL_SYM, Some(Val::Int(1)), true);
arithmetic_macro!(div, DIV_SYM, Some(Val::Int(1)), false);
arithmetic_macro!(min, MIN_SYM, None, false);
arithmetic_macro!(max, MAX_SYM, None, false);
arithmetic_macro!(bitand, BITAND_SYM, Some(Val::Int(-1)), true);
arithmetic_macro!(bitor, BITOR_SYM, Some(Val::Int(0)), true);
arithmetic_macro!(bitxor, BITXOR_SYM, Some(Val::Int(0)), true);
fn include(path: &str) -> GResult<Val> {
let content_str = match fs::read_to_string(&path) {
Ok(content_str) => content_str,
Err(err) => {
let msg = error!("unable to read file '{}'", path);
return Err(msg.with_source(err))
}
};
let mut vals = glsp::parse_all(&content_str, Some(path))?;
if vals.len() == 1 {
Ok(vals.pop().unwrap())
} else {
Ok(Val::Arr(arr![SPLICE_SYM, ..vals]))
}
}
fn def(args: Rest<Val>) -> GResult<Val> {
match expand_let_like(DEF_SYM, PlaceStrategy::Global, &args)? {
Some(expanded) => Ok(expanded),
None => {
match args.len() {
0 => Ok(Val::Nil),
1 | 2 => {
let name = Sym::from_val(&args[0])?;
let init = args.get(1).cloned().unwrap_or(Val::Nil);
Ok(backquote!("(bind-global! '~name ~init)"))
}
_ => unreachable!()
}
}
}
}
fn defn(name: Sym, params: Root<Arr>, body: Rest<Val>) -> Val {
backquote!(r#"
(bind-global! '~name (fn &name ~name ~params ~..body))
"#)
}
fn defmacro(name: Sym, params: Root<Arr>, body: Rest<Val>) -> Val {
backquote!(r#"
(bind-macro! '~name (fn &name ~name ~params ~..body))
"#)
}
fn with_global(args: Rest<Val>) -> GResult<Val> {
match expand_let_like(WITH_GLOBAL_SYM, PlaceStrategy::Global, &args)? {
Some(expanded) => Ok(expanded),
None => {
match args.len() {
0 => Ok(Val::Nil),
1 | 2 => {
let name = Sym::from_val(&args[0])?;
let init = args.get(1).cloned().unwrap_or(Val::Nil);
Ok(backquote!(r#"
(splice
(let inner_value# ~init)
(let outer_value# (global '~name))
(global= '~name inner_value#)
(defer
(global= '~name outer_value#))
(defer-yield
(do
(= inner_value# (global '~name))
(global= '~name outer_value#))
(do
(= outer_value# (global '~name))
(global= '~name inner_value#)))
#n)
"#))
}
_ => unreachable!()
}
}
}
}
fn unwrap_question_mark(val: Val) -> GResult<Option<Val>> {
match val {
Val::Arr(arr) => {
if arr.len() >= 1 && arr.get::<Val>(0).unwrap() == Val::Sym(QUESTION_MARK_SYM) {
ensure!(arr.len() == 2, "? expects 1 argument, but received {}", arr.len() - 1);
let arg = arr.get::<Val>(1).unwrap();
ensure!(!is_splay_form(&arg), "the argument to ? cannot be splayed");
Ok(Some(arg))
} else {
Ok(None)
}
}
_ => Ok(None)
}
}
fn unwrap_slice_args(rest: &[Val]) -> GResult<Option<(Val, Val)>> {
let to_from = match &*rest {
&[Val::Sym(COLON_SYM)] => Some((Val::Nil, Val::Nil)),
&[ref from, Val::Sym(COLON_SYM)] => Some((from.clone(), Val::Nil)),
&[Val::Sym(COLON_SYM), ref to] => Some((Val::Nil, to.clone())),
&[ref from, Val::Sym(COLON_SYM), ref to] => Some((from.clone(), to.clone())),
_ => {
ensure!(!rest.contains(&Val::Sym(COLON_SYM)), "invalid slice arguments");
None
}
};
if let Some((to, from)) = &to_from {
ensure!(!is_splay_form(to) && !is_splay_form(from), "slice arguments cannot be splayed");
}
Ok(to_from)
}
fn global(name: Val) -> GResult<Val> {
if let Some(opt_name) = unwrap_question_mark(name)? {
return Ok(backquote!("(global-opt ~opt_name)"))
}
macro_no_op!()
}
fn set_global(name: Val, new_val: Val) -> GResult<Val> {
if let Some(opt_name) = unwrap_question_mark(name)? {
return Ok(backquote!("(global-opt= ~opt_name ~new_val)"))
}
macro_no_op!()
}
fn get_macro(name: Val) -> GResult<Val> {
if let Some(opt_name) = unwrap_question_mark(name)? {
return Ok(backquote!("(macro-opt ~opt_name)"))
}
macro_no_op!()
}
fn set_macro(name: Val, new_val: Val) -> GResult<Val> {
if let Some(opt_name) = unwrap_question_mark(name)? {
return Ok(backquote!("(macro-opt= ~opt_name ~new_val)"))
}
macro_no_op!()
}
fn access(coll: Val, rest: Rest<Val>) -> GResult<Val> {
if let Some((from, to)) = unwrap_slice_args(&rest)? {
return Ok(backquote!("(access-slice ~coll ~from ~to)"))
}
if rest.len() >= 1 {
if let Some(opt_index) = unwrap_question_mark(rest[0].clone())? {
ensure!(rest.len() == 1, "too many arguments to [coll (? idx)] form");
return Ok(backquote!("(access-opt ~coll ~opt_index)"))
}
}
macro_no_op!()
}
fn set_access(coll: Val, rest: Rest<Val>) -> GResult<Val> {
if rest.len() == 0 {
macro_no_op!()
}
let new_val = rest.last().unwrap().clone();
let args = &rest[..rest.len() - 1];
if let Some((from, to)) = unwrap_slice_args(args)? {
return Ok(backquote!("(access-slice= ~coll ~from ~to ~new_val)"))
}
if args.len() >= 1 {
if let Some(opt_index) = unwrap_question_mark(args[0].clone())? {
ensure!(args.len() == 1, "too many arguments to (= [coll (? idx)] new-val) form");
return Ok(backquote!("(access-opt= ~coll ~opt_index ~new_val)"))
}
}
macro_no_op!()
}
fn remove(coll: Val, rest: Rest<Val>) -> GResult<Val> {
if let Some((from, to)) = unwrap_slice_args(&rest)? {
return Ok(backquote!("(remove-slice! ~coll ~from ~to)"))
}
ensure!(rest.len() == 1, "argument mismatch: expected 2 arguments, \
received {}", rest.len() + 1);
if let Some(opt_index) = unwrap_question_mark(rest[0].clone())? {
return Ok(backquote!("(remove-opt! ~coll ~opt_index)"))
}
macro_no_op!()
}
fn del(coll: Val, rest: Rest<Val>) -> GResult<Val> {
if let Some((from, to)) = unwrap_slice_args(&rest)? {
return Ok(backquote!("(del-slice! ~coll ~from ~to)"))
}
ensure!(rest.len() == 1, "argument mismatch: expected 2 arguments, \
received {}", rest.len());
if let Some(opt_index) = unwrap_question_mark(rest[0].clone())? {
return Ok(backquote!("(del-opt! ~coll ~opt_index)"))
}
macro_no_op!()
}
fn call_met(args: Rest<Val>) -> GResult<Val> {
if args.len() == 0 {
macro_no_op!()
}
if let Some(opt_met_name) = unwrap_question_mark(args[0].clone())? {
let rest = &args[1..];
Ok(backquote!("(call-met-opt ~opt_met_name ~..rest)"))
} else {
macro_no_op!()
}
}
fn ensure(test: Val, error_args: Rest<Val>) -> Val {
if error_args.len() == 0 {
let message = str!("(ensure {}) failed", &test);
backquote!("(if ~test #n (bail ~message))")
} else {
backquote!("(if ~test #n (bail ~..error_args))")
}
}
fn dbg(forms: Rest<Val>) -> GResult<Root<Arr>> {
let result = arr![DO_SYM];
for form in &forms {
let form_name = glsp::gensym();
let stringified_form = str!["{:?}", form];
let clause: Val = backquote!(r#"
(do
(let ~form_name ~form)
(eprn (if (str? ~form_name)
(str "[" (file-location) "] " ~&stringified_form " = \"" ~form_name "\"")
(str "[" (file-location) "] " ~&stringified_form " = " ~form_name))))
"#);
result.push(clause)?;
}
Ok(result)
}
fn todo(forms: Rest<Val>) -> Root<Arr> {
if forms.len() == 0 {
backquote!(r#"(bail "not yet implemented")"#)
} else {
backquote!(r#"(bail "not yet implemented: " ~..forms)"#)
}
}
fn try_(body: Rest<Val>) -> Root<Arr> {
backquote!("(try-call 'brief (fn () ~..body))")
}
fn try_verbose(body: Rest<Val>) -> Root<Arr> {
backquote!("(try-call 'verbose (fn () ~..body))")
}
fn when(cond_clause: Val, rest: Rest<Val>) -> Root<Arr> {
let then_clause = match rest.len() {
0 => Val::Nil,
1 => rest[0].clone(),
_ => backquote!("(do ~..rest)")
};
backquote!("(if ~cond_clause ~then_clause #n)")
}
fn unless(cond_clause: Val, rest: Rest<Val>) -> Root<Arr> {
let then_clause = match rest.len() {
0 => Val::Nil,
1 => rest[0].clone(),
_ => backquote!("(do ~..rest)")
};
backquote!("(if ~cond_clause #n ~then_clause)")
}
fn while_(cond_clause: Val, body: Rest<Val>) -> Root<Arr> {
backquote!(r#"
(block LOOP
(if ~cond_clause
(do
~..body
(restart-block LOOP))
#n))
"#)
}
fn until(cond_clause: Val, body: Rest<Val>) -> Root<Arr> {
backquote!(r#"
(block LOOP
(if ~cond_clause
#n
(do
~..body
(restart-block LOOP))))
"#)
}
fn cond(clauses: Rest<Root<Arr>>) -> GResult<Val> {
let mut result = Val::Nil;
for (rev_i, clause) in clauses.iter().rev().enumerate() {
ensure!(clause.len() >= 1, "empty `cond` clause");
let predicate = clause.get::<Val>(0)?;
let body = glsp::arr_from_iter(clause.iter().skip(1))?;
if predicate == Val::Sym(ELSE_SYM) {
ensure!(rev_i == 0, "`else` must be the last clause in the `cond` form");
result = backquote!("(do ~..body)");
} else {
if body.len() == 0 {
result = backquote!(r#"
(do
(let predicate-name# ~predicate)
(if predicate-name# predicate-name# ~result))
"#);
} else {
result = backquote!("(if ~predicate (do ~..body) ~result)");
}
}
}
Ok(result)
}
fn and(forms: Rest<Val>) -> Val {
if forms.len() == 0 {
Val::Bool(true)
} else {
let mut result = forms.last().unwrap().clone();
for form in forms.iter().rev().skip(1) {
result = backquote!("(if ~form ~result #f)");
}
result
}
}
fn or(forms: Rest<Val>) -> GResult<Val> {
if forms.len() == 0 {
Ok(Val::Bool(false))
} else {
let mut result = forms.last().unwrap().clone();
for form in forms.iter().rev().skip(1) {
result = backquote!(r#"
(do
(let form-name# ~form)
(if form-name# form-name# ~result))
"#);
}
Ok(result)
}
}
fn do_0(first: Val, rest: Rest<Val>) -> Val {
backquote!(r#"
(do
(let first-name# ~first)
~..rest
first-name#)
"#)
}
fn cond_num_eq(test_form: Val, clauses: Rest<Val>) -> GResult<Val> {
cond_with_comparator(NUM_EQ_SYM, test_form, &clauses)
}
fn cond_same(test_form: Val, clauses: Rest<Val>) -> GResult<Val> {
cond_with_comparator(SAMEP_SYM, test_form, &clauses)
}
fn cond_eq(test_form: Val, clauses: Rest<Val>) -> GResult<Val> {
cond_with_comparator(EQP_SYM, test_form, &clauses)
}
fn cond_with_comparator(comparator: Sym, test_form: Val, clauses: &[Val]) -> GResult<Val> {
let test_name = glsp::gensym();
let cond_form = glsp::arr_with_capacity(clauses.len() + 1);
cond_form.push(COND_SYM)?;
for clause in clauses {
ensure!(clause.is_arr(), "invalid cond clause {}", &clause);
let clause = Root::<Arr>::from_val(&clause)?;
ensure!(clause.len() >= 1, "invalid cond clause {}", &clause);
let first = clause.get::<Val>(0)?;
if first == Val::Sym(ELSE_SYM) {
cond_form.push(clause)?;
} else if first == Val::Sym(ANY_OF_SYM) {
ensure!(clause.len() >= 2 && clause.get::<Val>(1)?.is_arr(), "invalid any-of clause");
let candidates = clause.get::<Root<Arr>>(1)?;
let or_form = glsp::arr_with_capacity(candidates.len() + 1);
or_form.push(OR_SYM)?;
for candidate in candidates.iter() {
or_form.push::<Val>(backquote!("(~comparator ~candidate ~test_name)"))?;
}
let rest = glsp::arr_from_iter(clause.iter().skip(2))?;
cond_form.push::<Val>(backquote!("(~or_form ~..rest)"))?;
} else {
let rest = glsp::arr_from_iter(clause.iter().skip(1))?;
cond_form.push::<Val>(backquote!("((~comparator ~first ~test_name) ~..rest)"))?;
}
}
Ok(backquote!(r#"
(do
(let ~test_name ~test_form)
~cond_form)
"#))
}
fn backquote(arg: Val) -> GResult<Val> {
let mut auto_gensyms = HashMap::new();
let val = backquote_impl(arg, &mut auto_gensyms)?;
if auto_gensyms.len() == 0 {
Ok(val)
} else {
let result = arr![DO_SYM];
for (original_sym, local_sym) in auto_gensyms {
let original_str = original_sym.name();
let tag = &original_str[0 .. original_str.len() - 1];
let let_form: Val = backquote!("(let ~local_sym (gensym ~tag))");
result.push(let_form)?;
}
result.push(val)?;
Ok(Val::Arr(result))
}
}
fn backquote_impl(arg: Val, auto_gensyms: &mut HashMap<Sym, Sym>) -> GResult<Val> {
match arg {
Val::Arr(arr) if arr.len() == 0 => {
Ok(Val::Arr(arr![]))
}
Val::Arr(arr) if arr.get::<Val>(0)? == Val::Sym(UNQUOTE_SYM) => {
ensure!(arr.len() == 2, "invalid `unquote` form");
Ok(arr.get::<Val>(1)?)
}
Val::Arr(arr) if arr.get::<Val>(0)? == Val::Sym(BACKQUOTE_SYM) => {
Ok(Val::Arr(arr))
}
Val::Arr(arr) => {
let result = glsp::arr_with_capacity(arr.len() + 1);
result.set_span(arr.span());
result.push(ARR_SYM)?;
for item in arr.iter() {
result.push(backquote_impl(item, auto_gensyms)?)?;
}
Ok(Val::Arr(result))
}
Val::Sym(sym) if sym.name().ends_with("#") => {
let local_name = match auto_gensyms.entry(sym) {
Occupied(entry) => *entry.get(),
Vacant(entry) => *entry.insert(glsp::gensym_with_tag("auto-gensym")?)
};
Ok(Val::Sym(local_name))
}
_ => {
Ok(backquote!("(quote ~arg)"))
}
}
}
fn expand_let_like(
let_name: Sym,
place_strategy: PlaceStrategy,
args: &[Val]
) -> GResult<Option<Val>> {
if args.len() == 0 {
return Ok(None)
}
let (pat, forms_consumed) = pat_from_forms(args, false, Span::default())?;
let mut rest_args = &args[forms_consumed..];
if forms_consumed == 1 && pat.at.is_none() && pat.pred.is_none() {
match args {
&[] => unreachable!(),
&[Val::Sym(UNDERSCORE_SYM)] => return Ok(Some(backquote!("(~let_name)"))),
&[Val::Sym(UNDERSCORE_SYM), ref init_val] => {
return Ok(Some(backquote!("(do ~init_val #n)")))
}
&[Val::Sym(name)] => return Ok(Some(backquote!("(~let_name ~name #n)"))),
&[Val::Sym(_name), ref _init_val] => return Ok(None),
&[Val::Sym(name), ref init_val, ref rest @ ..] => {
return Ok(Some(backquote!("
(splice
(~let_name ~name ~init_val)
(~let_name ~..rest))
")))
}
_ => ()
}
}
let init_form: Option<Val> = if rest_args.len() > 0 {
let init_form = rest_args[0].clone();
rest_args = &rest_args[1..];
Some(init_form)
} else {
None
};
let splice_form: Root<Arr> = backquote!("(splice)");
let mut names = HashSet::new();
pat.names(&mut names, false);
for name in names {
let to_push: Val = backquote!("(~let_name ~name #n)");
splice_form.push(to_push)?;
}
if let Some(init_form) = init_form {
let init_name = glsp::gensym();
let do_form: Root<Arr> = backquote!("(do (let ~init_name ~init_form))");
let set_strategy = SetStrategy(place_strategy, AssignStrategy::Set);
pat.codegen(init_name, &do_form, set_strategy, MismatchStrategy::Bail)?;
splice_form.push(do_form)?;
}
if rest_args.len() > 0 {
let to_push: Val = backquote!("(~let_name ~..rest_args)");
splice_form.push(to_push)?;
}
Ok(Some(Val::Arr(splice_form)))
}
fn let_(args: Rest<Val>) -> GResult<Val> {
match expand_let_like(LET_SYM, PlaceStrategy::Local, &args)? {
Some(expanded) => Ok(expanded),
None => {
if args.len() == 0 {
Ok(backquote!("(splice)"))
} else {
macro_no_op!()
}
}
}
}
fn let_fn(name: Sym, params: Root<Arr>, body: Rest<Val>) -> Val {
backquote!(r#"
(splice
(let ~name #n)
(= ~name (fn &name ~name ~params ~..body)))
"#)
}
fn match_(input_form: Val, clauses: Rest<Root<Arr>>) -> GResult<Val> {
let block_name = glsp::gensym();
let input_name = glsp::gensym();
let block_form: Root<Arr> = backquote!("
(block ~block_name
(let ~input_name ~input_form))
");
for clause in clauses {
let clause_forms = SmallVec::<[Val; 16]>::from_iter(clause.iter());
let (_, forms_consumed) = pat_from_forms(&clause_forms[..], false, clause.span())?;
let clause_pat = &clause_forms[..forms_consumed];
let clause_body = &clause_forms[forms_consumed..];
let when_let_form: Val = backquote!("
(when-let ~..clause_pat ~input_name
(finish-block ~block_name (do ~..clause_body)))
");
block_form.push(when_let_form)?;
}
block_form.push(Val::Nil)?;
Ok(Val::Arr(block_form))
}
fn matchesp(input_form: Val, pat_forms: Rest<Val>) -> GResult<Val> {
let (pat, forms_consumed) = pat_from_forms(&pat_forms, false, Span::default())?;
ensure!(forms_consumed == pat_forms.len(), "unexpected end of pattern in (matches?)");
let block_name = glsp::gensym();
let input_name = glsp::gensym();
let block_form: Root<Arr> = backquote!("
(block ~block_name
(let ~input_name ~input_form))
");
pat.codegen(
input_name,
&block_form,
SetStrategy(PlaceStrategy::Local, AssignStrategy::Discard),
MismatchStrategy::FinishBlock(block_name)
)?;
block_form.push(true)?;
Ok(backquote!("(bool ~block_form)"))
}
fn when_let(args: Rest<Val>) -> GResult<Val> {
let (pat, forms_consumed) = pat_from_forms(&args, false, Span::default())?;
ensure!(args.len() >= forms_consumed + 1, "(when-let) is missing an initializer");
let init_form = args[forms_consumed].clone();
let body = &args[forms_consumed + 1..];
let block_name = glsp::gensym();
let input_name = glsp::gensym();
let block_form: Root<Arr> = backquote!("
(block ~block_name
(let ~input_name ~init_form))
");
let mut names = HashSet::new();
pat.names(&mut names, false);
for name in names {
let to_push: Val = backquote!("(let ~name #n)");
block_form.push(to_push)?;
}
pat.codegen(
input_name,
&block_form,
SetStrategy(PlaceStrategy::Local, AssignStrategy::Set),
MismatchStrategy::FinishBlock(block_name)
)?;
if body.len() == 0 {
block_form.push(Val::Nil)?;
} else {
for body_form in body {
block_form.push(body_form)?;
}
}
Ok(Val::Arr(block_form))
}
#[derive(Copy, Clone, PartialEq)]
enum FnStrategy {
Trivial,
Simple,
Complex
}
fn fn_(args: Rest<Val>) -> GResult<Val> {
fn_common(&args, false)
}
fn met_fn(args: Rest<Val>) -> GResult<Val> {
fn_common(&args, true)
}
fn fn_common(args: &[Val], atsign_params: bool) -> GResult<Val> {
let (params_i, params_arr) = {
let mut i = 0;
loop {
ensure!(i < args.len(), "unexpected end of (fn) form");
match args[i] {
Val::Sym(FLAG_NAME_SYM) => i += 2,
Val::Sym(FLAG_ARG_LIMITS_SYM) => i += 3,
Val::Arr(ref arr) => break (i, arr.clone()),
ref arg => bail!("unexpected form {} in (fn)", arg)
}
}
};
let flags = &args[..params_i];
let body = &args[params_i + 1..];
if params_arr.len() == 0 {
if atsign_params == false {
macro_no_op!()
} else {
return Ok(backquote!("(fn ~..flags () ~..body)"))
}
}
let (params_pat, _) = pat_from_forms(
&[Val::Arr(params_arr.clone())],
atsign_params,
params_arr.span()
)?;
let pats = match ¶ms_pat.matcher {
Matcher::Arr(pats) => pats.clone(),
_ => bail_at!(params_arr.span(), "in (fn), {} is not a valid array pattern", params_arr)
};
let mut strategy = FnStrategy::Trivial;
fn matcher_is_trivial(matcher: &Matcher) -> bool {
match matcher {
Matcher::Underscore | Matcher::Sym(_) | Matcher::AtsignSym(_) => true,
Matcher::Opt(sub_matcher, _) | Matcher::Rest(sub_matcher) => {
match &**sub_matcher {
Matcher::Underscore | Matcher::Sym(_) | Matcher::AtsignSym(_) => true,
_ => false
}
}
_ => false
}
}
for (pat_i, pat) in pats.iter().enumerate() {
let opt_with_nontrivial_initializer = match &pat.matcher {
Matcher::Opt(_, Some(val)) => {
match val {
Val::Nil | Val::Bool(_) | Val::Int(_) | Val::Flo(_) |
Val::Char(_) | Val::Str(_) | Val::Tab(_) => false,
Val::Arr(arr) if arr.len() == 0 => false,
Val::Arr(arr) if arr.len() == 2 &&
arr.get::<Val>(0)? == Val::Sym(QUOTE_SYM) => false,
_ => true
}
}
_ => false
};
let nonterminal_rest = match &pat.matcher {
Matcher::Rest(_) => pat_i != pats.len() - 1,
_ => false
};
if pat.at.is_some() ||
pat.pred.is_some() ||
!matcher_is_trivial(&pat.matcher) ||
nonterminal_rest ||
opt_with_nontrivial_initializer {
if strategy == FnStrategy::Trivial {
strategy = FnStrategy::Simple;
}
} else {
continue
}
let nontrivial_opt_or_rest = match &pat.matcher {
Matcher::Opt(sub_matcher, _) | Matcher::Rest(sub_matcher) => {
pat.at.is_some() ||
pat.pred.is_some() ||
!matcher_is_trivial(sub_matcher)
}
_ => false
};
if nontrivial_opt_or_rest ||
nonterminal_rest ||
opt_with_nontrivial_initializer {
strategy = FnStrategy::Complex;
break
}
}
match strategy {
FnStrategy::Trivial => {
let mut to_no_op = true;
for pat in &pats {
assert!(pat.at.is_none() && pat.pred.is_none());
match &pat.matcher {
Matcher::Underscore | Matcher::AtsignSym(_) => {
to_no_op = false;
break
}
_ => ()
}
}
if to_no_op && !atsign_params {
macro_no_op!()
}
fn trivial_matcher_to_form(matcher: &Matcher) -> Val {
match matcher {
Matcher::Sym(name) | Matcher::AtsignSym(name) => Val::Sym(*name),
Matcher::Underscore => Val::Sym(glsp::gensym()),
Matcher::Opt(sub_matcher, None) => {
Val::Arr(arr![
QUESTION_MARK_SYM,
trivial_matcher_to_form(sub_matcher)
])
}
Matcher::Opt(sub_matcher, Some(init_val)) => {
Val::Arr(arr![
QUESTION_MARK_SYM,
trivial_matcher_to_form(sub_matcher),
init_val.clone()
])
}
Matcher::Rest(sub_matcher) => {
Val::Arr(arr![
SPLAY_SYM,
trivial_matcher_to_form(sub_matcher)
])
}
_ => panic!()
}
}
let trivial_params = glsp::arr_from_iter(pats.iter().map(|pat| {
trivial_matcher_to_form(&pat.matcher)
}))?;
Ok(backquote!("(fn ~..flags ~trivial_params ~..body)"))
}
FnStrategy::Simple => {
let simple_params = glsp::arr();
let pats_body = glsp::arr();
let mut names = HashSet::new();
for pat in &pats {
pat.names(&mut names, false);
}
for name in names {
let to_push: Val = backquote!("(let ~name #n)");
pats_body.push(to_push)?;
}
for pat in &pats {
let simple_name = glsp::gensym();
let (simple_form, simple_pat) = match &pat.matcher {
Matcher::Opt(sub_matcher, None) => {
(Val::Arr(arr![QUESTION_MARK_SYM, simple_name]),
Some(Pat {
matcher: (**sub_matcher).clone(),
..pat.clone()
}))
}
Matcher::Opt(sub_matcher, Some(init_val)) => {
(Val::Arr(arr![QUESTION_MARK_SYM, simple_name, init_val.clone()]),
Some(Pat {
matcher: (**sub_matcher).clone(),
..pat.clone()
}))
}
Matcher::Rest(sub_matcher) => {
if let Matcher::Underscore = **sub_matcher {
(Val::Arr(arr![SPLAY_SYM, UNDERSCORE_SYM]), None)
} else {
(Val::Arr(arr![SPLAY_SYM, simple_name]),
Some(Pat {
matcher: (**sub_matcher).clone(),
..pat.clone()
}))
}
}
_ => (Val::Sym(simple_name), Some(pat.clone()))
};
simple_params.push(simple_form)?;
if let Some(simple_pat) = simple_pat {
simple_pat.codegen(
simple_name,
&pats_body,
SetStrategy(PlaceStrategy::Local, AssignStrategy::Set),
MismatchStrategy::Bail
)?;
}
}
Ok(backquote!("(fn ~..flags ~simple_params ~..pats_body ~..body)"))
}
FnStrategy::Complex => {
let arg_name = glsp::gensym();
let pat_body = glsp::arr();
let mut names = HashSet::new();
params_pat.names(&mut names, false);
for name in names {
let to_push: Val = backquote!("(let ~name #n)");
pat_body.push(to_push)?;
}
params_pat.codegen(
arg_name,
&pat_body,
SetStrategy(PlaceStrategy::Local, AssignStrategy::Set),
MismatchStrategy::Bail
)?;
let (min_args, max_args) = params_pat.matcher.arg_limits();
ensure!(!flags.contains(&Val::Sym(FLAG_ARG_LIMITS_SYM)), "unexpected &arg-limits flag");
Ok(backquote!("
(fn &arg-limits ~min_args ~max_args ~..flags (..~arg_name) ~..pat_body ~..body)
"))
}
}
}
fn fn0(body: Rest<Val>) -> Root<Arr> {
backquote!("(fn () ~..body)")
}
fn fn1(body: Rest<Val>) -> GResult<Root<Arr>> {
fn replace_underscores(src: Val, replacement: Sym) -> GResult<Val> {
match src {
Val::Arr(ref arr) if arr.len() > 0 => {
let cloned = glsp::arr_from_iter(arr.iter())?;
let head = cloned.get::<Val>(0)?;
if head != Val::Sym(FN_SYM) && head != Val::Sym(QUOTE_SYM) {
let start_i = if head == Val::Sym(LET_SYM) { 2 } else { 0 };
for i in start_i .. cloned.len() {
let child_form = cloned.get::<Val>(i)?;
if child_form == Val::Sym(UNDERSCORE_SYM) {
cloned.set(i, replacement)?;
} else {
cloned.set(i, replace_underscores(child_form, replacement)?)?;
}
}
}
Ok(Val::Arr(cloned))
}
_ => Ok(src)
}
}
let mut expanded = glsp::expand_multi(&body, Some(EnvMode::Copied))?;
let param_name = glsp::gensym();
for form in &mut expanded {
*form = replace_underscores(form.clone(), param_name)?;
}
Ok(backquote!("(fn (~param_name) ~..expanded)"))
}
fn arrow_first(first: Val, rest: Rest<Val>) -> GResult<Val> {
arrow(ArrowPos::First, first, &rest)
}
fn arrow_last(first: Val, rest: Rest<Val>) -> GResult<Val> {
arrow(ArrowPos::Last, first, &rest)
}
enum ArrowPos {
First,
Last
}
fn arrow(pos: ArrowPos, first: Val, rest: &[Val]) -> GResult<Val> {
if rest.len() == 0 {
return Ok(first)
}
let mut result_name = glsp::gensym();
let do_form: Root<Arr> = backquote!("(do (let ~result_name ~first))");
for (i, val) in rest.iter().enumerate() {
let (val, splayed) = match val {
Val::Arr(arr) if arr.len() == 2 && arr.get::<Val>(0).unwrap() == Val::Sym(SPLAY_SYM) => {
(arr.get(1).unwrap(), true)
}
val => (val.clone(), false)
};
let result_form: Val = if splayed {
backquote!("(splay ~result_name)")
} else {
backquote!("~result_name")
};
let call_form: Root<Arr> = match val {
Val::Sym(callee_name) => backquote!("(~callee_name ~result_form)"),
Val::Arr(call_arr) if call_arr.len() >= 1 => {
let callee: Val = call_arr.get(0)?;
let args: Vec<Val> = Vec::from_iter(call_arr.iter().skip(1));
let out_call_arr: Root<Arr> = match callee {
Val::Sym(MET_NAME_SYM) | Val::Sym(QUESTION_MARK_SYM) | Val::Sym(ATSIGN_SYM) => {
backquote!("(~&call_arr ~result_form)")
}
callee => {
match pos {
ArrowPos::First => backquote!("(~callee ~result_form ~..args)"),
ArrowPos::Last => backquote!("(~callee ~..args ~result_form)")
}
}
};
out_call_arr.set_span(call_arr.span());
out_call_arr
}
val => bail!("expected a sym or a non-empty arr, received {}", val)
};
result_name = glsp::gensym();
let do_clause = if i == rest.len() - 1 {
call_form
} else {
backquote!("(let ~result_name ~call_form)")
};
do_form.push(do_clause)?;
}
Ok(Val::Arr(do_form))
}
fn question_mark(arg: Val) -> GResult<Val> {
if let Val::Arr(arr) = arg {
if arr.len() == 2 {
if let Val::Sym(ATSIGN_SYM) = arr.get(0).unwrap() {
let second = arr.get::<Val>(1).unwrap();
return Ok(backquote!("(atsign-opt ~second)"))
}
}
}
macro_no_op!()
}
fn tab(clauses: Rest<Root<Arr>>) -> GResult<Root<Arr>> {
if clauses.len() == 0 {
macro_no_op!()
}
let len = clauses.len();
let tab_name = glsp::gensym();
let set_forms = glsp::arr_with_capacity(len);
for clause in clauses.iter() {
ensure!(clause.len() == 2, "expected (tab (k0 v0) (k1 v1) ..base)");
let (key_form, val_form): (Val, Val) = (clause.get(0)?, clause.get(1)?);
if key_form == Val::Sym(SPLAY_SYM) {
let set_form: Root<Arr> = backquote!("(extend! ~tab_name ..~val_form)");
set_forms.push(set_form)?;
} else {
let set_form: Root<Arr> = backquote!("(access= ~tab_name ~key_form ~val_form)");
set_forms.push(set_form)?;
}
}
Ok(backquote!(r#"
(do
(let ~tab_name (tab))
~..set_forms
~tab_name)
"#))
}
fn for_(args: Rest<Val>) -> GResult<Val> {
let (_, forms_consumed) = pat_from_forms(&args, false, Span::default())?;
ensure!(args.len() >= forms_consumed + 2 && args[forms_consumed] == Val::Sym(IN_SYM),
"invalid for loop: expected (for pattern in iterable ...)");
let pat_forms = &args[..forms_consumed];
let iterable = args[forms_consumed + 1].clone();
let body = &args[forms_consumed + 2..];
Ok(backquote!(r#"
(do
(let iter# (iter ~iterable))
(block LOOP
(let item# (iter-next! iter#))
(when (iter-finished? iter#)
(finish-block LOOP))
(let ~..pat_forms item#)
~..body
(restart-block LOOP)))
"#))
}
fn forn(clause: Root<Arr>, body: Rest<Val>) -> GResult<Val> {
ensure!(clause.len() >= 2 && clause.len() <= 4 && clause.get::<Val>(0)?.is_sym(),
"invalid forn clause {}", &clause);
let rest = clause.shallow_clone();
let name: Sym = rest.pop_start()?;
Ok(backquote!(r#"
(for ~name in (rn ~..rest)
~..body)
"#))
}
fn forni(clause: Root<Arr>, body: Rest<Val>) -> GResult<Val> {
ensure!(clause.len() >= 2 && clause.len() <= 4 && clause.get::<Val>(0)?.is_sym(),
"invalid forni clause {}", &clause);
let rest = clause.shallow_clone();
let name: Sym = rest.pop_start()?;
Ok(backquote!(r#"
(for ~name in (rni ~..rest)
~..body)
"#))
}
fn break_(result: Option<Val>) -> Val {
match result {
Some(result) => backquote!("(finish-block LOOP ~result)"),
None => backquote!("(finish-block LOOP)")
}
}
fn continue_() -> Val {
backquote!("(restart-block LOOP)")
}
fn loop_(body: Rest<Val>) -> Val {
backquote!(r#"
(block LOOP
~..body
(restart-block LOOP))
"#)
}
fn yield_from(iterable: Val) -> Val {
backquote!(r#"
(do
(let iterable# ~iterable)
(cond
((coro? iterable#)
(let result# (coro-run iterable#))
(while (eq? (coro-state iterable#) 'paused)
(= result# (coro-run iterable# (yield result#))))
result#)
(else
(for item# in iterable#
(yield item#)))))
"#)
}
fn defplace(accessor: Sym, setter: Sym, memoize_args: Option<bool>) -> Root<Arr> {
match memoize_args {
Some(memoize_args) => backquote!("(bind-place! '~accessor '~setter ~memoize_args)"),
None => backquote!("(bind-place! '~accessor '~setter)")
}
}
fn bind_place(
std: &mut Std,
accessor: Sym,
setter: Sym,
memoize_args: Option<bool>
) -> GResult<()> {
match std.setters.entry(accessor) {
Vacant(entry) => entry.insert((setter, memoize_args.unwrap_or(true))),
Occupied(_) => bail!("duplicate setter {}", accessor)
};
Ok(())
}
fn set(std: &Std, args: Rest<Val>) -> GResult<Val> {
ensure!(args.len() % 2 == 0, "= expects an even number of arguments");
fn set_clause(std: &Std, place: Val, new_val_form: Val) -> GResult<Val> {
ensure!(place.is_arr(), "invalid place {:?} passed to =", place);
let place = place.unwrap_arr();
ensure!(place.len() >= 1 && place.get::<Val>(0)?.is_sym(),
"invalid place {:?} passed to =", place);
let callee: Sym = place.get(0)?;
if callee == QUESTION_MARK_SYM {
ensure!(place.len() == 2, "invalid place {:?} passed to =", place);
let opt_place = place.get::<Val>(1)?;
ensure!(opt_place.is_arr(), "invalid place {:?} passed to =", place);
let opt_place = opt_place.unwrap_arr();
ensure!(opt_place.len() >= 1 && opt_place.get::<Val>(0)?.is_sym(),
"invalid place {:?} passed to =", place);
let opt_callee: Sym = opt_place.get(0)?;
if let Some((opt_setter, _)) = std.opt_setters.get(&opt_callee) {
let opt_args = glsp::arr_from_iter(opt_place.iter().skip(1))?;
Ok(backquote!("(~opt_setter ~..opt_args ~new_val_form)"))
} else {
bail!("invalid place {:?} passed to =", place);
}
} else {
if let Some((setter, _)) = std.setters.get(&callee) {
let args = glsp::arr_from_iter(place.iter().skip(1))?;
Ok(backquote!("(~setter ~..args ~new_val_form)"))
} else {
bail!("invalid place {:?} passed to =", place);
}
}
}
match args.len() {
0 => Ok(Val::Nil),
2 => {
ensure!(!is_splay_form(&args[1]), "the second argument to = cannot be splayed");
if args[0].is_sym() {
macro_no_op!()
}
set_clause(std, args[0].clone(), args[1].clone())
}
_ => {
let do_form = arr![DO_SYM];
for pair in args.chunks_exact(2) {
if pair[0].is_sym() {
do_form.push(arr![ASSIGNMENT_SYM, ..pair])?;
} else {
do_form.push(set_clause(std, pair[0].clone(), pair[1].clone())?)?;
}
}
Ok(Val::Arr(do_form))
}
}
}
fn in_place<F>(place: Val, f: F) -> GResult<Val>
where
F: FnOnce(Val) -> GResult<Val>
{
match place {
Val::Sym(sym) => {
f(Val::Sym(sym))
}
Val::Arr(ref place) if place.len() >= 1 && place.get::<Val>(0)?.is_sym() => {
let accessor = place.get::<Sym>(0)?;
let std = Std::borrow();
ensure!(std.setters.contains_key(&accessor), "unrecognized place form {:?}", place);
let (_, memoize_args) = *std.setters.get(&accessor).unwrap();
if memoize_args {
let mut arg_lets = Vec::<Root<Arr>>::with_capacity(place.len() - 1);
let mut arg_names = Vec::<Sym>::with_capacity(place.len() - 1);
for arg_form in place.iter().skip(1) {
let arg_name = glsp::gensym();
arg_lets.push(backquote!("(let ~arg_name ~arg_form)"));
arg_names.push(arg_name);
}
let op_form = f(backquote!("(~accessor ~..arg_names)"))?;
Ok(backquote!(r#"
(do
~..arg_lets
~op_form)
"#))
} else {
f(Val::Arr(place.clone()))
}
}
_ => bail!("unrecognized place form {:?}", place)
}
}
fn inc_assign(place: Val, by: Rest<Val>) -> GResult<Val> {
let by = if by.len() == 0 { &[Val::Int(1)] } else { &*by };
in_place(place, |place| Ok(backquote!("(= ~&place (+ ~&place ~..by))")))
}
fn dec_assign(place: Val, by: Rest<Val>) -> GResult<Val> {
let by = if by.len() == 0 { &[Val::Int(1)] } else { &*by };
in_place(place, |place| Ok(backquote!("(= ~&place (- ~&place ~..by))")))
}
fn mul_assign(place: Val, by: Rest<Val>) -> GResult<Val> {
ensure!(by.len() >= 1, "mul! expects a place and at least one additional argument");
in_place(place, |place| Ok(backquote!("(= ~&place (* ~&place ~..by))")))
}
fn div_assign(place: Val, by: Rest<Val>) -> GResult<Val> {
in_place(place, |place| Ok(backquote!("(= ~&place (/ ~&place ~..by))")))
}
fn div_euclid_assign(place: Val, by: Rest<Val>) -> GResult<Val> {
in_place(place, |place| Ok(backquote!("(= ~&place (div-euclid ~&place ~..by))")))
}
fn rem_assign(place: Val, by: Val) -> GResult<Val> {
in_place(place, |place| Ok(backquote!("(= ~&place (% ~&place ~&by))")))
}
fn rem_euclid_assign(place: Val, by: Val) -> GResult<Val> {
in_place(place, |place| Ok(backquote!("(= ~&place (rem-euclid ~&place ~&by))")))
}
fn abs_assign(place: Val) -> GResult<Val> {
in_place(place, |place| Ok(backquote!("(= ~&place (abs ~&place))")))
}
fn neg_assign(place: Val) -> GResult<Val> {
in_place(place, |place| Ok(backquote!("(= ~&place (- ~&place))")))
}
fn seek_assign(place: Val, target: Val, step_by: Option<Val>) -> GResult<Val> {
match step_by {
Some(step_by) => {
in_place(place, |place| Ok(backquote!("
(= ~&place (seek ~&place ~&target ~&step_by))
")))
}
None => {
in_place(place, |place| Ok(backquote!("
(= ~&place (seek ~&place ~&target))
")))
}
}
}
fn antiseek_assign(place: Val, anti_target: Val, step_by: Option<Val>) -> GResult<Val> {
match step_by {
Some(step_by) => {
in_place(place, |place| Ok(backquote!("
(= ~&place (antiseek ~&place ~&anti_target ~&step_by))
")))
}
None => {
in_place(place, |place| Ok(backquote!("
(= ~&place (antiseek ~&place ~&anti_target))
")))
}
}
}
fn clamp_assign(place: Val, min: Val, max: Val) -> GResult<Val> {
in_place(place, |place| Ok(backquote!("(= ~&place (clamp ~&place ~&min ~&max))")))
}
fn swap_assign(place0: Val, place1: Val) -> GResult<Val> {
in_place(place0, |place0| {
in_place(place1, |place1| {
Ok(backquote!("
(do
(let tmp-name# ~&place0)
(= ~&place0 ~&place1)
(= ~&place1 tmp-name#))
"))
})
})
}