Arquivo: evaluator/application.rs
use gc::Gc;
use crate::{
maj_list,
core::{ Maj, MajState },
axioms::{
predicates::*,
primitives::*
},
};
use crate::axioms::utils::{
STACK_RED_ZONE,
STACK_PER_RECURSION
};
use super::maj_eval;
- Uma primitiva pode ser despachada para a aplicação de primitivas;
- Um macro deverá ser aplicado como um procedimento normal, mas seu resultado deve ser interpretado logo em seguida;
- Uma clausura deverá ser aplicada a seus argumentos. Isso envolve processos mais complexos como currying e desestruturação, portanto esse processo deverá ser delegado para sua própria função;
- Caso nenhuma das situações se encaixe, então teremos uma falha de aplicação desconhecida.
pub fn maj_apply(
mut state: &mut MajState,
fun: Gc<Maj>,
args: Gc<Maj>,
env: Gc<Maj>
) -> Gc<Maj> {
// primitives
if maj_primitivep(fun.clone()).to_bool() {
let name = maj_car(maj_cdr(maj_cdr(fun)));
apply_primitive(&mut state, name, args, env)
}
// closure
else if maj_closurep(fun.clone()).to_bool() {
maj_apply_closure(&mut state, fun, args, env)
}
// macro
else if maj_macrop(fun.clone()).to_bool() {
let (expr, can_evaluate) =
expand_macro(&mut state, fun, args,
env.clone());
if can_evaluate {
stacker::maybe_grow(
STACK_RED_ZONE,
STACK_PER_RECURSION,
|| maj_eval(&mut state, expr, env))
} else {
// Error
expr
}
}
// otherwise, fail
else {
maj_err(
Maj::string("Cannot apply {} to args {}"),
maj_list!(fun, args))
}
}
A aplicação de uma clausura em uma lista de argumentos é um dos
processos-chave da interpretação da linguagem. Aqui, tomamos como
argumento a clausura em si, que chamamos fun
, e uma lista de
argumentos que já tenham sido interpretados – a essa lista daremos o
nome args
.
Primeiramente, verificamos se fun
é uma clausura válida – não por ser
um literal designando clausura, mas por ser uma lista de cinco
elementos.
Podemos relembrar que uma clausura possui a sintaxe
(lit closure <env> <lambda-list> (<body>))
Recuperamos o contexto capturado pela função (env
), a lista[fn:6] de
símbolos que designam os argumentos da função (lambda-list
) e o corpo
da função a ser interpretado (body
).
O corpo da função pode possuir uma ou mais expressões, portanto, é
necessário “corrigi-lo” antes da aplicação, adicionando-se um do
implícito.
Se a quantidade de argumentos fornecidos for nula quando a função pede um número de argumentos superior a zero, lançamos um erro. Isso evita uma possível aplicação parcial com zero argumentos.
A seguir, fazemos ligações entre os símbolos da lambda-list
e os
argumentos, até onde for possível, tomando o contexto capturado como
base. Caso ainda sobre algum símbolo para ser ligado, isso será
indicativo de que estamos tratando de aplicação parcial.
Se o processo de ligação apresentar um erro no lugar do contexto extendido, interrompemos a aplicação imediatamente e retornamo-no.
Logo após, tratamos a aplicação parcial. Se a lista de argumentos ainda não-ligados não for vazia, então retornamos imediatamente uma nova clausura, gerada ad-hoc: o contexto capturado por ela é o contexto extendido, a lista de argumentos da clausura será composta pelos exatos mesmos argumentos que não foram ligados, na ordem em que apareciam originalmente. O corpo da função continuará sendo o mesmo.
Por fim, a aplicação total de uma função representa uma operação
simples: basta interpretar o corpo da função, usando o contexto
extendido pelo processo de ligação dos argumentos aos símbolos da
lambda-list
.
fn maj_apply_closure(
mut state: &mut MajState,
fun: Gc<Maj>,
args: Gc<Maj>,
lexenv: Gc<Maj>
) -> Gc<Maj> {
use crate::core::environment::maj_env_union;
let length = maj_length(fun.clone()).to_integer().unwrap();
if length != 5 {
return maj_err(
Maj::string("Invalid syntax: {}"),
maj_list!(fun));
}
// (lit closure <env> lambda-list . body)
let env = maj_car(maj_cdr(maj_cdr(fun.clone())));
let lambda_list =
maj_car(maj_cdr(maj_cdr(maj_cdr(fun.clone()))));
let body =
maj_car(maj_cdr(maj_cdr(maj_cdr(maj_cdr(fun.clone())))));
if maj_nilp(args.clone()).to_bool() &&
!maj_nilp(lambda_list.clone()).to_bool() {
return maj_err(
Maj::string(
"Cannot curry function without arguments"),
Maj::nil());
}
if maj_consp(lambda_list.clone()).to_bool() &&
maj_proper_list_p(lambda_list.clone()).to_bool() {
let ll_len = maj_length(lambda_list.clone())
.to_integer();
match ll_len {
Some(ll_len) => {
let args_len = maj_length(args.clone())
.to_integer()
.unwrap();
if args_len > ll_len {
return maj_err(Maj::string(
"Too many arguments in function call"),
Maj::nil());
}
},
// Dotted list
None => {},
}
}
let (uargs, extenv) = maj_bind(lambda_list, args, env.clone());
if maj_errorp(extenv.clone()).to_bool() {
env
} else if !maj_nilp(uargs.clone()).to_bool() {
maj_list!(Maj::lit(),
Maj::closure(),
extenv,
uargs,
body)
} else {
// Implicit `do`
let body = Maj::cons(Maj::do_sym(), body);
// Unite extended environment with lexical environment
// (on evaluation only)
let extenv = maj_env_union(extenv, lexenv);
maj_eval(&mut state, body, extenv)
}
}
A função a seguir realiza o processo genérico de ligação dos símbolos
da lambda-list
aos argumentos.
Essa função opera de forma recursiva, e retorna dois valores:
- Uma lista de símbolos não-ligados na
lambda-list
, caso existam; - O novo contexto após sua extensão, ou um objeto de erro.
Essa função também é responsável pelo processo de desestruturação de
argumentos, quando a lambda-list
possui sub-listas. Esse processo é
delegado a outra função que veremos a seguir.
fn maj_bind(
lambda_list: Gc<Maj>,
args: Gc<Maj>,
env: Gc<Maj>
) -> (Gc<Maj>, Gc<Maj>) {
use crate::core::environment::maj_env_push;
// -1. lambda_list is nil.
if maj_nilp(lambda_list.clone()).to_bool() {
// -1.1. If (not (nilp args)), then error
if !maj_nilp(args.clone()).to_bool() {
return
(Maj::nil(),
maj_err(
Maj::string("Arguments {} exceeded lambda-list"),
maj_list!(args.clone())));
}
// -1.X. Otherwise, return (lambda_list, env).
// Everything is already bound.
else {
return (lambda_list, env);
}
}
// 0. args is nil.
// 0.1. Return (lambda_list, env) as well.
// This does currying.
else if maj_nilp(args.clone()).to_bool() {
return (lambda_list, env);
}
match &*lambda_list.clone() {
// 1. lambda_list is a cons.
Maj::Cons { car, cdr } => {
let extenv =
if maj_consp(car.clone()).to_bool() {
// If car is a cons: Do destructuring.
let newenv =
maj_destructuring_bind(car.clone(),
maj_car(args.clone()),
env.clone());
if maj_errorp(newenv.clone()).to_bool() {
return (Maj::nil(), newenv);
}
newenv
} else {
// If car is not a cons: Normal binding.
// Bind car to (car args) in the lexenv.
maj_env_push(env.clone(),
car.clone(),
maj_car(args.clone()))
};
// Recur with extended environment, cdr as
// lambda_list, (cdr args) as args
maj_bind(cdr.clone(), maj_cdr(args), extenv)
},
// 2. lambda_list is a symbol.
Maj::Sym(_) => {
// TODO: if (last args) is (&), we need to curry
// while binding (butlast args) to the symbol.
// Then return the symbol itself as lambda-list.
// The binding incurs in
// (append (lookup ,symbol) (butlast args)).
// If (last args) is not (&),
// Both leave no unbound syms in lambda-list.
(Maj::nil(),
maj_env_push(
env.clone(),
lambda_list,
// 2.1. If (not (nilp args)) then bind args
if !maj_nilp(args.clone()).to_bool() {
args
}
// 2.2. If (nilp args) then bind nil
else {
Maj::nil()
}))
},
// X. Otherwise, error. Only cons and symbols allowed.
_ => (Maj::nil(),
maj_err(
Maj::string(
"Lambda list can only have symbols or conses"),
Maj::nil()))
}
}
fn maj_destructuring_bind(list: Gc<Maj>,
args: Gc<Maj>,
env: Gc<Maj>) -> Gc<Maj> {
use crate::core::environment::maj_env_push;
let list_nilp = maj_nilp(list.clone()).to_bool();
let args_nilp = maj_nilp(args.clone()).to_bool();
// Do not allow leftover args
if list_nilp && !args_nilp {
return maj_err(
Maj::string("Arguments exceed destructuring pattern"),
Maj::nil());
}
// Leftover symbols, though, should bind to nil.
if !list_nilp && args_nilp {
return maj_bind_rec_nil(list, env);
}
// When both are nil, we are done
if list_nilp && args_nilp {
return env;
}
// When list is a symbol and args is not, bind list
// to args and return
if !maj_consp(list.clone()).to_bool() {
return maj_env_push(env, list, args);
}
// When args is a non-nil symbol, fail immediately!
if !maj_consp(args.clone()).to_bool() {
return maj_err(
Maj::string("Cannot destructure atomic value {}"),
maj_list!(maj_car(args.clone())));
}
// Otherwise, there is destructuring to do.
let sym = maj_car(list.clone());
let arg = maj_car(args.clone());
let extenv =
if maj_consp(sym.clone()).to_bool() {
// If sym is a cons, recursively destructure
// it with arg as new environment.
maj_destructuring_bind(sym, arg, env)
} else {
// Otherwise, do an ad-hoc binding to
// generate the extended environment.
maj_env_push(env, sym, arg)
};
// Proceed recursively with new environment
maj_destructuring_bind(maj_cdr(list),
maj_cdr(args),
extenv)
}
fn maj_bind_rec_nil(lambda_list: Gc<Maj>, env: Gc<Maj>) -> Gc<Maj> {
use crate::core::environment::maj_env_push;
if maj_nilp(lambda_list.clone()).to_bool() {
return Maj::nil();
}
match &*lambda_list.clone() {
Maj::Sym(_) => {
maj_env_push(env.clone(),
lambda_list.clone(),
Maj::nil())
},
Maj::Cons { car, cdr } => {
let extenv = maj_bind_rec_nil(car.clone(), env);
maj_bind_rec_nil(cdr.clone(), extenv)
},
_ => panic!("Never try to recursively bind a list with more than symbols and conses"),
}
}
pub fn expand_macro(
mut state: &mut MajState,
mac: Gc<Maj>,
args: Gc<Maj>,
env: Gc<Maj>
) -> (Gc<Maj>, bool) {
if maj_macrop(mac.clone()).to_bool() {
// (lit macro <closure>)
let closure = maj_car(maj_cdr(maj_cdr(mac)));
let result = maj_apply(&mut state,
closure,
args.clone(),
env);
if maj_closurep(result.clone()).to_bool() {
(maj_err(
Maj::string(
"Error expanding macro expression for {}"),
maj_list!(args)),
false)
} else if maj_errorp(result.clone()).to_bool() {
(result, false)
} else {
(result, true)
}
} else {
(Maj::cons(mac, args), false)
}
}
pub fn apply_primitive(mut state: &mut MajState,
prim: Gc<Maj>,
args: Gc<Maj>,
env: Gc<Maj>) -> Gc<Maj> {
use crate::printing::maj_format;
use crate::axioms::MajPrimArgs;
let primitive = state.find_primitive(prim.clone());
match primitive {
Some((function, arity)) => {
let argl = maj_length(args.clone())
.to_integer()
.unwrap();
match *arity {
MajPrimArgs::None => {
if argl != 0 {
maj_err(
Maj::string("{} requires no arguments"),
maj_list!(prim))
} else {
function(&mut state, Maj::nil(), env)
}
},
MajPrimArgs::Required(n) => {
let n = n as i64;
if argl < n {
// Curry on argl < n && argl != 0.
// Delegate to closures
curry_primitive(&mut state, prim, n, argl, args, env, false)
} else if argl > n {
// Fail on argl > n
maj_err(
Maj::string("Too many arguments for {}"),
maj_list!(prim))
} else {
// Apply on argl = n
function(&mut state, args, env)
}
},
MajPrimArgs::Variadic(n) => {
let n = n as i64;
// Check for & to account for in argl.
let last = maj_last(args.clone());
let force_curry = maj_eq(last, Maj::ampersand())
.to_bool();
let targl = if force_curry { argl - 1 } else { argl };
// Normal and variadic currying, respectively
if (targl < n && !force_curry) ||
(targl > n && force_curry) {
// Delegate currying to closures
curry_primitive(&mut state, prim, n, targl, args, env, true)
} else {
function(&mut state, args, env)
}
},
}
},
_ => panic!("Primitive \"{}\" does not exist",
maj_format(&state, prim)),
}
}
fn gen_arglist(mut state: &mut MajState, n: i64, variadicp: bool) -> Gc<Maj> {
let mut list = if variadicp {
Maj::symbol(&mut state, "rest")
} else {
Maj::nil()
};
for _ in 0..n {
list = Maj::cons(maj_gensym(&mut state),
list.clone());
}
list
}
fn wrap_primitive(prim: Gc<Maj>,
env: Gc<Maj>,
arglist: Gc<Maj>) -> Gc<Maj> {
// (lit closure <env> <arglist> ((<prim> . <arglist>)))
maj_list!(
Maj::lit(),
Maj::closure(),
env,
arglist.clone(),
maj_list!(
maj_cons(prim, arglist)))
}
fn curry_primitive(mut state: &mut MajState,
prim: Gc<Maj>,
num_args: i64,
num_params: i64,
args: Gc<Maj>,
env: Gc<Maj>,
variadicp: bool) -> Gc<Maj> {
if num_params == 0 {
return maj_err(
Maj::string(
"Cannot curry function without arguments"),
Maj::nil());
}
// Currying a primitive function involves generating
// a wrapper closure, then performing simple application.
let arglist = gen_arglist(&mut state, num_args, variadicp);
let wrapped = wrap_primitive(prim, env.clone(), arglist);
maj_apply(&mut state, wrapped, args, env)
}
[fn:6] Aqui, não fazemos distinção de a lambda-list
ser realmente uma
lista ou não, pois a mesma deve ser submetida a regras de aplicação
parcial e desestruturação, mas discutiremos essas regras a seguir.