Environment


Our environment structure must encode a list of relationships between names and values. There are many ways to build a structure that can do this sort of thing. We are going to go for the simplest possible method that works well. This is to use two lists of equal length. One is a list of lval*, and the other is a list of char*. Each entry in one list has a corresponding entry in the other list at the same position.

We’ve already forward declared our lenv struct, so we can define it as follows.

  1. struct lenv {
  2. int count;
  3. char** syms;
  4. lval** vals;
  5. };

We need some functions to create and delete this structure. These are pretty simple. Creation initialises the struct fields, while deletion iterates over the items in both lists and deletes or frees them.

  1. lenv* lenv_new(void) {
  2. lenv* e = malloc(sizeof(lenv));
  3. e->count = 0;
  4. e->syms = NULL;
  5. e->vals = NULL;
  6. return e;
  7. }
  1. void lenv_del(lenv* e) {
  2. for (int i = 0; i < e->count; i++) {
  3. free(e->syms[i]);
  4. lval_del(e->vals[i]);
  5. }
  6. free(e->syms);
  7. free(e->vals);
  8. free(e);
  9. }

Next we can create two functions that either get values from the environment or put values into it.

To get a value from the environment we loop over all the items in the environment and check if the given symbol matches any of the stored strings. If we find a match we can return a copy of the stored value. If no match is found we should return an error.

The function for putting new variables into the environment is a little bit more complex. First we want to check if a variable with the same name already exists. If this is the case we should replace its value with the new one. To do this we loop over all the existing variables in the environment and check their name. If a match is found we delete the value stored at that location, and store there a copy of the input value.

If no existing value is found with that name, we need to allocate some more space to put it in. For this we can use realloc, and store a copy of the lval and its name at the newly allocated locations.

  1. lval* lenv_get(lenv* e, lval* k) {
  2. /* Iterate over all items in environment */
  3. for (int i = 0; i < e->count; i++) {
  4. /* Check if the stored string matches the symbol string */
  5. /* If it does, return a copy of the value */
  6. if (strcmp(e->syms[i], k->sym) == 0) {
  7. return lval_copy(e->vals[i]);
  8. }
  9. }
  10. /* If no symbol found return error */
  11. return lval_err("unbound symbol!");
  12. }
  1. void lenv_put(lenv* e, lval* k, lval* v) {
  2. /* Iterate over all items in environment */
  3. /* This is to see if variable already exists */
  4. for (int i = 0; i < e->count; i++) {
  5. /* If variable is found delete item at that position */
  6. /* And replace with variable supplied by user */
  7. if (strcmp(e->syms[i], k->sym) == 0) {
  8. lval_del(e->vals[i]);
  9. e->vals[i] = lval_copy(v);
  10. return;
  11. }
  12. }
  13. /* If no existing entry found allocate space for new entry */
  14. e->count++;
  15. e->vals = realloc(e->vals, sizeof(lval*) * e->count);
  16. e->syms = realloc(e->syms, sizeof(char*) * e->count);
  17. /* Copy contents of lval and symbol string into new location */
  18. e->vals[e->count-1] = lval_copy(v);
  19. e->syms[e->count-1] = malloc(strlen(k->sym)+1);
  20. strcpy(e->syms[e->count-1], k->sym);
  21. }