Q-Expressions (expressões citadas) • Capítulo 10

Adicionando Recursos


Você vai notar que os capítulos seguintes seguirão um padrão em comum. Este padrão é a abordagem típica usada para adicionar recursos novos a uma linguagem. Consiste em uma sequência de passos que levam um novo recurso do começo ao fim. Estes passos estão listados abaixo, e são exatamente o que vamos fazer neste capítulo para introduzir um novo recurso chamado uma Q-Expression.

SintaxeAdiciona nova regra à gramática para o recurso.
RepresentaçãoAdiciona nova variação de tipo de dado para representar o recurso.
Análise sintática (parsing)Adiciona novas funções para ler o recurso da árvore de sintaxe abstrata (AST).
SemânticaAdiciona novas funções para avaliar e manipular o recurso.

Quoted Expressions


Neste capítulo vamos implementar um novo tipo de valor Lisp chamado uma Q-Expression.

Isto significa quoted expression (expressão citada), e é um tipo de expressão Lisp que não é avaliada pela mecânica padrão de Lisp. Quando encontradas pela função de avaliação, Q-expressions são deixadas exatamente como estão. Isto as torna ideal para determinados propósitos. Nós podemos usá-las para armazenar e manipular outros valores Lisp como números, símbolos, ou outras S-Expressions mesmo.

Depois que adicionarmos Q-Expressions vamos implementar um conjunto conciso de operadores para manipulá-las. Assim como os operadores aritméticos, estes se mostrarão fundamentais em como pensamos a respeito e como brincamos com expressões.

A sintaxe para Q-Expressions é muito similar à das S-Expressions. A única diferença é que em vez de parênteses (), Q-Expressions são envoltas em chaves {}. Podemos adicionar isto à nossa gramática conforme segue.

Nunca ouvi falar de Q-Expressions.

Q-Expressions não existem em outros Lisps. Outros Lisps usam Macros para parar a avaliação. Estas se parecem como funções normais, mas não avaliam seus argumentos. Existe uma macro especial chamada quote ' que pode ser usada para parar a avaliação de quase qualquer coisa. Esta é a inspiração para Q-Expressions, que só existem em nosso Lisp, e serão usadas no lugar de Macros para fazer todas as mesmas coisas e mais.

A maneira que usei S-Expression e Q-Expression neste livro é um leve abuso da terminologia, mas espero que este delito torne mais claro o comportamento do nosso Lisp.

mpc_parser_t* Number = mpc_new("number");
mpc_parser_t* Symbol = mpc_new("symbol");
mpc_parser_t* Sexpr  = mpc_new("sexpr");
mpc_parser_t* Qexpr  = mpc_new("qexpr");
mpc_parser_t* Expr   = mpc_new("expr");
mpc_parser_t* Lispy  = mpc_new("lispy");

mpca_lang(MPCA_LANG_DEFAULT,
  "                                                    \
    number : /-?[0-9]+/ ;                              \
    symbol : '+' | '-' | '*' | '/' ;                   \
    sexpr  : '(' <expr>* ')' ;                         \
    qexpr  : '{' <expr>* '}' ;                         \
    expr   : <number> | <symbol> | <sexpr> | <qexpr> ; \
    lispy  : /^/ <expr>* /$/ ;                         \
  ",
  Number, Symbol, Sexpr, Qexpr, Expr, Lispy);

Precisamos lembrar de atualizar nossa função de limpeza para lidar com a nova regra que adicionamos.

mpc_cleanup(6, Number, Symbol, Sexpr, Qexpr, Expr, Lispy);

Lendo Q-Expressions


Como Q-Expressions são tão similares às S-Expressions, muito do seu comportamento interno será o mesmo. Vamos reutilizar nossos campos de dados de S-Expression para representar Q-Expressions, mas ainda vamos precisar adicionar um tipo separado para a enumeração.

enum { LVAL_ERR, LVAL_NUM, LVAL_SYM, LVAL_SEXPR, LVAL_QEXPR };

Podemos adicionar um construtor para esta variação.

/* Um apontador para um novo lval Qexpr vazio */
lval* lval_qexpr(void) {
  lval* v = malloc(sizeof(lval));
  v->type = LVAL_QEXPR;
  v->count = 0;
  v->cell = NULL;
  return v;
}

