I created a basic Scheme interpreter in C
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <ctype.h>
#define BT_FREE 0
#define BT_READY 1
#define BT_ATOM 2
#define BT_NUM 3
#define BT_CONS 4
#define BT_FUNCTION 5
#define BT_FORM 6
#define BT_BUILTIN 7
#define BT_DYNAMIC 8
#define TOK_NIL 0
#define TOK_ERROR 1
#define TOK_OPEN 2
#define TOK_CLOSE 3
#define TOK_QUOTE 4
#define TOK_DOT 5
typedef struct {
int type;
union {
struct {
int car;
int cdr;
};
int64_t id;
};
} cons_cell;
int hptr = 10;
#define TABLE_SIZE 999983
cons_cell cons_cells[TABLE_SIZE];
const char *atom_table[TABLE_SIZE];
/**
* Returns the 'car' (first element) of a cons cell identified by its ID.
*
* @param id The ID of the cons cell.
* @return The 'car' of the cons cell.
*/
int car(int id)
{
return cons_cells[id].car;
}
/**
* Returns the 'cdr' (rest) of a cons cell identified by its ID.
*
* @param id The ID of the cons cell.
* @return The 'cdr' of the cons cell.
*/
int cdr(int id)
{
return cons_cells[id].cdr;
}
/**
* Returns the 'car' (first element) of the 'cdr' (rest) of a cons cell
* identified by its ID.
*
* @param id The ID of the cons cell.
* @return The 'car' of the 'cdr'.
*/
int cadr(int id)
{
return car(cdr(id));
}
/**
* Returns the 'car' (first element) of the 'cdr' (rest) of a cons cell
* identified by its ID.
*
* @param id The ID of the cons cell.
* @return The 'car' of the 'cdr'.
*/
int cddr(int id)
{
return cdr(cdr(id));
}
/**
* Returns the 'car' (first element) of the 'cdr' (rest) of the 'cdr' (rest) of
* a cons cell identified by its ID.
*
* @param id The ID of the cons cell.
* @return The 'car' of the 'cdr' of the 'cdr'.
*/
int caddr(int id)
{
return car(cdr(cdr(id)));
}
int cdddr(int id)
{
return cdr(cdr(cdr(id)));
}
/**
* Returns the 'car' (first element) of the 'cdr' (rest) of the 'cdr' (rest) of
* the 'cdr' (rest) of a cons cell identified by its ID.
*
* @param id The ID of the cons cell.
* @return The 'car' of the 'cdr' of the 'cdr' of the 'cdr'.
*/
int cadddr(int id)
{
return car(cdr(cdr(cdr(id))));
}
/**
* Allocates a new memory cell and returns its identifier.
*
* @return The identifier of the newly allocated memory cell.
*/
int alloc()
{
return hptr++;
}
/**
* Allocates a new cons cell with the specified car and cdr values.
*
* @param car The identifier of the car value.
* @param cdr The identifier of the cdr value.
* @return The identifier of the newly allocated cons cell.
*/
int alloc_cons(int car, int cdr)
{
int p = alloc();
cons_cells[p].type = BT_CONS;
cons_cells[p].car = car;
cons_cells[p].cdr = cdr;
return p;
}
#define show_error(...) do { fprintf(stderr, __VA_ARGS__); exit(0); } while(0)
#define show_warning(...) fprintf(stderr, __VA_ARGS__)
/**
* Calculates a hash value for a given string.
*
* @param s The string for which to calculate the hash value.
* @return The calculated hash value.
*/
int hash_function(const char *s)
{
int hashValue = 0;
for (const char *c=s; *c; c++) {
hashValue = (hashValue * 31 + tolower(*c)) % TABLE_SIZE;
}
return hashValue;
}
bool iequals(const char *a, const char *b) {
return !strcmp(a,b);
}
/**
* Interns a string into the atom table and returns its hash value.
*
* @param s The string to be interned.
* @return The hash value of the interned string.
*/
int intern(const char* s)
{
int hash_value = hash_function(s);
int original_hash = hash_value;
int i = 1;
while (atom_table[hash_value] &&
!iequals(atom_table[hash_value], s)) {
hash_value = (original_hash + i) % TABLE_SIZE;
i++;
}
atom_table[hash_value] = s;
return hash_value;
}
/**
* Converts a string to an atom or number and returns its identifier.
*
* @param s The string to be converted.
* @return The identifier of the resulting atom or number.
*/
int string_to_atom(const char *s) {
if (iequals(s, "nil")) {
return TOK_NIL;
}
size_t n = strlen(s);
size_t y = 0;
int64_t nval = 0;
// Check if the string represents a number
if ((s[0] == '-' && isdigit(s[1])) || isdigit(s[0])) {
char *endptr;
nval = strtoll(s, &endptr, 0);
y = endptr - s;
}
int x = alloc();
if (y == n) {
cons_cells[x].type = BT_NUM;
cons_cells[x].id = nval;
} else {
int pt = intern(s);
cons_cells[x].type = BT_ATOM;
cons_cells[x].id = pt;
}
return x;
}
int atom_quote;
int atom_true;
void append_char(char **str, size_t *len, size_t *cap, char ch) {
if (*len + 1 >= *cap) {
*cap = (*cap == 0) ? 1 : (*cap * 2);
*str = realloc(*str, *cap);
if (*str == NULL) {
perror("Failed to allocate memory");
exit(EXIT_FAILURE);
}
}
(*str)[(*len)++] = ch;
(*str)[*len] = '\0';
}
/**
* Reads and returns the next token from the input stream, handling various
* cases including escape commands, comments, parentheses, and symbols.
*
* @return The token identifier for the read token. Possible values include:
* - TOK_OPEN: Opening parenthesis '('.
* - TOK_CLOSE: Closing parenthesis ')'.
* - TOK_DOT: Dot '.' indicating a pair.
* - TOK_QUOTE: Single quote '\'' indicating a quoted expression.
* - TOK_ERROR: An error occurred during token reading.
* - id for atom for an identifier or number
*/
int read_token()
{
int c;
for (;;) {
c = getchar();
while (isspace(c))
c = getchar();
if (c == ':') { /* escape commands */
c = getchar();
if (c == 'q')
exit(0);
else
return TOK_ERROR;
while ((c = getchar()) != '\n');
} else if (c == ';') { /* end of line comment */
while ((c = getchar()) != '\n');
}
if (c == EOF)
exit(0);
switch (c) {
case '(':
return TOK_OPEN;
case ')':
return TOK_CLOSE;
case '.':
return TOK_DOT;
case '\'':
return TOK_QUOTE;
default:
char *s = NULL;
size_t length = 0, capacity = 0;
append_char(&s, &length, &capacity, c);
for (;;) {
c = getchar();
if (isspace(c) || c == '.' || c == '(' || c == ')')
break;
append_char(&s, &length, &capacity, tolower(c));
}
ungetc(c, stdin);
return string_to_atom(s);
}
}
}
int read_list();
/**
* Reads and parses the next object from input.
*
* @return The identifier of the parsed object.
*/
int read_obj()
{
int tok = read_token();
switch (tok) {
case TOK_OPEN:
return read_list();
case TOK_QUOTE:
tok = read_obj();
switch (tok) {
case TOK_CLOSE:
show_warning("ignoring quote before close parenthesis");
return tok;
case TOK_DOT:
show_warning("ignoring quote before dot");
return tok;
case TOK_ERROR:
return tok;
default:
return alloc_cons(atom_quote, alloc_cons(tok, 0));
}
default:
return tok;
}
}
/**
* Reads and parses a list from input.
*
* @return The identifier of the parsed list.
*/
int read_list()
{
int sh = read_obj();
int st;
switch (sh) {
case TOK_ERROR:
return TOK_ERROR;
case TOK_CLOSE:
return 0;
case TOK_DOT:
sh = read_obj();
switch (sh) {
case TOK_ERROR:
return TOK_ERROR;
case TOK_DOT:
case TOK_CLOSE:
show_error("a dot must be followed by an object");
return TOK_ERROR;
}
st = read_list();
if (st == TOK_ERROR)
return TOK_ERROR;
if (st != 0) {
show_error("only one object may follow a dot");
return TOK_ERROR;
}
return sh;
default:
st = read_list();
if (st == TOK_ERROR)
return TOK_ERROR;
return alloc_cons(sh, st);
}
}
void write_obj(int s);
/**
* Writes a list represented by a cons cell structure to the standard output.
*
* @param s The cons cell representing the list.
*/
void write_list(cons_cell s)
{
write_obj(s.car);
int st = s.cdr;
if (st == 0) {
return;
}
if (cons_cells[st].type == BT_CONS) {
printf(" ");
write_list(cons_cells[st]);
} else {
printf(" . ");
write_obj(st);
}
}
/**
* Writes a list represented by a cons cell structure to the standard output.
*
* @param s The cons cell representing the list.
*/
void write_obj(int s)
{
if (s == 0) {
puts("nil");
return;
}
if (s == TOK_ERROR) {
puts("[ERROR]");
return;
}
switch (cons_cells[s].type) {
case BT_ATOM:
printf("%s", atom_table[cons_cells[s].id]);
break;
case BT_NUM:
printf("%lld", cons_cells[s].id);
break;
case BT_CONS:
printf("(");
if (s != 0) {
write_list(cons_cells[s]);
}
printf(")");
break;
case BT_FREE:
printf("[NULL]");
break;
case BT_FORM:
printf("[syntax]");
break;
case BT_FUNCTION:
printf("[function]");
break;
case BT_BUILTIN:
printf("[built in function]");
break;
default:
printf("[???]");
}
}
/**
* Defines a new variable 'var' with the specified value 'aval' within the given
* environment 'env'. If the variable already exists within the environment, its
* value is updated. If the variable is not found, a new binding is created.
*
* @param var The identifier of the variable to be defined or updated.
* @param aval The value to associate with the variable.
* @param env The environment (a linked list of frames) in which to define or
* update the variable.
* @return The identifier of the defined or updated variable.
*/
int defvar(int var, int aval, int env)
{
int frame = car(env);
int vars = car(frame);
int vals = cdr(frame);
int64_t vid = cons_cells[var].id;
while (vars != 0) {
if (cons_cells[vars].type == BT_ATOM) {
if (cons_cells[vars].id == vid) {
int oid = car(vals);
cons_cells[vals].car = aval;
return var;
} else {
break;
}
}
if (cons_cells[car(vars)].id == vid) {
cons_cells[vals].car = aval;
return var;
}
vars = cdr(vars);
vals = cdr(vals);
}
vars = car(frame);
vals = cdr(frame);
cons_cells[frame].car = alloc_cons(var, vars);
cons_cells[frame].cdr = alloc_cons(aval, vals);
return var;
}
/**
* Updates the value associated with a variable identified by 'var' within the
* given environment 'env'. If the variable is found and updated successfully,
* the function returns the old value of the variable. If the variable is not
* found, an error message is displayed, and TOK_ERROR is returned.
*
* @param var The identifier of the variable to be updated.
* @param aval The new value to associate with the variable.
* @param env The environment (a linked list of frames) in which to search for
* and update the variable.
* @return The old value of the variable if found and updated, or TOK_ERROR if
* the variable is unbound.
*/
int setvar(int64_t var, int aval, int env)
{
while (env) {
int frame = car(env);
int vars = car(frame);
int vals = cdr(frame);
while (vars != 0) {
if (cons_cells[vars].type == BT_ATOM) {
if (cons_cells[vars].id == var) {
int oid = car(vals);
cons_cells[vals].car = aval;
return oid;
} else {
break;
}
}
if (cons_cells[car(vars)].id == var) {
int oid = car(vals);
cons_cells[vals].car = aval;
return oid;
}
vars = cdr(vars);
vals = cdr(vals);
}
env = cdr(env);
}
show_error("ubound variable: %s", atom_table[var]);
return TOK_ERROR;
}
/**
* Searches for a variable with the specified identifier 'var' within the given
* environment 'env'. If the variable is found, its associated value is
* returned. If the variable is not found, an error message is displayed, and
* TOK_ERROR is returned.
*
* @param var The identifier of the variable to be looked up.
* @param env The environment (a linked list of frames) in which to search for
* the variable.
* @return The value associated with the variable if found, or TOK_ERROR if the
* variable is undefined.
*/
int lookup(int var, int env)
{
while (env) {
int frame = car(env);
int vars = car(frame);
int vals = cdr(frame);
while (vars) {
if (cons_cells[vars].type == BT_ATOM) {
if (cons_cells[vars].id == var) {
return vals;
} else {
break;
}
}
if (cons_cells[car(vars)].id == var) {
return car(vals);
}
vars = cdr(vars);
vals = cdr(vals);
}
env = cdr(env);
}
show_error("undefined variable: %s", atom_table[var]);
return TOK_ERROR;
}
int eval_obj(int id, int env);
/**
* Evaluates a list of expressions identified by 'id' within the given
* environment 'env'. Each expression in the list is evaluated, and a new list
* containing the results is created.
*
* @param id The identifier of the list of expressions to be evaluated.
* @param env The environment in which to evaluate the expressions.
* @return An evaluated list containing the results of evaluating each
* expression, or 0 if 'id' is not a list.
*/
int lvals(int id, int env)
{
if (cons_cells[id].type == BT_CONS) {
int ecar = eval_obj(car(id), env);
int head = alloc_cons(ecar, 0);
int l = cdr(id), prev = head;
while (l) { // iterate through the list and evaluate each element
ecar = eval_obj(car(l), env);
int nc = alloc_cons(ecar, 0);
cons_cells[prev].cdr = nc;
prev = nc;
l = cdr(l);
}
return head;
} else {
return 0;
}
}
#define PPLUS 1
#define PMINUS 2
#define PTIMES 3
#define PCONS 4
#define PCAR 5
#define PCDR 6
#define PEQUAL 7
#define PNOT 8
#define PEQ 9
#define PSETCAR 10
#define PSETCDR 11
#define PAPPLY 12
#define PLIST 13
#define PREAD 14
#define PLT 15
#define PGT 16
#define PGEQ 17
#define PLEQ 18
#define PNUMP 20
#define PPROCP 21
#define PSYMP 22
#define PCONSP 24
/**
* Evaluates a given expression 'id' with a list of arguments 'args'.
* Depending on the type of 'id' (function or builtin), it performs the
* corresponding evaluation.
*
* For functions:
* - Creates a new environment with parameter bindings and evaluates the
* function's body.
* - Returns the result of evaluating the function's body.
*
* For builtins:
* - Handles various builtin operations based on the 'id':
* - Arithmetic operations (PPLUS, PTIMES, PMINUS)
* - Logical operations (PNOT)
* - List operations (PCONS, PCAR, PCDR, PLIST)
* - Input and output (PREAD)
* - Type checks (PSYMP, PNUMP, PPROCP, PCONSP)
* - Mutation operations (PSETCAR, PSETCDR)
* - Equality checks (PEQUAL, PEQ)
* - Comparison operations (PLT, PGT, PLEQ, PGEQ)
*
* If 'id' is not a function or builtin, an error message is displayed.
*
* @param id The identifier of the expression to be evaluated.
* @param args The list of arguments for the evaluation.
* @return The result of the evaluation or TOK_ERROR if there is an error.
*/
int apply(int id, int args)
{
if (cons_cells[id].type == BT_FUNCTION) {
int params = car(id);
int body = cadr(id);
int procenv = cddr(id);
int env = alloc_cons(alloc_cons(params, args), procenv);
while (cdr(body)) {
eval_obj(car(body), env);
body = cdr(body);
}
return eval_obj(car(body), env);
} else if (cons_cells[id].type == BT_BUILTIN) {
switch (cons_cells[id].id) {
case PPLUS:
case PTIMES:{
int64_t sum = (cons_cells[id].id == PPLUS) ? 0 : 1;
for (int a = args; a; a = cdr(a)) {
if (cons_cells[id].id == PPLUS) {
sum += cons_cells[car(a)].id;
} else if (cons_cells[id].id == PTIMES) {
sum *= cons_cells[car(a)].id;
}
}
int p = alloc();
cons_cells[p].type = BT_NUM;
cons_cells[p].id = sum;
return p;
}
case PNOT:
return car(args) ? 0 : atom_true;
case PCONS:
return alloc_cons(car(args), cadr(args));
case PCAR:
return car(car(args));
case PCDR:
return cdr(car(args));
case PAPPLY:
return apply(car(args), cadr(args));
case PLIST:
return args;
case PREAD:
return read_obj();
case PSYMP:
return cons_cells[car(args)].type == BT_ATOM ? atom_true : 0;
case PNUMP:
return cons_cells[car(args)].type == BT_NUM ? atom_true : 0;
case PPROCP:
return cons_cells[car(args)].type == BT_FUNCTION ||
cons_cells[car(args)].type == BT_BUILTIN ? atom_true : 0;
case PCONSP:
return cons_cells[car(args)].type == BT_CONS ? atom_true : 0;
case PSETCAR:{
int arg1 = car(args);
int arg2 = cadr(args);
return cons_cells[arg1].car = arg2;
}
case PSETCDR:{
int arg1 = car(args);
int arg2 = cadr(args);
return cons_cells[arg1].cdr = arg2;
}
case PMINUS:{
int64_t arg1 = cons_cells[car(args)].id;
int rargs = cdr(args);
if (rargs) {
int64_t res = arg1;
while (rargs) {
res -= cons_cells[car(rargs)].id;
rargs = cdr(rargs);
}
int p = alloc();
cons_cells[p].type = BT_NUM;
cons_cells[p].id = res;
return p;
} else {
int p = alloc();
cons_cells[p].type = BT_NUM;
cons_cells[p].id = -arg1;
return p;
}
}
case PEQUAL:{
if (!args)
return atom_true;
int64_t f = cons_cells[car(args)].id;
int64_t a = cdr(args);
while (a) {
if (cons_cells[car(a)].id != f)
return 0;
a = cdr(a);
}
return atom_true;
}
case PEQ:{
int arg1 = car(args);
int arg2 = cadr(args);
if (cons_cells[arg1].type != cons_cells[arg2].type)
return 0;
switch (cons_cells[arg1].type) {
case BT_NUM:
case BT_FUNCTION:
case BT_BUILTIN:
case BT_ATOM:
return cons_cells[arg1].id ==
cons_cells[arg2].id ? atom_true : 0;
default:
return arg1 == arg2 ? atom_true : 0;
}
return atom_true;
}
case PLT:
case PGT:
case PLEQ:
case PGEQ:{
if (!args) {
return atom_true;
}
int64_t f = cons_cells[car(args)].id;
int a = cdr(args);
while (a) {
int current_value = cons_cells[car(a)].id;
if ((cons_cells[id].id == PLT && f < current_value) ||
(cons_cells[id].id == PGT && f > current_value) ||
(cons_cells[id].id == PLEQ && f <= current_value)
|| (cons_cells[id].id == PGEQ
&& f >= current_value)) {
f = current_value;
a = cdr(a);
} else {
return 0;
}
}
return atom_true;
}
}
} else {
show_error("bad application: not a function");
return TOK_ERROR;
}
}
/**
* Evaluates an object (atom, number, function, or list) within a specified
* environment.
*
* @param id The identifier of the object to be evaluated.
* @param env The environment in which the evaluation takes place.
* @return The result of the evaluation.
*/
int eval_obj(int id, int env)
{
if (!id)
return 0;
switch (cons_cells[id].type) {
case BT_NUM:
case BT_FUNCTION:
case BT_BUILTIN:
return id;
case BT_CONS:
if (cons_cells[cons_cells[id].car].type == BT_ATOM) {
int x = cons_cells[car(id)].id;
if (x == intern("quote")) {
return cadr(id);
} else if (x == intern("lambda")) {
int p = alloc();
cons_cells[p].type = BT_FUNCTION;
cons_cells[p].car = cadr(id);
cons_cells[p].cdr = alloc_cons(cddr(id), env);
return p;
} else if (x == intern("begin")) {
int seq = cdr(id);
while (cdr(seq)) {
eval_obj(car(seq), env);
seq = cdr(seq);
}
return eval_obj(car(seq), env);
} else if (x == intern("and")) {
int seq = cdr(id);
while (cdr(seq)) {
int t = eval_obj(car(seq), env);
if (!t)
return 0;
seq = cdr(seq);
}
return eval_obj(car(seq), env);
} else if (x == intern("or")) {
int seq = cdr(id);
while (cdr(seq)) {
int t = eval_obj(car(seq), env);
if (t)
return t;
seq = cdr(seq);
}
return eval_obj(car(seq), env);
} else if (x == intern("cond")) {
int clauses = cdr(id);
while (clauses) {
int clause = car(clauses);
int praedicate = car(clause);
if (eval_obj(praedicate, env)) {
int seq = cdr(clause);
while (cdr(seq)) {
eval_obj(car(seq), env);
seq = cdr(seq);
}
return eval_obj(car(seq), env);
}
clauses = cdr(clauses);
}
} else if (x == intern("set!")) {
int64_t vid = cons_cells[cadr(id)].id;
int aval = caddr(id);
return setvar(vid, eval_obj(aval, env), env);
} else if (x == intern("define")) {
int64_t vid = cadr(id);
int aval = caddr(id);
if (cons_cells[vid].type == BT_CONS) {
int p = alloc();
cons_cells[p].type = BT_FUNCTION;
cons_cells[p].car = cdr(vid);
cons_cells[p].cdr = alloc_cons(cddr(id), env);
return defvar(car(vid), p, env);
}
return defvar(vid, eval_obj(aval, env), env);
} else if (x == intern("if")) {
int protasis = cadr(id);
int apodosis = caddr(id);
int alt = cadddr(id);
return eval_obj(protasis, env) ? eval_obj(apodosis, env)
: eval_obj(alt, env);
} else {
int args = cdr(id);
int proc = eval_obj(car(id), env);
return apply(proc, lvals(args, env));
}
} else {
int args = cdr(id);
int proc = eval_obj(car(id), env);
return apply(proc, lvals(args, env));
}
case BT_ATOM:
return lookup(cons_cells[id].id, env);
case BT_FREE:
show_error("attempt to evaluate free memory");
return TOK_ERROR;
default:
return id;
}
}
/**
* Creates an empty environment with no variable bindings.
*
* @return The identifier of the empty environment.
*/
int empty_environment()
{
int vars = 0, vals = 0;
int frame = alloc_cons(vars, vals);
return alloc_cons(frame, 0);
}
/**
* Creates a new primitive operation (builtin) with the given identifier 'id'.
*
* @param id The identifier of the primitive operation.
* @return The identifier of the created primitive operation.
*/
int mk_primop(int id)
{
int p = alloc();
cons_cells[p].type = BT_BUILTIN;
cons_cells[p].id = id;
return p;
}
/**
* Creates and initializes the default environment with predefined primitive
* operations.
*
* @return The identifier of the default environment.
*/
int default_environment()
{
int env = empty_environment();
defvar(string_to_atom("+"), mk_primop(PPLUS), env);
defvar(string_to_atom("-"), mk_primop(PMINUS), env);
defvar(string_to_atom("*"), mk_primop(PTIMES), env);
defvar(string_to_atom("cons"), mk_primop(PCONS), env);
defvar(string_to_atom("car"), mk_primop(PCAR), env);
defvar(string_to_atom("cdr"), mk_primop(PCDR), env);
defvar(string_to_atom("="), mk_primop(PEQUAL), env);
defvar(string_to_atom("not"), mk_primop(PNOT), env);
defvar(string_to_atom("eq?"), mk_primop(PEQ), env);
defvar(string_to_atom("eqv?"), mk_primop(PEQ), env);
defvar(atom_true, atom_true, env); // true
defvar(string_to_atom("set-car!"), mk_primop(PSETCAR), env);
defvar(string_to_atom("set-cdr!"), mk_primop(PSETCDR), env);
defvar(string_to_atom("apply"), mk_primop(PAPPLY), env);
defvar(string_to_atom("list"), mk_primop(PLIST), env);
defvar(string_to_atom("read"), mk_primop(PREAD), env);
defvar(string_to_atom("<"), mk_primop(PLT), env);
defvar(string_to_atom(">"), mk_primop(PGT), env);
defvar(string_to_atom(">="), mk_primop(PGEQ), env);
defvar(string_to_atom("<="), mk_primop(PLEQ), env);
defvar(string_to_atom("symbol?"), mk_primop(PSYMP), env);
defvar(string_to_atom("number?"), mk_primop(PNUMP), env);
defvar(string_to_atom("procedure?"), mk_primop(PPROCP), env);
defvar(string_to_atom("pair?"), mk_primop(PCONSP), env);
return env;
}
/**
* The main entry point of the program. Initializes variables and enters an
* infinite loop to read, evaluate, and print Lisp expressions.
*
* @return The exit code of the program (not used in this case).
*/
int main()
{
atom_quote = string_to_atom("quote");
atom_true = string_to_atom("t");
int env = default_environment();
for (;;) {
printf("]=> ");
fflush(stdin);
int x = eval_obj(read_obj(), env);
printf("\n;Value: ");
write_obj(x);
puts("");
}
}
scheme r7rs. But still, please do tell us what spec you strive to conform to and test against. When I hear "scheme" I immediately assume TCO; certainly Revised^5 spells this out. It would be very helpful to see the scheme source code in your test suite. It would be entertaining to see {success, failure} of trying to load standard libraries attempting to implement reader macro support and some recent language features. \$\endgroup\$