Skip to content

Instantly share code, notes, and snippets.

@tekknolagi
Last active January 11, 2021 20:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tekknolagi/f08163d90d445e1d67e87e654a8f7db0 to your computer and use it in GitHub Desktop.
Save tekknolagi/f08163d90d445e1d67e87e654a8f7db0 to your computer and use it in GitHub Desktop.
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// TODO(max): Consider writing this in Rust or Nim for some extra reader
// appeal.
typedef enum {
kInt,
kStr,
} ObjectType;
typedef struct {
ObjectType type;
union {
const char *str_value;
int int_value;
};
} Object;
Object new_int(int value) {
return (Object){.type = kInt, .int_value = value};
}
Object new_str(const char *value) {
return (Object){.type = kStr, .str_value = value};
}
typedef enum {
kAdd,
kPrint,
kUnknownSymbol = kPrint + 1,
} Symbol;
// Note: this takes advantage of the fact that in C, not putting anything
// between the parentheses means that this function can take any number of
// arguments.
typedef Object (*Method)();
typedef struct {
Symbol name;
Method method;
} MethodDefinition;
typedef unsigned char byte;
typedef struct {
ObjectType key;
Method value;
} CachedValue;
#define CHECK(cond) \
do { \
if (!(cond)) { \
abort(); \
} \
} while (0)
Object int_add(Object left, Object right) {
CHECK(left.type == kInt);
CHECK(right.type == kInt);
return new_int(left.int_value + right.int_value);
}
Object int_print(Object obj) {
CHECK(obj.type == kInt);
fprintf(stderr, "int: %d\n", obj.int_value);
return obj;
}
Object str_add(Object left, Object right) {
CHECK(left.type == kStr);
CHECK(right.type == kStr);
int result_size = strlen(left.str_value) + strlen(right.str_value) + 1;
char *result = malloc(result_size);
strcpy(result, left.str_value);
strcat(result, right.str_value);
return new_str(result);
}
Object str_print(Object obj) {
CHECK(obj.type == kStr);
fprintf(stderr, "str: \"%s\"\n", obj.str_value);
return obj;
}
static const MethodDefinition kIntMethods[] = {
{kAdd, int_add},
{kPrint, int_print},
{kUnknownSymbol, NULL},
};
static const MethodDefinition kStrMethods[] = {
{kAdd, str_add},
{kPrint, str_print},
{kUnknownSymbol, NULL},
};
static const MethodDefinition *kTypes[] = {
[kInt] = kIntMethods,
[kStr] = kStrMethods,
};
#define ARRAYSIZE(ARR) (sizeof(ARR) / sizeof(ARR)[0])
Method lookup_method(ObjectType type, Symbol name) {
CHECK(type < ARRAYSIZE(kTypes) && "out of bounds type");
const MethodDefinition *table = kTypes[type];
for (int i = 0; table[i].method != NULL; i++) {
if (table[i].name == name) {
return table[i].method;
}
}
CHECK(false && "could not find method");
}
typedef struct {
// Array of `num_opcodes' (op, arg) pairs (total size `num_opcodes' * 2).
byte *bytecode;
int num_opcodes;
// Array of `num_opcodes' elements.
CachedValue *caches;
} Code;
Code new_code(byte *bytecode, int num_opcodes) {
Code result;
result.bytecode = bytecode;
result.num_opcodes = num_opcodes;
result.caches = calloc(num_opcodes, sizeof *result.caches);
return result;
}
static unsigned kBytecodeSize = 2;
typedef enum {
// Load a value from the arguments array at index `arg'.
ARG,
// Add stack[-2] + stack[-1].
ADD,
// Pop the top of the stack and print it.
PRINT,
// Halt the machine.
HALT,
} Opcode;
void eval_code(Code *code, Object *args, int nargs) {
int pc = 0;
#define STACK_SIZE 100
Object stack_array[STACK_SIZE];
Object *stack = stack_array;
#define PUSH(x) *stack++ = (x)
#define POP() *--stack
#define CACHE_AT(pc) code->caches[(pc) / kBytecodeSize]
while (true) {
Opcode op = code->bytecode[pc];
byte arg = code->bytecode[pc + 1];
switch (op) {
case ARG:
CHECK(arg < nargs && "out of bounds arg");
PUSH(args[arg]);
break;
case ADD: {
Object right = POP();
Object left = POP();
CachedValue cached = CACHE_AT(pc);
Method method = cached.value;
if (method == NULL || cached.key != left.type) {
fprintf(stderr, "updating cache at %d\n", pc);
method = lookup_method(left.type, kAdd);
CACHE_AT(pc) = (CachedValue){.key = left.type, .value = method};
} else {
fprintf(stderr, "using cached value at %d\n", pc);
}
Object result = (*method)(left, right);
PUSH(result);
break;
}
case PRINT: {
Object obj = POP();
Method method = lookup_method(obj.type, kPrint);
(*method)(obj);
break;
}
case HALT:
return;
default:
fprintf(stderr, "unknown opcode %d\n", op);
abort();
}
pc += kBytecodeSize;
}
}
int main() {
byte bytecode[] = {/*0:*/ ARG, 0,
/*2:*/ ARG, 1,
/*4:*/ ADD, 0,
/*6:*/ PRINT, 0,
/*8:*/ HALT, 0};
Object int_args[] = {
new_int(5),
new_int(10),
};
Object str_args[] = {
new_str("hello "),
new_str("world"),
};
Code code = new_code(bytecode, sizeof bytecode / kBytecodeSize);
eval_code(&code, int_args, ARRAYSIZE(int_args));
eval_code(&code, int_args, ARRAYSIZE(int_args));
eval_code(&code, str_args, ARRAYSIZE(str_args));
eval_code(&code, str_args, ARRAYSIZE(str_args));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment