UP | HOME

Contextos

Table of Contents

Trataremos agora do submódulo que diz respeito a contextos. Em Majestic Lisp, um contexto é impresso em formato de lista associativa, onde cada um dos elementos da lista será um cons; em cada um desses cons, o car corresponde a um símbolo, e o cdr corresponde a um valor.

Por exemplo, a lista associativa

((square . (lit closure nil (x) (* x x)))
 (y      . 90)
 (x      . 5J2/3))

é um contexto qualquer válido, que realiza as seguintes associações:

square = (lit closure nil (x) (* x x))
y      = 90
x      = 5J2/3

como podemos ver, o símbolo square está associado a uma certa função, o símbolo y está associado ao numero \(90\), e o símbolo x está associado ao número complexo \(5 + \frac{2}{3}i\).

A maior parte do código listado a seguir poderá ser encontrado em src/core/environment.rs, exceto onde for apontado.

1. Importações

Começaremos importando alguns objetos importantes, como a estrutura Maj, que descreve os tipos primitivos de nossa linguagem. Também teremos à mão a estrutura genérica para identificar objetos gerenciados pelo coletor de lixo.

use super::Maj;
use gc::Gc;

Algumas das operações que se seguem envolvem comparações de tipos nos objetos, ou compara dois objetos. Para tanto, podemos importar alguns predicados da nossa lista de axiomas, que servirão para esse tipo de comparação. Os predicados possuem uma interface voltada diretamente para uso no próprio interpretador, mas poderemos utilizá-los normalmente em Rust se utilizarmos o método to_bool do tipo Maj.

use crate::axioms::predicates::{
    maj_nilp,
    maj_eq,
    maj_proper_list_p,
    maj_symbolp
};

2. Extensão de contexto

O processo de extensão do contexto é um processo não-destrutivo, que toma um certo contexto, um certo símbolo e um valor qualquer, e cria um novo contexto tal que, em seu topo, haverá uma nova associação entre o símbolo e o valor supracitados, feita através da criação de uma célula cons.

Portanto, dado um contexto

((x . 2))

se quisermos adicionar uma nova associação tal que y = 3, teremos, ao final da operação, este exato novo contexto:

((y . 3) (x . 2))

A função pública maj_env_push realiza esse trabalho. Também é importante ressaltar que o processo de identificação de um contexto envolve verificar se o mesmo é uma lista adequada, e o símbolo também será verificado quanto a tal. O valor poderá ser qualquer coisa, portanto não será verificado.

A criação de um novo contexto não pressupõe cópia do anterior: apenas aproveitamos o mesmo contexto antigo e adicionamos uma nova associação em seu topo, sem realizar nenhum processo destrutivo.

pub fn maj_env_push(env: Gc<Maj>, sym: Gc<Maj>, val: Gc<Maj>) -> Gc<Maj> {
    let is_env = maj_proper_list_p(env.clone()).to_bool();
    let is_sym = maj_symbolp(sym.clone()).to_bool();
    
    if !is_env || !is_sym {
        Maj::nil()
    } else {
        Maj::cons(
            Maj::cons(sym, val),
            env)
    }
}

É interessante ressaltar que um contexto, por ser validado apenas pela ideia de lista adequada, também envolve o uso correto da parte do programador.

3. TODO Procura por símbolo em um contexto

Outro processo importante de ser mencionado para o contexto é a procura, que envolve procurar pelo valor associado a um símbolo no contexto. Para tanto, atravessa-se o contexto recursivamente, cons a cons, e verifica-se pela igualdade (eq) entre o símbolo procurado e o car da associação.

Caso o contexto seja completamente atravessado e nenhuma associação seja encontrada, trata-se de um erro. Retornamos aqui o símbolo nil para referência, mas isso poderá ser mudado em breve.

pub fn maj_env_assoc(env: Gc<Maj>, sym: Gc<Maj>) -> Gc<Maj> {
    use crate::axioms::primitives::maj_err;
    use crate::maj_list;
    let mut itr = env.clone();
    while !maj_nilp(itr.clone()).to_bool() {
        if let Maj::Cons { car: entry, cdr } = &*itr.clone() {
            if let Maj::Cons {
                car: symbol,
                cdr: _
            } = &*entry.clone() {
                if maj_eq(symbol.clone(), sym.clone()).to_bool() {
                    return entry.clone();
                }
            } else {
                panic!("All entries on an environment must be pairs");
            }
            itr = cdr.clone();
        } else {
            panic!("Environment is not an alist");
        }
    }
    maj_err(
        Maj::string("{} is unbound"),
        maj_list!(sym))
}
pub fn maj_env_lookup(env: Gc<Maj>, sym: Gc<Maj>) -> Gc<Maj> {
    use crate::axioms::predicates::maj_errorp;
    use crate::axioms::primitives::maj_cdr;
    let result = maj_env_assoc(env, sym);
    if !maj_errorp(result.clone()).to_bool() {
        maj_cdr(result)
    } else {
        result
    }
}

4. TODO União de contextos

pub fn maj_env_union(env1: Gc<Maj>, env2: Gc<Maj>) -> Gc<Maj> {
    use crate::axioms::{
        primitives::{ maj_car, maj_cdr },
        predicates::maj_errorp
    };
    let is_env_env1 = maj_proper_list_p(env1.clone()).to_bool();
    let is_env_env2 = maj_proper_list_p(env2.clone()).to_bool();
    if !is_env_env1 || !is_env_env2 {
        panic!("Attempted union of improper lists");
    }
    
    // Uniting two envs involves creating a new env with mixed bindings.
    // It must basically be env2 with env1 bindings substituting wherever
    // a substitution is needed.
    let mut iter = env2.clone();
    // 0. Reverse env2 (because of the way it works)
    let mut env2_bindings = vec![];
    while !maj_nilp(iter.clone()).to_bool() {
        env2_bindings.push(maj_car(iter.clone()));
        iter = maj_cdr(iter);
    }
    
    // 1. traverse for each bind2 on env2.
    let mut newenv = Maj::nil();
    for bind2 in env2_bindings.iter().rev() {
        // 2. if (sym in bind2) is defined in (bind1 in env1), collect bind1.
        let sym = maj_car(bind2.clone());
        let bind1 = maj_env_assoc(env1.clone(), sym);
        newenv = if !maj_errorp(bind1.clone()).to_bool() {
            Maj::cons(bind1, newenv)
        } else {
            //    2.5. otherwise collect bind2.
            Maj::cons(bind2.clone(), newenv)
        };
    }

    // 3. Add bindings on env1 that were not added
    iter = env1.clone();
    while !maj_nilp(iter.clone()).to_bool() {
        let binding = maj_car(iter.clone());
        let sym = maj_car(binding.clone());
        if maj_errorp(maj_env_assoc(newenv.clone(), sym.clone())).to_bool() {
            newenv = Maj::cons(binding, newenv);
        }
        iter = maj_cdr(iter);
    }

    newenv
}

Author: Lucas S. Vieira

Created: 2022-10-24 seg 00:27

Validate