Para imprimir e deletar Q-Expressions, fazemos essencialmente a mesma coisa que com S-Expressions. Podemos adicionar as linhas relevantes para nossas funções de imprimir e deletar conforme segue.

void lval_print(lval* v) {
  switch (v->type) {
    case LVAL_NUM:   printf("%li", v->num); break;
    case LVAL_ERR:   printf("Error: %s", v->err); break;
    case LVAL_SYM:   printf("%s", v->sym); break;
    case LVAL_SEXPR: lval_expr_print(v, '(', ')'); break;
    case LVAL_QEXPR: lval_expr_print(v, '{', '}'); break;
  }
}
void lval_del(lval* v) {

  switch (v->type) {
    case LVAL_NUM: break;
    case LVAL_ERR: free(v->err); break;
    case LVAL_SYM: free(v->sym); break;
    
    /* Caso Qexpr ou Sexpr, entao deleta todos elementos dentro */
    case LVAL_QEXPR:
    case LVAL_SEXPR:
      for (int i = 0; i < v->count; i++) {
        lval_del(v->cell[i]);
      }
      /* Tambem libera memoria alocada para conter os apontadores */
      free(v->cell);
    break;
  }
  
  free(v);
}

Usando estas mudanças simples podemos atualizar nossa função de leitura lval_read para conseguir ler Q-Expressions. Por que reusamos todos os campos de dados S-Expression para nosso tipo Q-Expression, podemos reusar todas as funções para S-Expressions como lval_add. Por isso, para ler Q-Expressions precisamos apenas adicionar um caso especial para construir uma Q-Expression vazia para lval_read logo abaixo onde detectamos e criamos S-Expressions vazias da árvore de sintaxe abstrata.

if (strstr(t->tag, "qexpr"))  { x = lval_qexpr(); }

Como não há maneira especial de avaliar Q-Expressions, não precisamos editar nenhuma das funções de avaliações. Nossas Q-Expressions devem estar prontas para experimentar. Compile e rode o programa. Tente usá-las como um novo tipo de dados e verifique que não estejam sendo avaliadas.

lispy> {1 2 3 4}
{1 2 3 4}
lispy> {1 2 (+ 5 6) 4}
{1 2 (+ 5 6) 4}
lispy> {{2 3 4} {1}}
{{2 3 4} {1}}
lispy>

Funções Builtins


Podemos ler Q-Expressions, mas elas ainda são inúteis. Precisamos de alguma maneira de manipulá-las.

Para isso podemos definir alguns operadores built-in (isto é, que já venham junto com a linguagem) para trabalhar com nosso tipo lista. Escolher um conjunto conciso de operadores é importante. Se implementarmos algumas operações fundamentais, podemos usá-las para definir novas operações sem precisar adicionar código C extra. Existem algumas maneiras de escolher estes operadores fundamentais, mas escolhi um conjunto que nos permitirá fazer tudo que precisamos. Eles são definidos a seguir.

listRecebe um ou mais argumentos e devolve uma nova Q-Expression contendo os argumentos
head
(cabeça)
Recebe uma Q-Expression e devolve uma Q-Expression com apenas o primeiro elemento
tail
(cauda)
Recebe uma Q-Expression e devolve uma Q-Expression com o primeiro elemento removido
join
(junta)
Recebe uma ou mais Q-Expressions e devolve uma Q-Expression delas unidas
evalRecebe uma Q-Expression e a avalia como se fosse uma S-Expression

Da mesma forma como para nossos operadores matemáticos, devemos adicionar estas funções como símbolos válidos possíveis. Depois, poderemos definir seu comportamento de maneira similar a builtin_op.

mpca_lang(MPCA_LANG_DEFAULT,
  "                                                        \
    number : /-?[0-9]+/ ;                                  \
    symbol : \"list\" | \"head\" | \"tail\"                \
           | \"join\" | \"eval\" | '+' | '-' | '*' | '/' ; \
    sexpr  : '(' <expr>* ')' ;                             \
    qexpr  : '{' <expr>* '}' ;                             \
    expr   : <number> | <symbol> | <sexpr> | <qexpr> ;     \
    lispy  : /^/ <expr>* /$/ ;                             \
  ",
  Number, Symbol, Sexpr, Qexpr, Expr, Lispy)

Primeira tentativa


