UP | HOME

Aplicação

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;

1. TODO Aplicação genérica

  • 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))
    }
}

2. TODO Aplicação de clausuras

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 lista1 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:

  1. Uma lista de símbolos não-ligados na lambda-list, caso existam;
  2. 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"),
    }
}

3. TODO Aplicação de macros

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)
    }
}

4. TODO Aplicação de primitivas

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)
}

5. TODO Predicados auxiliares

Footnotes:

1

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.

Author: Lucas S. Vieira

Created: 2022-10-24 seg 00:27

Validate