Interpretador
A análise sintática (da ALisP) fica isolada na função parse(text), que devolve uma árvore com a estrutura de text de acordo com a gramática ALisP. Por exemplo, parse("(+ 2 (* 3 4))") devolve ('+', 2, ('*', 3, 4)).
O passo seguinte consistem em associar um valor (a semântica) a cada representação (à sintaxe), de forma a construir a ALisP com as caraterísticas pretendidas. Nomeadamente, pretende-se que a ALisP tenha:
- Expressões e Variáveis.
- Instruções e Sequências.
- Condicionais.
- Ciclos.
- Funções.
- Entrada/Saída.
além de, é claro, certas funções base.
Para correr da forma esperada, um programa precisa de um certo "contexto" onde é mantida e atualizada informação sobre as variáveis. Por exemplo, no programa
(
    (set a 42)
    (set b (- a 2))
    (write (b a))
)
na primeira instrução é criada uma variável com nome a e valor 42 e na segunda instrução o valor de a é consultado, operado e o resultado define o valor da recém-criada variável b. Por fim, tanto o valor de b como de a são consultados para definir a lista (b a) que será escrita na consola.
Funções Base
As funções base definem cálculos "diretos" que dependem apenas dos valores dos argumentos e não afetam o contexto do programa.
Entre as funções base estão incluidas as operações aritméticas +, -, * e /; as relações de ordem <, >, <= e >=; de igualdade == e !=; as operações booleanas and, or e not; as funções trigonométricas sin e asin; e as funções para decompor listas head, tail e nth. A escolha das funções básicas deve corresponder às necessidades que levam ao desenvolvimento da linguagem de programação. Por exemplo, o octave é uma linguagem de manipulação de matrizes numéricas e tem pré-definidas as respetivas operações.
No âmbito da ALisP é suficiente definir as funções base como um dicionário Python onde o nome da função está associado a um tuple com a aridade e uma expressão lambda (isto é, a versão Python das funções anónimas):
import math
BASE_FUNCTIONS = {
    #
    # Aritméticas
    #
    '+': (2, lambda x, y: x + y),
    '-': (2, lambda x, y: x - y),
    '*': (2, lambda x, y: x * y),
    '/': (2, lambda x, y: x / y),
    #
    # Ordem
    #
    '>': (2, lambda x, y: x > y),
    '>=': (2, lambda x, y: x >= y),
    '<': (2, lambda x, y: x < y),
    '<=': (2, lambda x, y: x <= y),
    #
    # Igualdade
    #
    '==': (2, lambda x, y: x == y),
    '!=': (2, lambda x, y: x != y),
    #
    # Booleanas
    #
    'not': (1, lambda x: not x),
    'and': (2, lambda x, y: x and y),
    'or': (2, lambda x, y: x or y),
    #
    # Trigonométricas
    #
    'sin': (1, lambda x: math.sin(x)),
    'asin': (1, lambda x: math.asin(x)),
    #
    # Listas
    #
    'head': (1, lambda x: x[0]),
    'tail': (1, lambda x: x[1:]),
    'nth': (2, lambda n, x: x[n]),
}
Esta representação das funções base pode ser facilmente modificada de forma a proporcionar, à partida, instruções adequadas ao domínio que se pretende trabalhar. Por exemplo, para computação gráfica 2D as funções base podem incluir os controlos de uma "tartaruga", forward, turn, up, down e set_color.
Contexto
O contexto é, essencialmente, um dicionário que associa o nome de cada variável ao respetivo valor. Além disso, é necessário considerar que as funções definem variáveis locais mas também têm acesso às variáveis externas.
Em
(seq
    (set x 21)
    (set dobro (fn (x)
        (* x 2)
    ))
    (set x (- x 1))
    (x (dobro 10))
)
a variável x é local e externa em dobro. Na última linha, (x (dobro 10)), tem o valor 20 no primeiro elemento e, quando a função dobro é evocada, é usado o valor local, 10, passado como argumento. O papel da instrução seq é esclarecido mais tarde.
A implementação do contexto é:
class ALisPContext:
    def __init__(self, outer):
        self.outer = outer
        self.local = dict()
    def defines(self, key):
        return key in self.local.keys() or key in self.outer.keys()
    def set(self, var_name, var_value):
        self.local[var_name] = var_value
    def keys(self):
        return self.local.keys() | self.outer.keys()
    def __getitem__(self, key):
        return self.value(key)
    def value(self, name):
        result = None
        #
        if name in self.local.keys():
            result = self.local[name]
        elif name in self.outer.keys():
            result = self.outer[name]
        elif name == 'True':
            return True
        elif name == 'False':
            return False
        elif is_atomic(name): # 42, -4.2, ola
            result = atom_value(name)
        #
        return result