Nossas funções builtins devem ter a mesma interface que builtin_op. Isto significa que os argumentos devem ser empacotados em uma S-Expression que a função deve usar e a seguir deletar. Elas devem devolver um novo lval* como resultado da avaliação.

A funcionalidade de pegar a cabeça ou a cauda de uma Q-Expression propriamente dita não deve ser muito difícil para nós. Podemos usar as funções que já definimos para S-Expressions como lval_take e lval_pop. Mas como builtin_op, precisamos checar que as entradas que recebermos sejam válidas.

Vamos dar uma olhada em head e tail primeiro. Existem algumas condições para as quais estas funções não podem funcionar. Antes de tudo, precisamos nos assegurar que elas são somente passadas como argumento único, e que este argumento é uma Q-Expression. Então precisamos nos certificar que esta Q-Expression não está vazia e realmente tem alguns elementos.

A função head pode repetidamente extrair e deletar o item no índice 1 até que não há sobre mais nada na lista.

A função tail é ainda mais simples. Ela pode extrair e deletar o item no índice 0, deixando a cauda sobrando. Uma tentativa inicial para implementar estas funções pode se parecer com isso:

lval* builtin_head(lval* a) {
  /* Checa condicoes de erro */
  if (a->count != 1) {
    lval_del(a);
    return lval_err("Function 'head' passed too many arguments!");
  }
  
  if (a->cell[0]->type != LVAL_QEXPR) {
    lval_del(a);
    return lval_err("Function 'head' passed incorrect types!");
  }
  
  if (a->cell[0]->count == 0) {
    lval_del(a);
    return lval_err("Function 'head' passed {}!");
  }

  /* Do contrario, obtem o primeiro argumento */
  lval* v = lval_take(a, 0);

  /* Deleta todos elementos que nao sao cabeca e devolve */
  while (v->count > 1) { lval_del(lval_pop(v, 1)); }
  return v;
}
lval* builtin_tail(lval* a) {
  /* Checa condicoes de erro */
  if (a->count != 1) {
    lval_del(a);
    return lval_err("Function 'tail' passed too many arguments!");
  }
  
  if (a->cell[0]->type != LVAL_QEXPR) {
    lval_del(a);
    return lval_err("Function 'tail' passed incorrect types!");
  }  
  
  if (a->cell[0]->count == 0) {
    lval_del(a);
    return lval_err("Function 'tail' passed {}!");
  }

  /* Pega primeiro argumento */
  lval* v = lval_take(a, 0);

  /* Deleta primeiro elemento e devolve */
  lval_del(lval_pop(v, 0));
  return v;
}

Macros


strawberry

Morango • Uma MACRO deliciosa.

Estas funções head e tail fazem a coisa certa, mas o código é bem obscuro, e longo. Há tanta checagem de erro que a funcionalidade em si é difícil de ver. Um método que podemos usar para limpá-la é usar uma Macro.

Macros são comandos para o pré-processador para criarem coisas-que-parecem-funções que são avaliadas antes do programa ser compilado. Pode ser usados para muitas coisas diferentes, uma delas é o que precisamos fazer aqui, código de limpeza.

Macros funcionam recebendo alguns argumentos (que podem ser quase que qualquer coisa), e copiam e colam estes para um determinado padrão. Mudando o padrão ou os argumentos, podemos alterar o código gerado pela macro. Para definir macros, usamos a diretiva #define do pré-processador. Depois sito, escrevemos o nome da macro, seguido dos nomes dos argumentos entre parênteses. Depois disto, o padrão é especificado, para declarar qual código deve ser gerado para os dados argumentos.

Podemos projetar uma macro para ajudar com nossas condições de erro chamada LASSERT. Macros são tipicamente nomes dados em maiúsculas para ajudar a distingui-las de funções C normais. Esta macro recebe três argumentos args, cond, e err. Ela então gera código como mostrado no lado direito, mas como estas variáveis coladas nos lugares onde estão nomeadas. Este padrão é bem adequado para todas nossas condições de erro.

#define LASSERT(args, cond, err) \
  if (!(cond)) { lval_del(args); return lval_err(err); }

Podemos usar isto para mudar como nossas funções acima são escritas, sem realmente mudar o código gerado pelo compilador. Isto torna mais fácil de ler para o programador, e economiza um pouco de digitação. O restante das condições de erro para nossas funções devem se tornar mais fáceis de escrever também!

