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.
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);
}
}How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