ALisPContextUma instânciacdeALisPContext:
É construída em relação a um contexto "externo" (que pode ser o dicionário vazio,
{}).
c.set(var_name, var_value)define uma associação da variávelvar_nameao valorvar_value.
c.defines(key)testa se o nomekeytem uma associação, local ou externa.
c.keys()devolve o conjunto das chaves, locais ou externas, conhecidas.
c[atom]ouc.value(atom)devolve, por ordem:
- O valor associado a
namese existe essa associação a) local ou b) no contexto externo.- Se
nameé"True"ou"False"os valores booleanos correspondentes.- Se
nameé atómico (de acordo comis_atom) o respetivo valor calculado poratom_value.
None.
O método interessante de ALisPContext é value que, além de obter os valores associados a uma variável local ou externa, ainda proporciona os valores dos átomos e das constantes booleanas.
Valoração
A valoração consiste em calcular o valor de uma representação, isto é, definir uma semântica da (representação) sintaxe.
Esse cálculo depende de um contexto, que mantém e atualiza os valores das variáveis.
Por exemplo, A palavra 2 + (3 * 4) tem  representação ('+', 2, ('*', 3, 4)), obtida pelo analisador sintático. A esta representação corresponde o valor 14, que resulta de aplicar as respetivas operações aritméticas em cada nó, a começar pelas folhas.
Resumidamente, 14 é a semântica da sintaxe 2 + (3 * 4) e é calculada usando a representação intermédia ('+', 2, ('*', 3, 4)).
Em cada nó o conteúdo (daqui em diante representado por op) define a operação e os descendentes vão definir os valores dos argumentos (daqui em diante, args) dessa operação:
eval(('+', 2, ('*', 3, 4))) = 
    soma(eval(2), eval(('*', 3, 4))) =
    soma(2, mult(eval(3), eval(4))) =
    soma(2, mult(3, 4)) =
    soma(2, 12) =
    14
A valoração é uma função recursiva que, em cada nó
(op, *args), calcula o valor dos argumentos*argse, depois, conforme o valor deop, combina esses valores no resultado correspondente.Porém, um programa a correr define e consulta variáveis, escolhe ramos num condicional, etc.
O cálculo da valoração depende do contexto e das funções base. Um esquema incompleto da implementação segundo esses pressupostos será, por exemplo:
def eval(expr, context=None, ops=BASE_FUNCTIONS):
    if context is None:
        context = ALisPContext({})
    #
    #   expr is ATOM
    #
    context_value = context.value(expr)
    if context_value is not None:
        return context_value
    #
    #   expr is LIST
    #
    elif is_list(expr) and expr != NIL:
        #
        #   expr = (op args...)
        #
        op = head(expr)
        args = tail(expr)
        #
        # Use op and len(args) to do the right step
        # 
        if op in ops.keys():
            # BASE FUNCTIONS
        elif op == 'write' and len(args) == 1:
            # WRITE: (write MESSAGE)
        elif op == 'read' and len(args) == 0:
            # READ: (read)
        elif op == 'fn' and len(args) == 2:
            # FUNCTIONS: (fn ARGS INSTR)
        elif op == 'set' and len(args) == 2:
            # DEFINE/UPDATE VARIABLES: (set VAR VALUE) 
        elif op == 'while' and len(args) == 2:
            # CYCLES: (while GUARD INSTR)
        elif op == 'if' and len(args) == 3:
            # CONDITIONALS: (if COND TRUE_INSTR FALSE_INSTR)
        elif op == 'seq': 
            # SEQUENCE OF INSTRUCTIONS (LAST VALUE)
            # (seq *INSTR)
        elif is_list(op):
            # DEFER: (OP *ARGS)
        elif context.defines(op):
            # CONSULT VARIABLE: VAR
        else:
            # FALLBACK: (TERMINAL *REST)
    #
    #   expr is ALIEN... maybe raise error?
    #
    else:
        return expr
Funções Base
As funções base proporcionam alguma funcionalidade inicial e também uma ponte à linguagem hospedeira, o
Pythonneste caso.
Para calcular o valor de uma função base como, por exemplo, (+ 2 3), fica op == "+", args == (2, 3) e os passos são:
- Verificar se op(neste caso,+) é uma chave no dicionário das funções base (ops).
- Validar a aridade (if len(args) >= arity).
- Chamar recursivamente evalnos argumentos:val_args = tuple(eval(x, context, ops) for x in args[:arity]).
- Devolver o valor da função associada a opquando aplicada aos valores dos argumentos:return func(*val_args).
#
# BASE FUNCTIONS
#
if op in ops.keys():
    arity, func = ops[op]
    if len(args) >= arity:
        val_args = tuple(eval(x, context, ops) for x in args[:arity])
        return func(*val_args)
    else:
        return None
