Fundamentals 10 min read

Mastering String Handling and File Loading in a C‑Based Lisp Interpreter

This tutorial walks through extending a C‑based Lisp interpreter with string support, comment parsing, built‑in print and error commands, file loading capabilities, providing complete code examples and explanations for each feature in the interpreter.

AI Cyberspace
AI Cyberspace
AI Cyberspace
Mastering String Handling and File Loading in a C‑Based Lisp Interpreter

String and File Library

The file‑library feature automatically loads a set of predefined functions each time the interactive REPL starts, and allows loading source files for parsing, which is essential for implementing a programming language.

String Support

First, add a lexical rule to recognize strings as characters between double quotes, possibly containing escaped characters. string : /"(\\.|[^"])*"/ ; Introduce the LVAL_STR data type in the enum definition:

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

Update the type‑name function: case LVAL_STR: return "String"; Add fields to the lval struct:

/* Basic */
long num;
char* err;
char* sym;
char* str;

Provide a constructor for string values:

lval* lval_str(char* s) {
  lval* v = malloc(sizeof(lval));
  v->type = LVAL_STR;
  v->str = malloc(strlen(s) + 1);
  strcpy(v->str, s);
  return v;
}

Handle destruction, copying, and equality for strings:

case LVAL_STR: free(v->str); break;
case LVAL_STR:
  x->str = malloc(strlen(v->str) + 1);
  strcpy(x->str, v->str);
  break;
case LVAL_STR: return (strcmp(x->str, y->str) == 0);

Implement the reader for string literals:

lval *lval_read_str(mpc_ast_t *t) {
  t->contents[strlen(t->contents) - 1] = '\0';
  char *unescaped = malloc(strlen(t->contents + 1) + 1);
  strcpy(unescaped, t->contents + 1);
  unescaped = mpcf_unescape(unescaped);
  lval *str = lval_str(unescaped);
  free(unescaped);
  return str;
}

Printing strings adds surrounding quotes:

case LVAL_STR:   lval_print_str(v); break;
void lval_print_str(lval *v) {
  char *escaped = malloc(strlen(v->str) + 1);
  strcpy(escaped, v->str);
  escaped = mpcf_escape(escaped);
  printf("\"%s\"", escaped);
  free(escaped);
}

REPL examples demonstrate correct handling of escaped characters and list operations.

lispy> "hello"
"hello"
lispy> "hello
"
"hello
"
lispy> "hello\""
"hello\""
lispy> head {"hello" "world"}
{"hello"}
lispy> eval (head {"hello" "world"})
"hello"
lispy>

print Keyword Function

Implement a built‑in print that outputs each argument separated by spaces and appends a newline, returning an empty S‑expression.

lval* builtin_print(lenv* e, lval* a) {
  for (int i = 0; i < a->count; i++) {
    lval_print(a->cell[i]); putchar(' ');
  }
  putchar('
');
  lval_del(a);
  return lval_sexpr();
}

error Keyword Function

Provide an error built‑in that takes a single string argument and returns an LVAL_ERR containing that message.

lval* builtin_error(lenv* e, lval* a) {
  LASSERT_NUM("error", a, 1);
  LASSERT_TYPE("error", a, 0, LVAL_STR);
  lval* err = lval_err(a->cell[0]->str);
  lval_del(a);
  return err;
}

Comments

Comments are useful in source files. The language uses a semicolon‑started syntax: comment : /;[^\r\n]*/ ; Add the comment rule to the parser definition and ensure the reader skips comment nodes.

mpca_lang(MPCA_LANG_DEFAULT,
          "...\
    comment : /;[^\\r\
]*/ ;\
    ...",
          Number, Symbol, String, Comment, Sexpr, Qexpr, Expr, Lispy);

mpc_cleanup(8, Number, Symbol, String, Comment, Sexpr, Qexpr, Expr, Lispy);

if (strstr(t->children[i]->tag, "comment")) { continue; }

File Loading

The load built‑in reads a file, parses each expression, evaluates them sequentially, and returns an empty list or an error.

lval* builtin_load(lenv* e, lval* a) {
  LASSERT_NUM("load", a, 1);
  LASSERT_TYPE("load", a, 0, LVAL_STR);
  mpc_result_t r;
  if (mpc_parse_contents(a->cell[0]->str, Lispy, &r)) {
    lval* expr = lval_read(r.output);
    mpc_ast_delete(r.output);
    while (expr->count) {
      lval* x = lval_eval(e, lval_pop(expr, 0));
      if (x->type == LVAL_ERR) { lval_println(x); }
      lval_del(x);
    }
    lval_del(expr);
    lval_del(a);
    return lval_sexpr();
  } else {
    char* err_msg = mpc_err_string(r.error);
    mpc_err_delete(r.error);
    lval* err = lval_err("Could not load Library %s", err_msg);
    free(err_msg);
    lval_del(a);
    return err;
  }
}

Function Registration

/* String Functions */
lenv_add_builtin(e, "load",  builtin_load);
lenv_add_builtin(e, "error", builtin_error);
lenv_add_builtin(e, "print", builtin_print);

Command‑Line Arguments

Support loading files passed as CLI arguments.

if (argc >= 2) {
  for (int i = 1; i < argc; i++) {
    lval* args = lval_add(lval_sexpr(), lval_str(argv[i]));
    lval* x = builtin_load(e, args);
    if (x->type == LVAL_ERR) { lval_println(x); }
    lval_del(x);
  }
}
CParserstring handlingbuiltin functionsfile loadingLisp interpreter
AI Cyberspace
Written by

AI Cyberspace

AI, big data, cloud computing, and networking.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.