Head & Tail

Usando isto nossas funções head e tail são definidas como segue. Note quão mais clara é sua funcionalidade real.

lval* builtin_head(lval* a) {
  LASSERT(a, a->count == 1,
    "Function 'head' passed too many arguments!");
  LASSERT(a, a->cell[0]->type == LVAL_QEXPR,
    "Function 'head' passed incorrect type!");
  LASSERT(a, a->cell[0]->count != 0,
    "Function 'head' passed {}!");

  lval* v = lval_take(a, 0);  
  while (v->count > 1) { lval_del(lval_pop(v, 1)); }
  return v;
}
lval* builtin_tail(lval* a) {
  LASSERT(a, a->count == 1,
    "Function 'tail' passed too many arguments!");
  LASSERT(a, a->cell[0]->type == LVAL_QEXPR,
    "Function 'tail' passed incorrect type!");
  LASSERT(a, a->cell[0]->count != 0,
    "Function 'tail' passed {}!");

  lval* v = lval_take(a, 0);  
  lval_del(lval_pop(v, 0));
  return v;
}

List & Eval

A função list é simples. Ela apenas converte a S-Expression da entrada em uma Q-Expression e a devolve.

A função eval é similar, ao contrário. Ela recebe como entrada uma única Q-Expression, que converte para uma S-Expression, e a avalia usando lval_eval.

lval* builtin_list(lval* a) {
  a->type = LVAL_QEXPR;
  return a;
}
lval* builtin_eval(lval* a) {
  LASSERT(a, a->count == 1,
    "Function 'eval' passed too many arguments!");
  LASSERT(a, a->cell[0]->type == LVAL_QEXPR,
    "Function 'eval' passed incorrect type!");

  lval* x = lval_take(a, 0);
  x->type = LVAL_SEXPR;
  return lval_eval(x);
}

Join

A função join é nossa última função a definir.

Diferente das outras, ela pode receber múltiplos argumentos, então sua estrutura se parece um pouco como a da builtin_op. Primeiro verificamos que todos os argumentos são Q-Expressions e então às unimos uma por uma. Para fazer isso, usamos a função lval_join. Isto funciona repetidamente extraindo cada item do segundo e adicionando-o ao primeiro até que o segundo esteja vazio. Então deletamos o segundo e devolvemos o primeiro.

lval* builtin_join(lval* a) {

  for (int i = 0; i < a->count; i++) {
    LASSERT(a, a->cell[i]->type == LVAL_QEXPR,
      "Function 'join' passed incorrect type.");
  }

  lval* x = lval_pop(a, 0);

  while (a->count) {
    x = lval_join(x, lval_pop(a, 0));
  }

  lval_del(a);
  return x;
}
lval* lval_join(lval* x, lval* y) {

  /* Para cada celula em 'y', adiciona-a a 'x' */
  while (y->count) {
    x = lval_add(x, lval_pop(y, 0));
  }

  /* Deleta o 'y' vazio e devolve 'x' */
  lval_del(y);  
  return x;
}

Busca de Builtins


Temos agora todas nossas funções builtin definidas. Precisamos fazer uma função que possa chamar a builtin correta dependendo do símbolo que ela encontrar na avaliação. Podemos fazer isso usando strcmp e strstr.

lval* builtin(lval* a, char* func) {
  if (strcmp("list", func) == 0) { return builtin_list(a); }
  if (strcmp("head", func) == 0) { return builtin_head(a); }
  if (strcmp("tail", func) == 0) { return builtin_tail(a); }
  if (strcmp("join", func) == 0) { return builtin_join(a); }
  if (strcmp("eval", func) == 0) { return builtin_eval(a); }
  if (strstr("+-/*", func)) { return builtin_op(a, func); }
  lval_del(a);
  return lval_err("Unknown Function!");
}

A seguir, podemos mudar nossa linha de avaliação em lval_eval_sexpr para chamar builtin em vez de builtin_op.

/* Chama builtin com operador */
lval* result = builtin(v, f->sym);
lval_del(f);
return result;