Entrada/Saída
As funções de entrada e saída permitem interações com o utilizador via uma consola.
A sintaxe é:
(write MESSAGE)em queMESSAGEé um valor qualquer a ser escrito na consola.
(read)para ler um valor da consola.
- A "real" interação ocorre via as funções correspondentes no Python:printparawriteeinputpararead.
- No caso da saída (write):- É admitido exatamente um argumento.
- Recursivamente, é calculado o valor desse argumento.
- O valor, que é um tuple, é enviado para a consola (print) numa sintaxe compatível comALisP(expr_repr).
 
- Para a entrada (read):- Não são admitidos argumentos.
- É lido o texto da consola via input.
- Esse texto é analisado (parse).
- Recursivamente, é calculado o valor da representação obtida na análise anterior.
 
#
# WRITE
#
elif op == 'write' and len(args) == 1:
    value = eval(args[0], context, ops)
    print(expr_repr(value))
    return value
#
# READ
#
elif op == 'read' and len(args) == 0:
    user_input = input()
    user_prog = parse(user_input)
    value = eval(user_prog, context, ops)
    return value
Funções
As funções, em
ALisPsão valores como as listas, números ou textos.A sintaxe é
(fn ARGS INSTR)em quefné a instrução,ARGSé uma lista de variáveis eINSTRé uma lista de instruções.Depois de criada uma função pode ser guardada numa variável para uso posterior.
Por exemplo:
(
    (set succ (fn (x) (+ x 1)))
    (succ 41)
    (succ 0)
)
As funções são o único caso em que eval não é recursiva. No exemplo acima:
- Em (fn (x) (+ x 1))é definido um certo valor.
- No interpretador esse valor é uma instância da classe Lambda.
- Essa instância é construída com os argumentos (x,)e('+', 'x', 1)que são as representações de"(x)"e"(+ x 1)"respetivamente.
- O valor do passo anterior é guardado na variável succ.
- Mais tarde, na linha seguinte, (succ 41):- É criado um contexto local, onde xfica com o valor41.
- Nesse contexto é calculado o valor de (+ x 1), usandoeval.
 
- É criado um contexto local, onde 
- Na linha (succ 0)estes passos são repetidos, mas num novo contexto local em quextem valor0.
A criação do valor Lambda ocorre nas seguintes linhas:
#
# FUNCTIONS
#
elif op == 'fn' and len(args) == 2:
    fn_args = args[0]
    fn_expr = args[1]
    return Lambda(fn_args, fn_expr)
O cálculo das evocações é tratado mais tarde, com as variáveis.
Copiar
Um valor é copiado para uma variável que fica registada no contexto atual. Mais tarde esse valor pode ser recuperado como o valor da variável.
A sintaxe é
(set VAR VALUE)em queVARé um identificador eVALUEuma qualquer expressãoALisP.
- Por exemplo, o seguinte programa tem valor 42:
(seq
    (set bad-answer 20)
    (set almost-good-answer (* 2 bad-answer))
    (+ almost-good-answer 2)
)
No lado do interpretador as instruções set atualizam o contexto da seguinte forma:
#
# DEFINE/UPDATE VARIABLES
#
elif op == 'set' and len(args) == 2:
    var_name = uq(args[0]) # Inelegant "dequotation" :(
    var_value = eval(args[1], context, ops)
    context.set(var_name, var_value)
    return var_value
Ciclos
Um ciclo repete um bloco de instruções enquanto uma certa condição é verdadeira.
A sintaxe é
(while GUARD INSTR)em queGUARDé uma condição eINSTRum bloco de instruções.
- Note bem que a ALisPtem valores booleanosTrueeFalsemas a condição pode ser qualquer valor. Os valores que também valem comoFalsesão0,0.0e(). Todos os restantes valores valem comoTrue.
- Por exemplo, o ciclo seguinte escreve a "tabela" dos quadrados de 0 a 10:
(
    (set i 0)
    (while (<= i 10) (
        (write (i (* i i)))
        (set i (+ i 1))
    ))
)
A implementação dos ciclos ALisP usa os ciclos do Python:
#
# CYCLES
#
elif op == 'while' and len(args) == 2:
    guard = args[0]
    statements = args[1]
    last = NIL
    while eval(guard, context, ops):
        last = eval(statements, context, ops)
    return last
Condicionais
Um condicional corre um certo bloco de instruções, ou outro, conforme uma condição é verdadeira ou falsa.
A sintaxe é
(if COND TRUE_INSTR FALSE_INSTR)em queCONDé uma condição eTRUE_INSTReFALSE_INSTRsão blocos de instruções.
- Note bem que a sintaxe "força" três argumentos.
- Por exemplo, o valor de (seq (set a 10) (if (> a 0) positivo negativo))épositivo.
Tal como nos ciclos, os condicionais do ALisP usam os do Python:
#
# CONDITIONALS
#
elif op == 'if' and len(args) == 3:
    cond, seq_true, seq_false = args
    if eval(cond, context, ops):
        return eval(seq_true, context, ops)
    else:
        return eval(seq_false, context, ops)
Com esta implementação apenas é percorrido, e calculado o valor do bloco que corresponde ao valor da condição.
Sequências
Uma sequência de instruções corre cada uma, por ordem, e devolve o valor da última instrução. A sequência vazia,
(seq)vale a lista vazia,(), designada NIL.A sintaxe é
(seq *INSTR)em que*INSTRé uma sequência de instruções.
- Note bem que *INSTRnão é uma lista, mas os seus elementos.
- Por exemplo:
- O valor de (seq 0 1 2 3)é3.
- O valor de (seq (0 1 2 3))é(0 1 2 3).
- O valor de (seq (set a 40) (+ a 2))é42.
 
- O valor de 
A implementação do valor para as sequências é:
#
# SEQUENCE OF INSTRUCTIONS (RETURNS ONLY THE LAST VALUE)
#
elif op == 'seq':
    if len(args) == 0:
        return NIL
    else:
        seq = eval(args, context, ops)
        return seq[-1]
Diferimento
Na
ALisPo primeiro elemento de uma lista determina como é calculado o valor dessa lista. Pode-se evocar uma função, executar uma instrução ou apenas definir a lista dos valores.Quando o primeiro elemento é também uma lista é preciso começar por determinar o seu valor para seguir no cálculo do valor da lista parente.
Isto é, cálculo do valor da lista parente é diferido pelo valor da lista que é o primeiro elemento.
Por exemplo, o valor de:
- (1 2 3)é- (1 2 3).
- (+ 2 3)é- 5.
- (if True 42 24)é- 42.
- ((1 2 3) 2 3)é- ((1 2 3) 2 3).
- ((if True + *) 2 3)é- 5.
- ((if False + *) 2 3)é- 6.
Nos três últimos exemplos, 4., 5. e 6., o primeiro elemento da lista é uma lista que vai definir o modo de calcular o valor da lista parente.
No caso
((1 2 3) 2 3)o valor do primeiro elemento é(1 2 3)que não é nem uma função nem uma instrução. Tal como no exemplo 1., a lista parente tem apenas valores.Nos exemplos 5. e 6. o valor do primeiro elemento é, respetivamente,
+e*. Portanto, o valor da lista parente vai ser diferido, respetivamente, para(+ 2 3)e(* 2 3).
A implementação do cálculo diferido é:
#
# LIST OF INSTRUCTIONS (RETURNS ALL THE VALUES)
#
elif is_list(op):
    op_val = eval(op, context, ops)
    args_vals = tuple([eval(xi, context, ops) for xi in args])
    expr2 = (op_val,) + args_vals
    if op_val == op:
        return expr2
    else:
        return eval(expr2, context, ops)
O teste if op_val == op deteta se o cálculo (não) vai ser diferido.
Variáveis e Evocação de Funções
Uma variável pode referir qualquer valor
ALisP, guardado no contexto atual.Quando esse valor é um tipo básico, inteiro,
double, lista,stringou booleano, basta consultar o respetivo valor, guardado no contexto.Quando a variável refere uma função é preciso calcular o valor da função com os argumentos dados.
Por exemplo, o valor de:
- (seq (set a 2) (+ a 40)é- 42.
- (seq ((set a +)) (a 2 40)é- 42. O duplo- ((set a +))é um hack para isolar o valor de- (set a +), que é- +, uma operação.
- (seq (set a (fn (x y) (+ x y))) (a 2 40))é- 42. Esse valor resulta de aplicar a função- (fn (x y) (+ x y))aos argumentos- (2 40). O corpo e os argumentos desta função forma guardados no contexto, com nome- a.
A implementação:
#
# CONSULT VARIABLE
#
elif context.defines(op):
    val = eval(op, context, ops)
    #
    #   Function evocation
    #
    if isinstance(val, Lambda):
        arity = len(val.args)
        if arity <= len(args):
            local_context = AlispContext(context)
            for i in range(arity):
                local_context.set(
                    val.args[i],
                    eval(args[i], context, ops))
            return eval(val.expr, local_context, ops)
        else:
            return None
    #
    #   Other types (number, text, list...)
    #
    else:
        expr2 = (val, ) + args
        return eval(expr2, context, ops)
Fallback
Se a "operação" não é um dos casos anteriores deve ser um terminal.
Por exemplo, (4 (+ 1 1)). Neste caso o resultado será (4 2).
A implementação:
#
# FALLBACK
#
else:
    return (op,) + eval(args, context, ops)