Finalmente Q-Expressions devem estar completamente suportadas em nossa linguagem. Compile e rode a última versão e veja o que você pode fazer com os novos operadores de lista. Experimente colocar código e símbolo dentro das nossas listas e avaliá-las em diferentes maneiras. A capacidade de colocar S-Expressions dentro de uma lista usando Q-Expressions é muito legal. Significa que podemos tratar código como dados mesmo. Isto é emblemático dos Lisps, e algo que não pode ser feito realmente em linguagens como C!

lispy> list 1 2 3 4
{1 2 3 4}
lispy> {head (list 1 2 3 4)}
{head (list 1 2 3 4)}
lispy> eval {head (list 1 2 3 4)}
{1}
lispy> tail {tail tail tail}
{tail tail}
lispy> eval (tail {tail tail {5 6 7}})
{6 7}
lispy> eval (head {(+ 1 2) (+ 10 20)})
3

Referência


#include "mpc.h"

#ifdef _WIN32

static char buffer[2048];

char* readline(char* prompt) {
  fputs(prompt, stdout);
  fgets(buffer, 2048, stdin);
  char* cpy = malloc(strlen(buffer)+1);
  strcpy(cpy, buffer);
  cpy[strlen(cpy)-1] = '\0';
  return cpy;
}

void add_history(char* unused) {}

#else
#include <editline/readline.h>
#include <editline/history.h>
#endif

/* Add QEXPR as possible lval type */
enum { LVAL_ERR, LVAL_NUM, LVAL_SYM, LVAL_SEXPR, LVAL_QEXPR };

typedef struct lval {
  int type;
  long num;
  char* err;
  char* sym;
  int count;
  struct lval** cell;
} lval;

lval* lval_num(long x) {
  lval* v = malloc(sizeof(lval));
  v->type = LVAL_NUM;
  v->num = x;
  return v;
}

lval* lval_err(char* m) {
  lval* v = malloc(sizeof(lval));
  v->type = LVAL_ERR;
  v->err = malloc(strlen(m) + 1);
  strcpy(v->err, m);
  return v;
}

lval* lval_sym(char* s) {
  lval* v = malloc(sizeof(lval));
  v->type = LVAL_SYM;
  v->sym = malloc(strlen(s) + 1);
  strcpy(v->sym, s);
  return v;
}

lval* lval_sexpr(void) {
  lval* v = malloc(sizeof(lval));
  v->type = LVAL_SEXPR;
  v->count = 0;
  v->cell = NULL;
  return v;
}

/* A pointer to a new empty Qexpr lval */
lval* lval_qexpr(void) {
  lval* v = malloc(sizeof(lval));
  v->type = LVAL_QEXPR;
  v->count = 0;
  v->cell = NULL;
  return v;
}

void lval_del(lval* v) {

  switch (v->type) {
    case LVAL_NUM: break;
    case LVAL_ERR: free(v->err); break;
    case LVAL_SYM: free(v->sym); break;
    
    /* If Qexpr or Sexpr then delete all elements inside */
    case LVAL_QEXPR:
    case LVAL_SEXPR:
      for (int i = 0; i < v->count; i++) {
        lval_del(v->cell[i]);
      }
      /* Also free the memory allocated to contain the pointers */
      free(v->cell);
    break;
  }
  
  free(v);
}

lval* lval_add(lval* v, lval* x) {
  v->count++;
  v->cell = realloc(v->cell, sizeof(lval*) * v->count);
  v->cell[v->count-1] = x;
  return v;
}

lval* lval_pop(lval* v, int i) {
  lval* x = v->cell[i];
  memmove(&v->cell[i], &v->cell[i+1],
    sizeof(lval*) * (v->count-i-1));
  v->count--;
  v->cell = realloc(v->cell, sizeof(lval*) * v->count);
  return x;
}

lval* lval_join(lval* x, lval* y) {

  while (y->count) {
    x = lval_add(x, lval_pop(y, 0));
  }

  lval_del(y);  
  return x;
}

lval* lval_take(lval* v, int i) {
  lval* x = lval_pop(v, i);
  lval_del(v);
  return x;
}

void lval_print(lval* v);

void lval_expr_print(lval* v, char open, char close) {
  putchar(open);
  for (int i = 0; i < v->count; i++) {
    
    lval_print(v->cell[i]);
    
    if (i != (v->count-1)) {
      putchar(' ');
    }
  }
  putchar(close);
}

void lval_print(lval* v) {
  switch (v->type) {
    case LVAL_NUM:   printf("%li", v->num); break;
    case LVAL_ERR:   printf("Error: %s", v->err); break;
    case LVAL_SYM:   printf("%s", v->sym); break;
    case LVAL_SEXPR: lval_expr_print(v, '(', ')'); break;
    case LVAL_QEXPR: lval_expr_print(v, '{', '}'); break;
  }
}

void lval_println(lval* v) { lval_print(v); putchar('\n'); }

#define LASSERT(args, cond, err) \
  if (!(cond)) { lval_del(args); return lval_err(err); }
  
lval* lval_eval(lval* v);

lval* builtin_list(lval* a) {
  a->type = LVAL_QEXPR;
  return a;
}

lval* builtin_head(lval* a) {
  LASSERT(a, a->count == 1,
    "Function 'head' passed too many arguments.");
  LASSERT(a, a->cell[0]->type == LVAL_QEXPR,
    "Function 'head' passed incorrect type.");
  LASSERT(a, a->cell[0]->count != 0,
    "Function 'head' passed {}.");
  
  lval* v = lval_take(a, 0);  
  while (v->count > 1) { lval_del(lval_pop(v, 1)); }
  return v;
}

lval* builtin_tail(lval* a) {
  LASSERT(a, a->count == 1,
    "Function 'tail' passed too many arguments.");
  LASSERT(a, a->cell[0]->type == LVAL_QEXPR,
    "Function 'tail' passed incorrect type.");
  LASSERT(a, a->cell[0]->count != 0,
    "Function 'tail' passed {}.");

  lval* v = lval_take(a, 0);  
  lval_del(lval_pop(v, 0));
  return v;
}

lval* builtin_eval(lval* a) {
  LASSERT(a, a->count == 1,
    "Function 'eval' passed too many arguments.");
  LASSERT(a, a->cell[0]->type == LVAL_QEXPR,
    "Function 'eval' passed incorrect type.");
  
  lval* x = lval_take(a, 0);
  x->type = LVAL_SEXPR;
  return lval_eval(x);
}

lval* builtin_join(lval* a) {

  for (int i = 0; i < a->count; i++) {
    LASSERT(a, a->cell[i]->type == LVAL_QEXPR,
      "Function 'join' passed incorrect type.");
  }
  
  lval* x = lval_pop(a, 0);
  
  while (a->count) {
    x = lval_join(x, lval_pop(a, 0));
  }
  
  lval_del(a);
  return x;
}

lval* builtin_op(lval* a, char* op) {
  
  for (int i = 0; i < a->count; i++) {
    if (a->cell[i]->type != LVAL_NUM) {
      lval_del(a);
      return lval_err("Cannot operate on non-number!");
    }
  }
  
  lval* x = lval_pop(a, 0);
  if ((strcmp(op, "-") == 0) && a->count == 0) { x->num = -x->num; }
  
  while (a->count > 0) {
  
    lval* y = lval_pop(a, 0);
    
    if (strcmp(op, "+") == 0) { x->num += y->num; }
    if (strcmp(op, "-") == 0) { x->num -= y->num; }
    if (strcmp(op, "*") == 0) { x->num *= y->num; }
    if (strcmp(op, "/") == 0) {
      if (y->num == 0) {
        lval_del(x); lval_del(y);
        x = lval_err("Division By Zero.");
        break;
      }
      x->num /= y->num;
    }
    
    lval_del(y);
  }
  
  lval_del(a);
  return x;
}

lval* builtin(lval* a, char* func) {
  if (strcmp("list", func) == 0) { return builtin_list(a); }
  if (strcmp("head", func) == 0) { return builtin_head(a); }
  if (strcmp("tail", func) == 0) { return builtin_tail(a); }
  if (strcmp("join", func) == 0) { return builtin_join(a); }
  if (strcmp("eval", func) == 0) { return builtin_eval(a); }
  if (strstr("+-/*", func)) { return builtin_op(a, func); }
  lval_del(a);
  return lval_err("Unknown Function!");
}

lval* lval_eval_sexpr(lval* v) {
  
  for (int i = 0; i < v->count; i++) {
    v->cell[i] = lval_eval(v->cell[i]);
  }
  
  for (int i = 0; i < v->count; i++) {
    if (v->cell[i]->type == LVAL_ERR) { return lval_take(v, i); }
  }
  
  if (v->count == 0) { return v; }
  
  if (v->count == 1) { return lval_take(v, 0); }
  
  lval* f = lval_pop(v, 0);
  if (f->type != LVAL_SYM) {
    lval_del(f); lval_del(v);
    return lval_err("S-expression Does not start with symbol.");
  }
  
  /* Call builtin with operator */
  lval* result = builtin(v, f->sym);
  lval_del(f);
  return result;
}

lval* lval_eval(lval* v) {
  if (v->type == LVAL_SEXPR) { return lval_eval_sexpr(v); }
  return v;
}

lval* lval_read_num(mpc_ast_t* t) {
  errno = 0;
  long x = strtol(t->contents, NULL, 10);
  return errno != ERANGE ? lval_num(x) : lval_err("invalid number");
}

lval* lval_read(mpc_ast_t* t) {
  
  if (strstr(t->tag, "number")) { return lval_read_num(t); }
  if (strstr(t->tag, "symbol")) { return lval_sym(t->contents); }
  
  lval* x = NULL;
  if (strcmp(t->tag, ">") == 0) { x = lval_sexpr(); } 
  if (strstr(t->tag, "sexpr"))  { x = lval_sexpr(); }
  if (strstr(t->tag, "qexpr"))  { x = lval_qexpr(); }
  
  for (int i = 0; i < t->children_num; i++) {
    if (strcmp(t->children[i]->contents, "(") == 0) { continue; }
    if (strcmp(t->children[i]->contents, ")") == 0) { continue; }
    if (strcmp(t->children[i]->contents, "}") == 0) { continue; }
    if (strcmp(t->children[i]->contents, "{") == 0) { continue; }
    if (strcmp(t->children[i]->tag,  "regex") == 0) { continue; }
    x = lval_add(x, lval_read(t->children[i]));
  }
  
  return x;
}

int main(int argc, char** argv) {
  
  mpc_parser_t* Number = mpc_new("number");
  mpc_parser_t* Symbol = mpc_new("symbol");
  mpc_parser_t* Sexpr  = mpc_new("sexpr");
  mpc_parser_t* Qexpr  = mpc_new("qexpr");
  mpc_parser_t* Expr   = mpc_new("expr");
  mpc_parser_t* Lispy  = mpc_new("lispy");
  
  mpca_lang(MPCA_LANG_DEFAULT,
    "                                                    \
      number : /-?[0-9]+/ ;                              \
      symbol : \"list\" | \"head\" | \"tail\" | \"eval\" \
             | \"join\" | '+' | '-' | '*' | '/' ;        \
      sexpr  : '(' <expr>* ')' ;                         \
      qexpr  : '{' <expr>* '}' ;                         \
      expr   : <number> | <symbol> | <sexpr> | <qexpr> ; \
      lispy  : /^/ <expr>* /$/ ;                         \
    ",
    Number, Symbol, Sexpr, Qexpr, Expr, Lispy);
  
  puts("Lispy Version 0.0.0.0.6");
  puts("Press Ctrl+c to Exit\n");
  
  while (1) {
  
    char* input = readline("lispy> ");
    add_history(input);
    
    mpc_result_t r;
    if (mpc_parse("<stdin>", input, Lispy, &r)) {
      lval* x = lval_eval(lval_read(r.output));
      lval_println(x);
      lval_del(x);
      mpc_ast_delete(r.output);
    } else {    
      mpc_err_print(r.error);
      mpc_err_delete(r.error);
    }
    
    free(input);
    
  }
  
  mpc_cleanup(6, Number, Symbol, Sexpr, Qexpr, Expr, Lispy);
  
  return 0;
}

Metas bônus


  • › Quais são os quatro passos típicos para adicionar novos recursos à uma linguagem?
  • › Crie uma Macro especificamente para testar número incorreto de argumentos.
  • › Crie uma Macro especificamente para testar ser chamado com uma lista vazia.
  • › Adicione uma função builtin cons que receba um valor e uma Q-Expression e o adicione-o ao começo.
  • › Adicione uma função builtin len que devolva o número de elementos em uma Q-Expression.
  • › Adicione uma função builtin init que devolva tudo de uma Q-Expression exceto o último elemento.

Navegação

‹ S-Expressions

• Conteúdo •

Variáveis ›