neonucleus/rewrite/neonucleus.c
2026-02-10 17:48:36 +01:00

2476 lines
70 KiB
C

// all in 1 C file for convenience of distribution.
// as long as it can include the header, all is fine.
// this should be very easy to include in any modern build system, as it is a single C file.
// When compiled, you can define:
// - NN_BAREMETAL, to remove any runtime dependency on libc and use minimal headers
// - NN_NO_LOCKS, to not use mutexes AT ALL.
// - NN_NO_C11_LOCKS, to not use C11 mutexes, instead using pthread mutexes for POSIX systems or Windows locks.
// - NN_ATOMIC_NONE, to not use atomics.
// Most of the time, you only depend on libc.
// However, if pthread locks are used, you will need to link in -lpthread.
// we need the header.
#include "neonucleus.h"
// to use the numerical accuracy better
#define NN_COMPONENT_CALLBUDGET 10000
#ifdef NN_ATOMIC_NONE
typedef size_t nn_refc_t;
void nn_incRef(nn_refc_t *refc) {
(*refc)++;
}
bool nn_decRef(nn_refc_t *refc) {
(*refc)--;
return (*refc) == 0;
}
#else
// we need atomics for thread-safe reference counting that will be used
// for managing the lifetimes of various resources
// TODO: provide a way to use non-atomic values, and evaluate if the context should contain a method for atomics.
#include <stdatomic.h>
typedef atomic_size_t nn_refc_t;
void nn_incRef(nn_refc_t *refc) {
atomic_fetch_add(refc, 1);
}
bool nn_decRef(nn_refc_t *refc) {
nn_refc_t old = atomic_fetch_sub(refc, 1);
return old == 1;
}
#endif
typedef struct nn_Lock nn_Lock;
// the special includes
#ifndef NN_BAREMETAL
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
#if defined(__STDC_NO_THREADS__) || defined(NN_NO_C11_LOCKS) || defined(NN_WINDOWS) // fuck you Windows
#ifdef NN_POSIX
#define NN_THREAD_PTHREAD
#endif
#ifdef NN_WINDOWS
#define NN_THREAD_WINDOWS
#endif
#else
#include <threads.h>
#define NN_THREAD_C11
#endif
#ifdef NN_POSIX
#include <sys/time.h>
#include <pthread.h>
#endif
#ifdef NN_WINDOWS
#include <windows.h>
#endif
#endif
void *nn_alloc(nn_Context *ctx, size_t size) {
if(size == 0) return ctx->alloc;
return ctx->alloc(ctx->state, NULL, 0, size);
}
void nn_free(nn_Context *ctx, void *memory, size_t size) {
if(memory == NULL) return;
if(memory == ctx->alloc) return;
ctx->alloc(ctx->state, memory, size, 0);
}
void *nn_realloc(nn_Context *ctx, void *memory, size_t oldSize, size_t newSize) {
if(memory == NULL) return nn_alloc(memory, newSize);
if(memory == ctx->alloc) return nn_alloc(memory, newSize);
if(newSize == 0) {
nn_free(ctx, memory, oldSize);
return ctx->alloc;
}
return ctx->alloc(ctx->state, memory, oldSize, newSize);
}
typedef struct nn_ArenaBlock {
// we should make each block be 1 allocation instead of 2
// TODO: make it 1 alloc instead of 2
void *memory;
size_t used;
size_t cap;
struct nn_ArenaBlock *next;
} nn_ArenaBlock;
typedef struct nn_Arena {
nn_Context ctx;
nn_ArenaBlock *block;
size_t nextCap;
} nn_Arena;
void nn_arinit(nn_Arena *arena, nn_Context *ctx) {
arena->ctx = *ctx;
arena->block = NULL;
arena->nextCap = 1024;
}
void nn_ardestroy(nn_Arena *arena) {
nn_ArenaBlock *b = arena->block;
while(b != NULL) {
nn_ArenaBlock *cur = b;
b = b->next;
nn_free(&arena->ctx, cur->memory, cur->cap);
nn_free(&arena->ctx, cur, sizeof(*cur));
}
}
nn_ArenaBlock *nn_arallocblock(nn_Context *ctx, size_t cap) {
void *memory = nn_alloc(ctx, cap);
if(memory == NULL) return NULL;
nn_ArenaBlock *block = nn_alloc(ctx, sizeof(*block));
if(block == NULL) {
nn_free(ctx, memory, cap);
return NULL;
}
block->memory = memory;
block->cap = cap;
block->used = 0;
block->next = NULL;
return block;
}
void *nn_aralloc(nn_Arena *arena, size_t size) {
if((size % NN_ALLOC_ALIGN) != 0) {
size_t over = size % NN_ALLOC_ALIGN;
size += NN_ALLOC_ALIGN - over;
}
if(arena->block == NULL) {
}
nn_ArenaBlock *block = arena->block;
while(block != NULL) {
nn_ArenaBlock *cur = block;
block = block->next;
size_t free = cur->cap - cur->used;
if(free >= size) {
void *mem = (void *)((size_t)cur->memory + cur->used);
cur->used += size;
return mem;
}
}
while(arena->nextCap <= size) arena->nextCap *= 2;
nn_ArenaBlock *newBlock = nn_arallocblock(&arena->ctx, arena->nextCap);
if(newBlock == NULL) {
return NULL;
}
newBlock->next = arena->block;
newBlock->used = size;
arena->block = newBlock;
return newBlock->memory;
}
size_t nn_strlen(const char *s) {
size_t l = 0;
while(*(s++) != '\0') l++;
return l;
}
void nn_memcpy(void *dest, const void *src, size_t len) {
char *out = (char *)dest;
const char *in = (const char *)src;
for(size_t i = 0; i < len; i++) out[i] = in[i];
}
char *nn_strdup(nn_Context *ctx, const char *s) {
size_t l = nn_strlen(s);
char *buf = nn_alloc(ctx, sizeof(char) * (l+1));
if(buf == NULL) return NULL;
nn_memcpy(buf, s, sizeof(char) * l);
buf[l] = '\0';
return buf;
}
const char *nn_arstrdup(nn_Arena *arena, const char *s) {
size_t len = nn_strlen(s);
char *buf = nn_aralloc(arena, sizeof(char) * (len+1));
nn_memcpy(buf, s, sizeof(char) * len);
buf[len] = '\0';
return buf;
}
void nn_strfree(nn_Context *ctx, char *s) {
size_t l = nn_strlen(s);
nn_free(ctx, s, sizeof(char) * (l+1));
}
void nn_memset(void *dest, int x, size_t len) {
char *out = (char *)dest;
for(size_t i = 0; i < len; i++) out[i] = (char)x;
}
// taken from https://wiki.osdev.org/CRC32
// OSDev wiki is really useful sometimes
// TODO: maybe allow one that uses compiler intrinsics
// because CPUs are really good at CRC32 nowadays
unsigned int nn_crc32_poly8_lookup[256] =
{
0, 0x77073096, 0xEE0E612C, 0x990951BA,
0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940,
0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116,
0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A,
0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818,
0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C,
0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086,
0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4,
0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE,
0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252,
0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60,
0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04,
0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E,
0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0,
0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6,
0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
};
unsigned int nn_computeCRC32(const char *data, size_t datalen) {
unsigned int crc = 0xFFFFFFFF;
while(datalen-- > 0) {
crc = nn_crc32_poly8_lookup[(crc ^ *(data++)) & 0xFF] ^ (crc >> 8);
}
return (crc ^ 0xFFFFFFFF);
}
void nn_crc32ChecksumBytes(unsigned int checksum, char out[8]) {
char bytes[4];
bytes[0] = (checksum >> 0) & 0xFF;
bytes[1] = (checksum >> 8) & 0xFF;
bytes[2] = (checksum >> 16) & 0xFF;
bytes[3] = (checksum >> 24) & 0xFF;
char alpha[16] = "0123456789abcdef";
for(size_t i = 0; i < 4; i++) {
char byte = bytes[i];
out[i*2] = alpha[(byte>>4) & 0xF];
out[i*2+1] = alpha[(byte>>0) & 0xF];
}
}
bool nn_simplifyPath(const char original[NN_MAX_PATH], char simplified[NN_MAX_PATH]) {
// pass 1: check for valid characters, and \ becomes /
for(size_t i = 0; true; i++) {
if(original[i] == '\\') simplified[i] = '/';
else simplified[i] = original[i];
if(original[i] == '\0') break;
}
// get rid of //, starting / and ending /
{
while(simplified[0] == '/') {
for(size_t i = 1; true; i++) {
simplified[i-1] = simplified[i];
if(simplified[i] == '\0') break;
}
}
size_t j = 0;
for(size_t i = 0; simplified[i] != '\0'; i++) {
if(simplified[i] == '/' && simplified[i+1] == '/') {
// simply discard it
continue;
} else {
simplified[j] = simplified[i];
j++;
}
}
simplified[j] = '\0';
while(simplified[j-1] == '/') {
j--;
simplified[j] = '\0';
}
}
// TODO: handle ..
// valid
return true;
}
int nn_memcmp(const char *a, const char *b, size_t len) {
for(size_t i = 0; i < len; i++) {
char c = a[i];
char d = b[i];
if(c != d) return (int)(unsigned char)c - (int)(unsigned char)d;
}
return 0;
}
int nn_strcmp(const char *a, const char *b) {
size_t i = 0;
while(1) {
char c = a[i];
char d = b[i];
if(c == '\0' && d == '\0') return 0;
if(c != d) return (int)(unsigned char)c - (int)(unsigned char)d;
i++;
}
}
nn_Lock *nn_createLock(nn_Context *ctx) {
nn_LockRequest req;
req.lock = NULL;
req.action = NN_LOCK_CREATE;
ctx->lock(ctx->state, &req);
return req.lock;
}
void nn_destroyLock(nn_Context *ctx, nn_Lock *lock) {
if(lock == NULL) return;
nn_LockRequest req;
req.lock = lock;
req.action = NN_LOCK_DESTROY;
ctx->lock(ctx->state, &req);
}
void nn_lock(nn_Context *ctx, nn_Lock *lock) {
nn_LockRequest req;
req.lock = lock;
req.action = NN_LOCK_LOCK;
ctx->lock(ctx->state, &req);
}
void nn_unlock(nn_Context *ctx, nn_Lock *lock) {
nn_LockRequest req;
req.lock = lock;
req.action = NN_LOCK_UNLOCK;
ctx->lock(ctx->state, &req);
}
double nn_currentTime(nn_Context *ctx) {
return ctx->time(ctx->state);
}
size_t nn_rand(nn_Context *ctx) {
return ctx->rng(ctx->state);
}
double nn_randf(nn_Context *ctx) {
double n = (double)nn_rand(ctx);
return n / (double)(ctx->rngMaximum + 1);
}
double nn_randfi(nn_Context *ctx) {
double n = (double)nn_rand(ctx);
return n / (double)ctx->rngMaximum;
}
static void *nn_defaultAlloc(void *_, void *memory, size_t oldSize, size_t newSize) {
#ifndef NN_BAREMETAL
if(newSize == 0) {
free(memory);
return NULL;
}
return realloc(memory, newSize);
#else
// 0 memory available
return NULL;
#endif
}
static double nn_defaultTime(void *_) {
#ifndef NN_BAREMETAL
// time does not exist... yet!
return 0;
#else
// time does not exist
return 0;
#endif
}
static size_t nn_defaultRng(void *_) {
#ifndef NN_BAREMETAL
return rand();
#else
// insane levels of RNG
return 1;
#endif
}
static void nn_defaultLock(void *state, nn_LockRequest *req) {
(void)state;
#ifndef NN_BAREMETAL
#if defined(NN_NO_LOCKS)
switch(req->action) {
case NN_LOCK_CREATE:;
req->lock = nn_defaultLock;
return;
case NN_LOCK_DESTROY:;
return;
case NN_LOCK_LOCK:;
return;
case NN_LOCK_UNLOCK:;
return;
}
return;
#elif defined(NN_THREAD_C11)
switch(req->action) {
case NN_LOCK_CREATE:;
mtx_t *mem = malloc(sizeof(mtx_t));
req->lock = mem;
if(mem == NULL) return;
if(mtx_init(mem, mtx_plain) != thrd_success) {
free(mem);
req->lock = NULL;
return;
}
return;
case NN_LOCK_DESTROY:;
mtx_destroy(req->lock);
free(req->lock);
return;
case NN_LOCK_LOCK:;
mtx_lock(req->lock);
return;
case NN_LOCK_UNLOCK:;
mtx_unlock(req->lock);
return;
}
#elif defined(NN_THREAD_PTHREAD)
switch(req->action) {
case NN_LOCK_CREATE:;
pthread_mutex_t *mem = malloc(sizeof(pthread_mutex_t));
req->lock = mem;
if(mem == NULL) return;
if(pthread_mutex_init(mem, NULL) != 0) {
free(mem);
req->lock = NULL;
return;
}
return;
case NN_LOCK_DESTROY:;
pthread_mutex_destroy(req->lock);
free(req->lock);
return;
case NN_LOCK_LOCK:;
pthread_mutex_lock(req->lock);
return;
case NN_LOCK_UNLOCK:;
pthread_mutex_unlock(req->lock);
return;
}
#elif defined(NN_THREAD_WINDOWS)
switch(req->action) {
case NN_LOCK_CREATE:;
req->lock = CreateMutex(NULL, false, NULL);
case NN_LOCK_DESTROY:;
CloseHandle(req->lock);
return;
case NN_LOCK_LOCK:;
WaitForSingleObject(req->lock, INFINITE);
return;
case NN_LOCK_UNLOCK:;
ReleaseMutex(req->lock);
return;
}
#endif
#endif
}
void nn_initContext(nn_Context *ctx) {
ctx->state = NULL;
ctx->alloc = nn_defaultAlloc;
ctx->time = nn_defaultTime;
#ifndef NN_BAREMETAL
// someone pointed out that running this multiple times
// in 1 second can cause the RNG to loop.
// However, if you call this function multiple times at all,
// that's on you.
srand(time(NULL));
ctx->rngMaximum = RAND_MAX;
#else
ctx->rngMaximum = 1;
#endif
ctx->rng = nn_defaultRng;
ctx->lock = nn_defaultLock;
}
typedef struct nn_ComponentType {
nn_Universe *universe;
void *userdata;
nn_Arena arena;
const char *name;
nn_ComponentHandler *handler;
// NULL-terminated
nn_Method *methods;
size_t methodCount;
} nn_ComponentType;
// currently just a wrapper around a context
// but will be way more in the future
typedef struct nn_Universe {
nn_Context ctx;
} nn_Universe;
typedef struct nn_Component {
char *address;
nn_ComponentType *ctype;
int slot;
float budgetUsed;
void *userdata;
void *state;
} nn_Component;
// the values
typedef enum nn_ValueType {
NN_VAL_NULL,
NN_VAL_BOOL,
NN_VAL_NUM,
NN_VAL_STR,
NN_VAL_USERDATA,
NN_VAL_TABLE,
} nn_ValueType;
// we don't use the enum member as a name because then all the
// switch cases would have a useless branch for it.
const char *nn_typenames[6] = {
[NN_VAL_NULL] = "null",
[NN_VAL_BOOL] = "bool",
[NN_VAL_NUM] = "number",
[NN_VAL_STR] = "string",
[NN_VAL_USERDATA] = "userdata",
[NN_VAL_TABLE] = "table",
};
typedef struct nn_String {
nn_Context ctx;
size_t refc;
size_t len;
char data[];
} nn_String;
typedef struct nn_Value {
nn_ValueType type;
union {
bool boolean;
double number;
nn_String *string;
size_t userdataIdx;
struct nn_Table *table;
};
} nn_Value;
typedef struct nn_Table {
nn_Context ctx;
size_t refc;
size_t len;
nn_Value vals[];
} nn_Table;
typedef struct nn_Signal {
size_t len;
nn_Value *values;
} nn_Signal;
typedef struct nn_Computer {
nn_ComputerState state;
nn_Universe *universe;
void *userdata;
char *address;
char *tmpaddress;
void *archState;
nn_Architecture arch;
nn_Architecture desiredArch;
size_t callBudget;
size_t totalCallBudget;
size_t componentCap;
size_t componentLen;
nn_Component *components;
size_t deviceInfoCap;
size_t deviceInfoLen;
nn_DeviceInfo *deviceInfo;
double totalEnergy;
double energy;
size_t totalMemory;
double creationTimestamp;
size_t stackSize;
size_t archCount;
size_t signalCount;
size_t userCount;
nn_Value callstack[NN_MAX_STACK];
char errorBuffer[NN_MAX_ERROR_SIZE];
nn_Architecture archs[NN_MAX_ARCHITECTURES];
nn_Signal signals[NN_MAX_SIGNALS];
char *users[NN_MAX_USERS];
} nn_Computer;
nn_Universe *nn_createUniverse(nn_Context *ctx) {
nn_Universe *u = nn_alloc(ctx, sizeof(nn_Universe));
if(u == NULL) return NULL;
u->ctx = *ctx;
return u;
}
void nn_destroyUniverse(nn_Universe *universe) {
nn_Context ctx = universe->ctx;
nn_free(&ctx, universe, sizeof(nn_Universe));
}
nn_ComponentType *nn_createComponentType(nn_Universe *universe, const char *name, void *userdata, const nn_Method methods[], nn_ComponentHandler *handler) {
nn_Context *ctx = &universe->ctx;
nn_ComponentType *ctype = nn_alloc(ctx, sizeof(nn_ComponentType));
if(ctype == NULL) return NULL;
ctype->universe = universe;
ctype->userdata = userdata;
ctype->handler = handler;
nn_Arena *arena = &ctype->arena;
nn_arinit(arena, ctx);
const char *namecpy = nn_arstrdup(arena, name);
if(namecpy == NULL) goto fail;
ctype->name = namecpy;
size_t methodCount = 0;
while(methods[methodCount].name != NULL) methodCount++;
nn_Method *methodscpy = nn_aralloc(arena, methodCount * sizeof(nn_Method));
if(methodscpy == NULL) goto fail;
ctype->methods = methodscpy;
ctype->methodCount = methodCount;
for(size_t i = 0; i < methodCount; i++) {
nn_Method cpy;
cpy.flags = methods[i].flags;
cpy.name = nn_arstrdup(arena, methods[i].name);
if(cpy.name == NULL) goto fail;
cpy.docString = nn_arstrdup(arena, methods[i].docString);
if(cpy.docString == NULL) goto fail;
ctype->methods[i] = cpy;
}
return ctype;
fail:;
// yes, because of arenas, we support freeing a "partially initialized state"
nn_destroyComponentType(ctype);
return NULL;
}
void nn_destroyComponentType(nn_ComponentType *ctype) {
if(ctype == NULL) return;
nn_Context *ctx = &ctype->universe->ctx;
nn_ComponentRequest req;
req.typeUserdata = ctype->userdata;
req.compUserdata = NULL;
req.state = NULL;
req.computer = NULL;
req.compAddress = NULL;
req.action = NN_COMP_FREETYPE;
req.methodCalled = NULL;
ctype->handler(&req);
nn_ardestroy(&ctype->arena);
nn_free(ctx, ctype, sizeof(nn_ComponentType));
}
nn_Computer *nn_createComputer(nn_Universe *universe, void *userdata, const char *address, size_t totalMemory, size_t maxComponents, size_t maxDevices) {
nn_Context *ctx = &universe->ctx;
nn_Computer *c = nn_alloc(ctx, sizeof(nn_Computer));
if(c == NULL) return NULL;
c->state = NN_BOOTUP;
c->universe = universe;
c->userdata = userdata;
c->address = nn_strdup(ctx, address);
if(c->address == NULL) {
nn_free(ctx, c, sizeof(nn_Computer));
return NULL;
}
c->tmpaddress = NULL;
c->arch.name = NULL;
c->desiredArch.name = NULL;
c->archState = NULL;
c->totalCallBudget = 1000;
c->callBudget = c->totalCallBudget;
c->componentCap = maxComponents;
c->componentLen = 0;
c->components = nn_alloc(ctx, sizeof(nn_Component) * maxComponents);
if(c->components == NULL) {
nn_strfree(ctx, c->address);
nn_free(ctx, c, sizeof(nn_Computer));
return NULL;
}
c->deviceInfoCap = maxDevices;
c->deviceInfoLen = 0;
c->deviceInfo = nn_alloc(ctx, sizeof(nn_DeviceInfo) * maxDevices);
if(c->deviceInfo == NULL) {
nn_free(ctx, c->components, sizeof(nn_Component) * maxComponents);
nn_strfree(ctx, c->address);
nn_free(ctx, c, sizeof(nn_Computer));
return NULL;
}
c->totalEnergy = 500;
c->energy = 500;
c->totalMemory = totalMemory;
c->creationTimestamp = nn_currentTime(ctx);
c->stackSize = 0;
c->archCount = 0;
c->signalCount = 0;
c->userCount = 0;
// set to empty string
c->errorBuffer[0] = '\0';
return c;
}
static void nn_dropValue(nn_Value val);
static void nn_dropComponent(nn_Computer *computer, nn_Component c);
void nn_destroyComputer(nn_Computer *computer) {
nn_Context *ctx = &computer->universe->ctx;
if(computer->arch.name != NULL && computer->archState != NULL) {
nn_ArchitectureRequest req;
req.computer = computer;
req.globalState = computer->arch.state;
req.localState = computer->archState;
req.action = NN_ARCH_DEINIT;
computer->arch.handler(&req);
}
for(size_t i = 0; i < computer->stackSize; i++) {
nn_dropValue(computer->callstack[i]);
}
for(size_t i = 0; i < computer->componentLen; i++) {
nn_dropComponent(computer, computer->components[i]);
}
for(size_t i = 0; i < computer->signalCount; i++) {
nn_Signal s = computer->signals[i];
for(size_t j = 0; j < s.len; j++) nn_dropValue(s.values[j]);
nn_free(ctx, s.values, sizeof(nn_Value) * s.len);
}
for(size_t i = 0; i < computer->userCount; i++) {
nn_strfree(ctx, computer->users[i]);
}
nn_free(ctx, computer->components, sizeof(nn_Component) * computer->componentCap);
nn_free(ctx, computer->deviceInfo, sizeof(nn_DeviceInfo) * computer->deviceInfoCap);
if(computer->tmpaddress != NULL) nn_strfree(ctx, computer->tmpaddress);
nn_strfree(ctx, computer->address);
nn_free(ctx, computer, sizeof(nn_Computer));
}
void *nn_getComputerUserdata(nn_Computer *computer) {
return computer->userdata;
}
const char *nn_getComputerAddress(nn_Computer *computer) {
return computer->address;
}
nn_Exit nn_setTmpAddress(nn_Computer *computer, const char *address) {
nn_Context ctx = computer->universe->ctx;
if(address == NULL) {
if(computer->tmpaddress != NULL) {
nn_strfree(&ctx, computer->tmpaddress);
}
computer->tmpaddress = NULL;
return NN_OK;
}
char *newTmp = nn_strdup(&ctx, address);
if(newTmp == NULL) return NN_ENOMEM;
if(computer->tmpaddress != NULL) {
nn_strfree(&ctx, computer->tmpaddress);
}
computer->tmpaddress = newTmp;
return NN_OK;
}
const char *nn_getTmpAddress(nn_Computer *computer) {
return computer->tmpaddress;
}
nn_Exit nn_addUser(nn_Computer *computer, const char *user) {
if(computer->userCount == NN_MAX_USERS) return NN_ELIMIT;
size_t len = nn_strlen(user);
if(len >= NN_MAX_USERNAME) return NN_ELIMIT;
char *usercpy = nn_strdup(&computer->universe->ctx, user);
if(usercpy == NULL) return NN_ENOMEM;
computer->users[computer->userCount++] = usercpy;
return NN_OK;
}
bool nn_removeUser(nn_Computer *computer, const char *user) {
bool removed = false;
size_t j = 0;
nn_Context ctx = computer->universe->ctx;
for(size_t i = 0; i < computer->userCount; i++) {
char *u = computer->users[i];
if(nn_strcmp(u, user) == 0) {
nn_strfree(&ctx, u);
removed = true;
} else {
computer->users[j] = computer->users[i];
j++;
}
}
computer->userCount = j;
return removed;
}
const char *nn_getUser(nn_Computer *computer, size_t idx) {
if(idx >= computer->userCount) return NULL;
return computer->users[idx];
}
void nn_setArchitecture(nn_Computer *computer, const nn_Architecture *arch) {
computer->arch = *arch;
}
nn_Architecture nn_getArchitecture(nn_Computer *computer) {
return computer->arch;
}
void nn_setDesiredArchitecture(nn_Computer *computer, const nn_Architecture *arch) {
computer->desiredArch = *arch;
}
nn_Architecture nn_getDesiredArchitecture(nn_Computer *computer) {
return computer->desiredArch;
}
nn_Exit nn_addSupportedArchitecture(nn_Computer *computer, const nn_Architecture *arch) {
if(computer->archCount == NN_MAX_ARCHITECTURES) return NN_ELIMIT;
computer->archs[computer->archCount++] = *arch;
return NN_OK;
}
const nn_Architecture *nn_getSupportedArchitectures(nn_Computer *computer, size_t *len) {
*len = computer->archCount;
return computer->archs;
}
nn_Architecture nn_findSupportedArchitecture(nn_Computer *computer, const char *name) {
for(size_t i = 0; i < computer->archCount; i++) {
if(nn_strcmp(computer->archs[i].name, name) == 0) return computer->archs[i];
}
return (nn_Architecture) {
.name = NULL,
.state = NULL,
.handler = NULL,
};
}
void nn_setTotalEnergy(nn_Computer *computer, double maxEnergy) {
computer->totalEnergy = maxEnergy;
}
double nn_getTotalEnergy(nn_Computer *computer) {
return computer->totalEnergy;
}
void nn_setEnergy(nn_Computer *computer, double energy) {
computer->energy = energy;
}
double nn_getEnergy(nn_Computer *computer) {
return computer->energy;
}
bool nn_removeEnergy(nn_Computer *computer, double energy) {
computer->energy -= energy;
if(computer->energy < 0) computer->energy = 0;
if(computer->energy <= 0) {
computer->state = NN_BLACKOUT;
return true;
}
return false;
}
size_t nn_getTotalMemory(nn_Computer *computer) {
return computer->totalMemory;
}
size_t nn_getFreeMemory(nn_Computer *computer) {
if(computer->state == NN_BOOTUP) return 0;
nn_ArchitectureRequest req;
req.computer = computer;
req.action = NN_ARCH_FREEMEM;
req.globalState = computer->arch.state;
req.localState = computer->archState;
computer->arch.handler(&req);
return req.freeMemory;
}
double nn_getUptime(nn_Computer *computer) {
return nn_currentTime(&computer->universe->ctx) - computer->creationTimestamp;
}
void nn_setError(nn_Computer *computer, const char *s) {
nn_setLError(computer, s, nn_strlen(s));
}
void nn_setLError(nn_Computer *computer, const char *s, size_t len) {
if(len >= NN_MAX_ERROR_SIZE) len = NN_MAX_ERROR_SIZE - 1;
nn_memcpy(computer->errorBuffer, s, sizeof(char) * len);
computer->errorBuffer[len] = '\0';
}
const char *nn_getError(nn_Computer *computer) {
return computer->errorBuffer;
}
void nn_clearError(nn_Computer *computer) {
// make it empty
computer->errorBuffer[0] = '\0';
}
void nn_setComputerState(nn_Computer *computer, nn_ComputerState state) {
computer->state = state;
}
nn_ComputerState nn_getComputerState(nn_Computer *computer) {
return computer->state;
}
void nn_setErrorFromExit(nn_Computer *computer, nn_Exit exit) {
switch(exit) {
case NN_OK:
return; // no error
case NN_EBADCALL:
return; // stored by component
case NN_ENOMEM:
nn_setError(computer, "out of memory");
return;
case NN_ELIMIT:
nn_setError(computer, "buffer overflow");
return;
case NN_ENOSTACK:
nn_setError(computer, "stack overflow");
return;
case NN_EBELOWSTACK:
nn_setError(computer, "stack underflow");
return;
case NN_EBADSTATE:
nn_setError(computer, "bad state");
return;
}
}
nn_Exit nn_tick(nn_Computer *computer) {
nn_Exit err;
if(computer->state == NN_BOOTUP) {
// init state
nn_ArchitectureRequest req;
req.computer = computer;
req.globalState = computer->arch.state;
req.localState = NULL;
req.action = NN_ARCH_INIT;
err = computer->arch.handler(&req);
if(err) {
computer->state = NN_CRASHED;
nn_setErrorFromExit(computer, err);
return err;
}
computer->archState = req.localState;
} else if(computer->state != NN_RUNNING) {
nn_setErrorFromExit(computer, NN_EBADSTATE);
return NN_EBADSTATE;
}
nn_resetCallBudget(computer);
nn_resetComponentBudgets(computer);
computer->state = NN_RUNNING;
nn_ArchitectureRequest req;
req.computer = computer;
req.globalState = computer->arch.state;
req.localState = computer->archState;
req.action = NN_ARCH_TICK;
err = computer->arch.handler(&req);
if(err) {
computer->state = NN_CRASHED;
nn_setErrorFromExit(computer, err);
return err;
}
return NN_OK;
}
nn_Exit nn_addComponent(nn_Computer *computer, nn_ComponentType *ctype, const char *address, int slot, void *userdata) {
if(computer->componentLen == computer->componentCap) return NN_ELIMIT;
nn_Component c;
c.address = nn_strdup(&computer->universe->ctx, address);
if(c.address == NULL) return NN_ENOMEM;
c.ctype = ctype;
c.slot = slot;
c.userdata = userdata;
c.state = NULL;
nn_ComponentRequest req;
req.typeUserdata = ctype->userdata;
req.compUserdata = userdata;
req.state = NULL;
req.computer = computer;
req.compAddress = address;
req.action = NN_COMP_INIT;
req.methodCalled = NULL;
nn_Exit err = ctype->handler(&req);
if(err != NN_OK) {
nn_strfree(&computer->universe->ctx, c.address);
return err;
}
// get the state back!
c.state = req.state;
computer->components[computer->componentLen++] = c;
if(computer->state == NN_RUNNING) {
err = nn_pushstring(computer, "component_added");
if(err) return err;
err = nn_pushstring(computer, address);
if(err) return err;
err = nn_pushstring(computer, ctype->name);
if(err) return err;
err = nn_pushSignal(computer, 3);
if(err) return err;
}
return NN_OK;
}
bool nn_hasComponent(nn_Computer *computer, const char *address) {
for(size_t i = 0; i < computer->componentLen; i++) {
nn_Component *c = &computer->components[i];
if(nn_strcmp(c->address, address) == 0) {
return true;
}
}
return false;
}
bool nn_hasMethod(nn_Computer *computer, const char *address, const char *method) {
for(size_t i = 0; i < computer->componentLen; i++) {
nn_Component *c = &computer->components[i];
if(nn_strcmp(c->address, address) != 0) continue;
bool found = false;
for(size_t j = 0; j < c->ctype->methodCount; j++) {
if(nn_strcmp(c->ctype->methods[j].name, method) != 0) continue;
found = true;
break;
}
if(!found) return false;
nn_ComponentRequest req;
req.typeUserdata = c->ctype->userdata;
req.compUserdata = c->userdata;
req.state = c->state;
req.computer = computer;
req.compAddress = address;
req.action = NN_COMP_ENABLED;
req.methodCalled = method;
// default response in case it is not implemented
req.methodEnabled = true;
// should never error
c->ctype->handler(&req);
return req.methodEnabled;
}
return false;
}
static void nn_dropComponent(nn_Computer *computer, nn_Component c) {
nn_ComponentRequest req;
req.typeUserdata = c.ctype->userdata;
req.compUserdata = c.userdata;
req.state = c.state;
req.computer = computer;
req.compAddress = c.address;
req.action = NN_COMP_DEINIT;
req.methodCalled = NULL;
c.ctype->handler(&req);
nn_strfree(&computer->universe->ctx, c.address);
}
nn_Exit nn_removeComponent(nn_Computer *computer, const char *address) {
size_t j = 0;
nn_Component c;
c.address = NULL;
for(size_t i = 0; i < computer->componentLen; i++) {
if(nn_strcmp(computer->components[i].address, address) == 0) {
c = computer->components[i];
} else {
computer->components[j++] = computer->components[i];
}
}
computer->componentLen = j;
// already removed!
if(c.address == NULL) return NN_EBADSTATE;
nn_dropComponent(computer, c);
if(computer->state == NN_RUNNING) {
nn_Exit err = nn_pushstring(computer, "component_removed");
if(err) return err;
err = nn_pushstring(computer, address);
if(err) return err;
// not a UAF because c is on-stack
err = nn_pushstring(computer, c.ctype->name);
if(err) return err;
err = nn_pushSignal(computer, 3);
if(err) return err;
}
return NN_OK;
}
const char *nn_getComponentType(nn_Computer *computer, const char *address) {
for(size_t i = 0; i < computer->componentLen; i++) {
nn_Component *c = &computer->components[i];
if(nn_strcmp(c->address, address) == 0) {
return c->ctype->name;
}
}
return NULL;
}
int nn_getComponentSlot(nn_Computer *computer, const char *address) {
for(size_t i = 0; i < computer->componentLen; i++) {
nn_Component *c = &computer->components[i];
if(nn_strcmp(c->address, address) == 0) {
return c->slot;
}
}
return 0;
}
const nn_Method *nn_getComponentMethods(nn_Computer *computer, const char *address, size_t *len) {
for(size_t i = 0; i < computer->componentLen; i++) {
nn_Component *c = &computer->components[i];
if(nn_strcmp(c->address, address) == 0) {
if(len != NULL) *len = c->ctype->methodCount;
return c->ctype->methods;
}
}
if(len != NULL) *len = 0;
return NULL;
}
const char *nn_getComponentAddress(nn_Computer *computer, size_t idx) {
if(idx >= computer->componentLen) return NULL;
return computer->components[idx].address;
}
const char *nn_getComponentDoc(nn_Computer *computer, const char *address, const char *method) {
if(!nn_hasComponent(computer, address)) {
return NULL;
}
if(!nn_hasMethod(computer, address, method)) {
return NULL;
}
for(size_t i = 0; i < computer->componentLen; i++) {
nn_Component c = computer->components[i];
if(nn_strcmp(c.address, address) != 0) continue;
for(size_t j = 0; j < c.ctype->methodCount; j++) {
if(nn_strcmp(c.ctype->methods[j].name, method) == 0) return c.ctype->methods[j].docString;
}
return NULL;
}
return NULL;
}
static void nn_retainValue(nn_Value val) {
switch(val.type) {
case NN_VAL_NULL:
case NN_VAL_BOOL:
case NN_VAL_NUM:
case NN_VAL_USERDATA:
return;
case NN_VAL_STR:
val.string->refc++;
return;
case NN_VAL_TABLE:
val.table->refc++;
return;
}
}
static void nn_dropValue(nn_Value val) {
nn_Context ctx;
size_t size;
switch(val.type) {
case NN_VAL_NULL:
case NN_VAL_BOOL:
case NN_VAL_NUM:
case NN_VAL_USERDATA:
return;
case NN_VAL_STR:
val.string->refc--;
if(val.string->refc != 0) return;
ctx = val.string->ctx;
size = val.string->len + 1;
nn_free(&ctx, val.string, sizeof(nn_String) + sizeof(char) * size);
return;
case NN_VAL_TABLE:
val.table->refc--;
if(val.table->refc != 0) return;
ctx = val.table->ctx;
size = val.table->len;
nn_free(&ctx, val.table, sizeof(nn_Table) + sizeof(nn_Value) * size * 2);
return;
}
}
nn_Exit nn_call(nn_Computer *computer, const char *address, const char *method) {
if(!nn_hasComponent(computer, address)) {
nn_setError(computer, "no such component");
return NN_EBADCALL;
}
if(!nn_hasMethod(computer, address, method)) {
nn_setError(computer, "no such method");
return NN_EBADCALL;
}
for(size_t i = 0; i < computer->componentLen; i++) {
nn_Component c = computer->components[i];
if(nn_strcmp(c.address, address) != 0) continue;
// minimum cost of a component call
if(computer->callBudget > 0) computer->callBudget--;
nn_ComponentRequest req;
req.typeUserdata = c.ctype->userdata;
req.compUserdata = c.userdata;
req.state = c.state;
req.computer = computer;
req.compAddress = address;
req.action = NN_COMP_CALL;
req.methodCalled = method;
// default is to return nothing
req.returnCount = 0;
nn_Exit err = c.ctype->handler(&req);
if(err) {
if(err != NN_EBADCALL) nn_setErrorFromExit(computer, err);
// clear junk
nn_clearstack(computer);
return err;
}
size_t endOfTrim = computer->stackSize - req.returnCount;
for(size_t i = 0; i < endOfTrim; i++) {
nn_dropValue(computer->callstack[i]);
}
for(size_t i = 0; i < req.returnCount; i++) {
computer->callstack[i] = computer->callstack[endOfTrim + i];
}
computer->stackSize = req.returnCount;
return NN_OK;
}
return NN_EBADSTATE;
}
void nn_setCallBudget(nn_Computer *computer, size_t budget) {
computer->totalCallBudget = budget;
}
size_t nn_getCallBudget(nn_Computer *computer) {
return computer->totalCallBudget;
}
void nn_callCost(nn_Computer *computer, size_t callIntensity) {
if(computer->callBudget < callIntensity) computer->callBudget = 0;
else computer->callBudget -= callIntensity;
}
size_t nn_callBudgetRemaining(nn_Computer *computer) {
return computer->callBudget;
}
void nn_resetCallBudget(nn_Computer *computer) {
computer->callBudget = computer->totalCallBudget;
}
bool nn_componentsOverused(nn_Computer *computer) {
for(size_t i = 0; i < computer->componentLen; i++) {
if(computer->components[i].budgetUsed >= NN_COMPONENT_CALLBUDGET) return true;
}
if(computer->totalCallBudget == 0) return false;
return computer->callBudget == 0;
}
void nn_resetComponentBudgets(nn_Computer *computer) {
for(size_t i = 0; i < computer->componentLen; i++) {
computer->components[i].budgetUsed = 0;
}
}
bool nn_costComponent(nn_Computer *computer, const char *address, double perTick) {
return nn_costComponentN(computer, address, 1, perTick);
}
bool nn_costComponentN(nn_Computer *computer, const char *address, double amount, double perTick) {
for(size_t i = 0; i < computer->componentLen; i++) {
nn_Component *c = &computer->components[i];
if(nn_strcmp(c->address, address) != 0) continue;
c->budgetUsed += (NN_COMPONENT_CALLBUDGET * amount) / perTick;
return c->budgetUsed >= NN_COMPONENT_CALLBUDGET;
}
return false;
}
bool nn_checkstack(nn_Computer *computer, size_t amount) {
return computer->stackSize + amount <= NN_MAX_STACK;
}
static nn_Exit nn_pushvalue(nn_Computer *computer, nn_Value val) {
if(!nn_checkstack(computer, 1)) {
nn_dropValue(val);
return NN_ENOSTACK;
}
computer->callstack[computer->stackSize++] = val;
return NN_OK;
}
nn_Exit nn_pushnull(nn_Computer *computer) {
return nn_pushvalue(computer, (nn_Value) {.type = NN_VAL_NULL});
}
nn_Exit nn_pushbool(nn_Computer *computer, bool truthy) {
return nn_pushvalue(computer, (nn_Value) {.type = NN_VAL_BOOL, .boolean = truthy});
}
nn_Exit nn_pushnumber(nn_Computer *computer, double num) {
return nn_pushvalue(computer, (nn_Value) {.type = NN_VAL_NUM, .number = num});
}
nn_Exit nn_pushstring(nn_Computer *computer, const char *str) {
return nn_pushlstring(computer, str, nn_strlen(str));
}
nn_Exit nn_pushlstring(nn_Computer *computer, const char *str, size_t len) {
nn_Context ctx = computer->universe->ctx;
nn_String *s = nn_alloc(&ctx, sizeof(nn_String) + sizeof(char) * (len + 1));
if(s == NULL) return NN_ENOMEM;
s->ctx = ctx;
s->refc = 1;
s->len = len;
nn_memcpy(s->data, str, sizeof(char) * len);
s->data[len] = '\0';
return nn_pushvalue(computer, (nn_Value) {.type = NN_VAL_STR, .string = s});
}
nn_Exit nn_pushuserdata(nn_Computer *computer, size_t userdataIdx) {
return nn_pushvalue(computer, (nn_Value) {.type = NN_VAL_USERDATA, .userdataIdx = userdataIdx});
}
nn_Exit nn_pusharraytable(nn_Computer *computer, size_t len) {
if(computer->stackSize < len) return NN_EBELOWSTACK;
nn_Context ctx = computer->universe->ctx;
nn_Table *t = nn_alloc(&ctx, sizeof(nn_Table) + sizeof(nn_Value) * len * 2);
if(t == NULL) return NN_ENOMEM;
t->ctx = ctx;
t->refc = 1;
t->len = len;
for(size_t i = 0; i < len; i++) {
t->vals[i*2].type = NN_VAL_NUM;
t->vals[i*2].number = (double)i;
t->vals[i*2+1] = computer->callstack[computer->stackSize - len + i];
}
computer->stackSize -= len;
return nn_pushvalue(computer, (nn_Value) {.type = NN_VAL_TABLE, .table = t});
}
nn_Exit nn_pushtable(nn_Computer *computer, size_t len) {
size_t size = len * 2;
if(computer->stackSize < size) return NN_EBELOWSTACK;
nn_Context ctx = computer->universe->ctx;
nn_Table *t = nn_alloc(&ctx, sizeof(nn_Table) + sizeof(nn_Value) * size);
if(t == NULL) return NN_ENOMEM;
t->ctx = ctx;
t->refc = 1;
t->len = len;
for(size_t i = 0; i < len*2; i++) {
t->vals[i] = computer->callstack[computer->stackSize - size + i];
}
computer->stackSize -= size;
return nn_pushvalue(computer, (nn_Value) {.type = NN_VAL_TABLE, .table = t});
}
nn_Exit nn_pop(nn_Computer *computer) {
return nn_popn(computer, 1);
}
nn_Exit nn_popn(nn_Computer *computer, size_t n) {
if(computer->stackSize < n) return NN_EBELOWSTACK;
for(size_t i = computer->stackSize - n; i < computer->stackSize; i++) {
nn_dropValue(computer->callstack[i]);
}
computer->stackSize -= n;
return NN_OK;
}
nn_Exit nn_dupe(nn_Computer *computer) {
return nn_dupen(computer, 1);
}
nn_Exit nn_dupen(nn_Computer *computer, size_t n) {
if(computer->stackSize < n) return NN_EBELOWSTACK;
if(!nn_checkstack(computer, n)) return NN_ENOSTACK;
for(size_t i = 0; i < n; i++) {
nn_Value v = computer->callstack[computer->stackSize - n + i];
nn_retainValue(v);
computer->callstack[computer->stackSize + i] = v;
}
computer->stackSize += n;
return NN_OK;
}
nn_Exit nn_dupeat(nn_Computer *computer, size_t idx) {
if(computer->stackSize <= idx) return NN_EBELOWSTACK;
nn_Value v = computer->callstack[idx];
nn_retainValue(v);
return nn_pushvalue(computer, v);
}
size_t nn_getstacksize(nn_Computer *computer) {
return computer->stackSize;
}
void nn_clearstack(nn_Computer *computer) {
nn_popn(computer, nn_getstacksize(computer));
}
bool nn_isnull(nn_Computer *computer, size_t idx) {
if(idx >= computer->stackSize) return false;
return computer->callstack[idx].type == NN_VAL_NULL;
}
bool nn_isboolean(nn_Computer *computer, size_t idx) {
if(idx >= computer->stackSize) return false;
return computer->callstack[idx].type == NN_VAL_BOOL;
}
bool nn_isnumber(nn_Computer *computer, size_t idx) {
if(idx >= computer->stackSize) return false;
return computer->callstack[idx].type == NN_VAL_NUM;
}
bool nn_isstring(nn_Computer *computer, size_t idx) {
if(idx >= computer->stackSize) return false;
return computer->callstack[idx].type == NN_VAL_STR;
}
bool nn_isuserdata(nn_Computer *computer, size_t idx) {
if(idx >= computer->stackSize) return false;
return computer->callstack[idx].type == NN_VAL_USERDATA;
}
bool nn_istable(nn_Computer *computer, size_t idx) {
if(idx >= computer->stackSize) return false;
return computer->callstack[idx].type == NN_VAL_TABLE;
}
const char *nn_typenameof(nn_Computer *computer, size_t idx) {
if(idx >= computer->stackSize) return "none";
return nn_typenames[computer->callstack[idx].type];
}
bool nn_toboolean(nn_Computer *computer, size_t idx) {
return computer->callstack[idx].boolean;
}
double nn_tonumber(nn_Computer *computer, size_t idx) {
return computer->callstack[idx].number;
}
const char *nn_tostring(nn_Computer *computer, size_t idx) {
return nn_tolstring(computer, idx, NULL);
}
const char *nn_tolstring(nn_Computer *computer, size_t idx, size_t *len) {
nn_String *s = computer->callstack[idx].string;
if(len != NULL) *len = s->len;
return s->data;
}
size_t nn_touserdata(nn_Computer *computer, size_t idx) {
return computer->callstack[idx].userdataIdx;
}
nn_Exit nn_dumptable(nn_Computer *computer, size_t idx, size_t *len) {
nn_Table *t = computer->callstack[idx].table;
if(!nn_checkstack(computer, t->len * 2)) return NN_ENOSTACK;
if(len != NULL) *len = t->len;
for(size_t i = 0; i < t->len * 2; i++) {
computer->callstack[computer->stackSize + i] = t->vals[i];
}
computer->stackSize += t->len * 2;
return NN_OK;
}
int nn_countValueCost(nn_Computer *computer, size_t values) {
int total = 0;
for(size_t i = 0; i < values; i++) {
nn_Value val = computer->callstack[computer->stackSize - values + i];
total += 2;
switch(val.type) {
case NN_VAL_NULL:
case NN_VAL_BOOL:
total += 4;
continue;
case NN_VAL_NUM:
total += 8;
continue;
case NN_VAL_STR:
total += val.string->len;
if(val.string->len == 0) total++;
continue;
case NN_VAL_TABLE:
case NN_VAL_USERDATA:
return -1;
}
}
return total;
}
size_t nn_countSignalCostValue(nn_Value value) {
size_t total = 2;
switch(value.type) {
case NN_VAL_NULL:
case NN_VAL_BOOL:
total += 4;
break;
case NN_VAL_NUM:
case NN_VAL_USERDATA:
total += 8;
break;
case NN_VAL_STR:
// 2+1
if(value.string->len == 0) total++;
else total += value.string->len;
break;
case NN_VAL_TABLE:
total += 2;
for(size_t i = 0; i < value.table->len * 2; i++) {
total += nn_countSignalCostValue(value.table->vals[i]);
}
break;
}
return total;
}
size_t nn_countSignalCost(nn_Computer *computer, size_t values) {
size_t total = 0;
for(size_t i = 0; i < values; i++) {
nn_Value val = computer->callstack[computer->stackSize - values + i];
total += nn_countSignalCostValue(val);
}
return total;
}
size_t nn_countSignals(nn_Computer *computer) {
return computer->signalCount;
}
nn_Exit nn_pushSignal(nn_Computer *computer, size_t valueCount) {
if(computer->state != NN_RUNNING) return nn_popn(computer, valueCount);
if(computer->signalCount == NN_MAX_SIGNALS) return NN_ELIMIT;
if(computer->stackSize < valueCount) return NN_EBELOWSTACK;
int cost = nn_countSignalCost(computer, valueCount);
if(cost == -1) return NN_EBADSTATE;
if(cost > NN_MAX_SIGNALSIZE) return NN_ELIMIT;
nn_Context ctx = computer->universe->ctx;
nn_Signal s;
s.len = valueCount;
s.values = nn_alloc(&ctx, sizeof(nn_Value) * valueCount);
if(s.values == NULL) return NN_ENOMEM;
for(size_t i = 0; i < valueCount; i++) {
s.values[i] = computer->callstack[computer->stackSize - valueCount + i];
}
computer->stackSize -= valueCount;
computer->signals[computer->signalCount++] = s;
return NN_OK;
}
nn_Exit nn_popSignal(nn_Computer *computer, size_t *valueCount) {
if(computer->signalCount == 0) return NN_EBADSTATE;
nn_Context ctx = computer->universe->ctx;
nn_Signal s = computer->signals[0];
if(!nn_checkstack(computer, s.len)) return NN_ENOSTACK;
if(valueCount != NULL) *valueCount = s.len;
for(size_t i = 0; i < s.len; i++) {
nn_pushvalue(computer, s.values[i]);
}
for(size_t i = 1; i < computer->signalCount; i++) {
computer->signals[i-1] = computer->signals[i];
}
computer->signalCount--;
nn_free(&ctx, s.values, sizeof(nn_Value) * s.len);
return NN_OK;
}
// todo: everything
typedef struct nn_EEPROM_state {
nn_Universe *universe;
nn_EEPROM eeprom;
void *userdata;
nn_EEPROMHandler *handler;
} nn_EEPROM_state;
typedef struct nn_VEEPROM_state {
nn_Universe *universe;
char *code;
size_t codelen;
char *data;
size_t datalen;
size_t archlen;
size_t labellen;
bool isReadonly;
char arch[NN_MAX_ARCHNAME];
char label[NN_MAX_LABEL];
} nn_VEEPROM_state;
nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) {
nn_EEPROM_state *state = req->typeUserdata;
void *instance = req->compUserdata;
// NULL for FREETYPE
nn_Computer *computer = req->computer;
nn_Context ctx = state->universe->ctx;
nn_EEPROMRequest ereq;
ereq.userdata = state->userdata;
ereq.instance = instance;
ereq.computer = computer;
ereq.eepromConf = &state->eeprom;
const char *method = req->methodCalled;
switch(req->action) {
case NN_COMP_FREETYPE:
nn_free(&ctx, state, sizeof(*state));
break;
case NN_COMP_INIT:
return NN_OK;
case NN_COMP_DEINIT:
ereq.action = NN_EEPROM_DROP;
return state->handler(&ereq);
case NN_COMP_ENABLED:
req->methodEnabled = true;
return NN_OK;
case NN_COMP_CALL:
if(nn_strcmp(method, "getSize") == 0) {
return nn_pushnumber(computer, state->eeprom.size);
}
if(nn_strcmp(method, "getDataSize") == 0) {
return nn_pushnumber(computer, state->eeprom.dataSize);
}
if(nn_strcmp(method, "isReadOnly") == 0) {
ereq.action = NN_EEPROM_ISREADONLY;
nn_Exit e = state->handler(&ereq);
if(e) return e;
req->returnCount = 1;
return nn_pushbool(computer, ereq.buflen != 0);
}
if(nn_strcmp(method, "getChecksum") == 0) {
nn_costComponent(computer, req->compAddress, 1);
// yup, on-stack.
// Perhaps in the future we'll make it heap-allocated.
char buf[state->eeprom.size];
ereq.action = NN_EEPROM_GET;
ereq.buf = buf;
ereq.buflen = state->eeprom.size;
nn_Exit e = state->handler(&ereq);
if(e) return e;
unsigned int chksum = nn_computeCRC32(buf, ereq.buflen);
char encoded[8];
nn_crc32ChecksumBytes(chksum, encoded);
req->returnCount = 1;
return nn_pushlstring(computer, encoded, 8);
}
if(nn_strcmp(method, "makeReadonly") == 0) {
nn_costComponent(computer, req->compAddress, 1);
// 1st argument is a string, which is the checksum we're meant to have
if(nn_getstacksize(computer) < 1) {
nn_setError(computer, "bad argument #1 (string expected)");
return NN_EBADCALL;
}
if(!nn_isstring(computer, 0)) {
nn_setError(computer, "bad argument #1 (string expected)");
return NN_EBADCALL;
}
size_t len;
const char *desired = nn_tolstring(computer, 0, &len);
if(len != 8) {
nn_setError(computer, "invalid checksum");
return NN_EBADCALL;
}
// yup, on-stack.
// Perhaps in the future we'll make it heap-allocated.
char buf[state->eeprom.size];
ereq.action = NN_EEPROM_GET;
ereq.buf = buf;
ereq.buflen = state->eeprom.size;
nn_Exit e = state->handler(&ereq);
if(e) return e;
unsigned int chksum = nn_computeCRC32(buf, ereq.buflen);
char encoded[8];
nn_crc32ChecksumBytes(chksum, encoded);
if(nn_memcmp(encoded, desired, sizeof(char) * 8)) {
nn_setError(computer, "incorrect checksum");
return NN_EBADCALL;
}
ereq.action = NN_EEPROM_MAKEREADONLY;
e = state->handler(&ereq);
if(e) return e;
req->returnCount = 1;
return nn_pushbool(computer, true);
}
if(nn_strcmp(method, "getLabel") == 0) {
nn_costComponent(computer, req->compAddress, 1);
char buf[NN_MAX_LABEL];
ereq.action = NN_EEPROM_GETLABEL;
ereq.buf = buf;
ereq.buflen = NN_MAX_LABEL;
nn_Exit e = state->handler(&ereq);
if(e) return e;
req->returnCount = 1;
if(ereq.buf == NULL) return nn_pushnull(computer);
return nn_pushlstring(computer, buf, ereq.buflen);
}
if(nn_strcmp(method, "setLabel") == 0) {
nn_costComponent(computer, req->compAddress, 1);
if(nn_getstacksize(computer) < 1) {
nn_setError(computer, "bad argument #1 (string expected)");
return NN_EBADCALL;
}
if(!nn_isstring(computer, 0)) {
nn_setError(computer, "bad argument #1 (string expected)");
return NN_EBADCALL;
}
size_t len;
const char *s = nn_tolstring(computer, 0, &len);
if(len > NN_MAX_LABEL) len = NN_MAX_LABEL;
char buf[NN_MAX_LABEL];
nn_memcpy(buf, s, sizeof(char) * len);
ereq.action = NN_EEPROM_SETLABEL;
ereq.buf = buf;
ereq.buflen = len;
nn_Exit e = state->handler(&ereq);
if(e) return e;
req->returnCount = 1;
return nn_pushlstring(computer, buf, ereq.buflen);
}
if(nn_strcmp(method, "get") == 0) {
nn_costComponent(computer, req->compAddress, 1);
// yup, on-stack.
// Perhaps in the future we'll make it heap-allocated.
char buf[state->eeprom.size];
ereq.action = NN_EEPROM_GET;
ereq.buf = buf;
ereq.buflen = state->eeprom.size;
nn_Exit e = state->handler(&ereq);
if(e) return e;
req->returnCount = 1;
return nn_pushlstring(computer, buf, ereq.buflen);
}
if(nn_strcmp(method, "getData") == 0) {
nn_costComponent(computer, req->compAddress, 1);
// yup, on-stack.
// Perhaps in the future we'll make it heap-allocated.
char buf[state->eeprom.dataSize];
ereq.action = NN_EEPROM_GETDATA;
ereq.buf = buf;
ereq.buflen = state->eeprom.dataSize;
nn_Exit e = state->handler(&ereq);
if(e) return e;
req->returnCount = 1;
return nn_pushlstring(computer, buf, ereq.buflen);
}
if(nn_strcmp(method, "getArchitecture") == 0) {
nn_costComponent(computer, req->compAddress, 1);
char buf[NN_MAX_ARCHNAME];
ereq.action = NN_EEPROM_GETARCH;
ereq.buf = buf;
ereq.buflen = NN_MAX_ARCHNAME;
nn_Exit e = state->handler(&ereq);
if(e) return e;
req->returnCount = 1;
return nn_pushlstring(computer, buf, ereq.buflen);
}
if(nn_strcmp(method, "set") == 0) {
nn_costComponent(computer, req->compAddress, 1);
if(nn_getstacksize(computer) < 1) {
nn_setError(computer, "bad argument #1 (string expected)");
return NN_EBADCALL;
}
if(!nn_isstring(computer, 0)) {
nn_setError(computer, "bad argument #1 (string expected)");
return NN_EBADCALL;
}
size_t len;
const char *s = nn_tolstring(computer, 0, &len);
if(len > state->eeprom.size) {
nn_setError(computer, "not enough space");
return NN_EBADCALL;
}
ereq.action = NN_EEPROM_SET;
// DO NOT MODIFY IT!!!!
ereq.buf = (char*)s;
ereq.buflen = len;
return state->handler(&ereq);
}
if(nn_strcmp(method, "setData") == 0) {
nn_costComponent(computer, req->compAddress, 1);
if(nn_getstacksize(computer) < 1) {
nn_setError(computer, "bad argument #1 (string expected)");
return NN_EBADCALL;
}
if(!nn_isstring(computer, 0)) {
nn_setError(computer, "bad argument #1 (string expected)");
return NN_EBADCALL;
}
size_t len;
const char *s = nn_tolstring(computer, 0, &len);
if(len > state->eeprom.dataSize) {
nn_setError(computer, "not enough space");
return NN_EBADCALL;
}
ereq.action = NN_EEPROM_SETDATA;
// DO NOT MODIFY IT!!!!
ereq.buf = (char*)s;
ereq.buflen = len;
return state->handler(&ereq);
}
if(nn_strcmp(method, "setArchitecture") == 0) {
nn_costComponent(computer, req->compAddress, 1);
if(nn_getstacksize(computer) < 1) {
nn_setError(computer, "bad argument #1 (string expected)");
return NN_EBADCALL;
}
if(!nn_isstring(computer, 0)) {
nn_setError(computer, "bad argument #1 (string expected)");
return NN_EBADCALL;
}
size_t len;
const char *s = nn_tolstring(computer, 0, &len);
if(len > NN_MAX_ARCHNAME) {
nn_setError(computer, "not enough space");
return NN_EBADCALL;
}
ereq.action = NN_EEPROM_SETARCH;
// DO NOT MODIFY IT!!!!
ereq.buf = (char*)s;
ereq.buflen = len;
return state->handler(&ereq);
}
return NN_OK;
}
return NN_OK;
}
nn_EEPROM nn_defaultEEPROM = (nn_EEPROM) {
.size = 4 * NN_KiB,
.dataSize = 256,
.readEnergyCost = 10,
.writeEnergyCost = 100,
.readDataEnergyCost = 10,
.writeDataEnergyCost = 50,
};
nn_ComponentType *nn_createEEPROM(nn_Universe *universe, const nn_EEPROM *eeprom, nn_EEPROMHandler *handler, void *userdata) {
nn_Context ctx = universe->ctx;
nn_EEPROM_state *state = nn_alloc(&ctx, sizeof(*state));
if(state == NULL) return NULL;
state->universe = universe;
state->eeprom = *eeprom;
state->userdata = userdata;
state->handler = handler;
const nn_Method methods[] = {
{"getSize", "function(): number - Get the storage capacity of the EEPROM.", NN_DIRECT},
{"getDataSize", "function(): number - Get the storage capacity of the EEPROM data.", NN_DIRECT},
{"getLabel", "function(): string - Get the EEPROM label", NN_INDIRECT},
{"setLabel", "function(label: string): string - Set the EEPROM label and return what was actually set, which may be truncated.", NN_INDIRECT},
{"get", "function(): string - Get the current EEPROM contents.", NN_INDIRECT},
{"getData", "function(): string - Get the current EEPROM data contents.", NN_INDIRECT},
{"set", "function(data: string) - Set the current EEPROM contents.", NN_INDIRECT},
{"setData", "function(data: string) - Set the current EEPROM data contents.", NN_INDIRECT},
{"getArchitecture", "function(): string - Get the current EEPROM architecture intended.", NN_INDIRECT},
{"setArchitecture", "function(data: string) - Set the current EEPROM architecture intended.", NN_INDIRECT},
{"isReadOnly", "function(): boolean - Returns whether the EEPROM is read-only.", NN_INDIRECT},
{"makeReadonly", "function(checksum: string) - Makes the EEPROM read-only, this cannot be undone.", NN_INDIRECT},
{"getChecksum", "function(): string - Returns a simple checksum of the EEPROM's contents and data.", NN_INDIRECT},
{NULL, NULL, NN_INDIRECT},
};
nn_ComponentType *t = nn_createComponentType(universe, "eeprom", state, methods, nn_eeprom_handler);
if(t == NULL) {
nn_free(&ctx, state, sizeof(*state));
return NULL;
}
return t;
}
static nn_Exit nn_veeprom_handler(nn_EEPROMRequest *req) {
const nn_EEPROM *conf = req->eepromConf;
nn_VEEPROM_state *state = req->userdata;
nn_Context ctx = state->universe->ctx;
switch(req->action) {
case NN_EEPROM_DROP:
nn_free(&ctx, state->code, sizeof(char) * conf->size);
nn_free(&ctx, state->data, sizeof(char) * conf->dataSize);
nn_free(&ctx, state, sizeof(*state));
return NN_OK;
case NN_EEPROM_ISREADONLY:
req->buflen = state->isReadonly ? 1 : 0;
return NN_OK;
case NN_EEPROM_MAKEREADONLY:
state->isReadonly = true;
return NN_OK;
case NN_EEPROM_GET:
req->buflen = state->codelen;
nn_memcpy(req->buf, state->code, sizeof(char) * state->codelen);
return NN_OK;
case NN_EEPROM_GETDATA:
req->buflen = state->datalen;
nn_memcpy(req->buf, state->data, sizeof(char) * state->datalen);
return NN_OK;
case NN_EEPROM_GETARCH:
req->buflen = state->archlen;
nn_memcpy(req->buf, state->arch, sizeof(char) * state->archlen);
return NN_OK;
case NN_EEPROM_GETLABEL:
req->buflen = state->labellen;
nn_memcpy(req->buf, state->label, sizeof(char) * state->labellen);
return NN_OK;
case NN_EEPROM_SET:
state->codelen = req->buflen;
nn_memcpy(state->code, req->buf, sizeof(char) * state->codelen);
return NN_OK;
case NN_EEPROM_SETDATA:
state->datalen = req->buflen;
nn_memcpy(state->data, req->buf, sizeof(char) * state->datalen);
return NN_OK;
case NN_EEPROM_SETARCH:
state->archlen = req->buflen;
nn_memcpy(state->arch, req->buf, sizeof(char) * state->archlen);
return NN_OK;
case NN_EEPROM_SETLABEL:
state->labellen = req->buflen;
nn_memcpy(state->label, req->buf, sizeof(char) * state->labellen);
return NN_OK;
}
return NN_OK;
}
nn_ComponentType *nn_createVEEPROM(nn_Universe *universe, const nn_EEPROM *eeprom, const nn_VEEPROM *vmem) {
nn_Context ctx = universe->ctx;
char *code = NULL;
char *data = NULL;
size_t archlen = 0;
nn_VEEPROM_state *state = nn_alloc(&ctx, sizeof(*state));
if(state == NULL) goto fail;
state->universe = universe;
state->isReadonly = vmem->isReadonly;
code = nn_alloc(&ctx, sizeof(char) * eeprom->size);
if(code == NULL) goto fail;
data = nn_alloc(&ctx, sizeof(char) * eeprom->dataSize);
if(data == NULL) goto fail;
state->code = code;
state->data = data;
state->codelen = vmem->codelen;
state->datalen = vmem->datalen;
state->labellen = vmem->labellen;
nn_memcpy(state->code, vmem->code, sizeof(char) * state->codelen);
nn_memcpy(state->data, vmem->data, sizeof(char) * state->datalen);
nn_memcpy(state->label, vmem->label, sizeof(char) * state->labellen);
if(vmem->arch != NULL) {
archlen = nn_strlen(vmem->arch);
}
state->archlen = archlen;
nn_memcpy(state->arch, vmem->arch, sizeof(char) * archlen);
nn_ComponentType *ty = nn_createEEPROM(universe, eeprom, nn_veeprom_handler, state);
if(ty == NULL) goto fail;
return ty;
fail:;
// remember, freeing NULL is fine!
nn_free(&ctx, code, sizeof(char) * eeprom->size);
nn_free(&ctx, data, sizeof(char) * eeprom->dataSize);
nn_free(&ctx, state, sizeof(*state));
return NULL;
}
typedef struct nn_Filesystem_state {
nn_Universe *universe;
void *userdata;
nn_FilesystemHandler *handler;
nn_Filesystem fs;
} nn_Filesystem_state;
nn_Filesystem nn_defaultFilesystems[4] = {
(nn_Filesystem) {
.spaceTotal = 1 * NN_MiB,
.readsPerTick = 4,
.writesPerTick = 2,
.dataEnergyCost = 256.0 / NN_MiB,
},
(nn_Filesystem) {
.spaceTotal = 2 * NN_MiB,
.readsPerTick = 4,
.writesPerTick = 2,
.dataEnergyCost = 512.0 / NN_MiB,
},
(nn_Filesystem) {
.spaceTotal = 4 * NN_MiB,
.readsPerTick = 7,
.writesPerTick = 3,
.dataEnergyCost = 1024.0 / NN_MiB,
},
(nn_Filesystem) {
.spaceTotal = 8 * NN_MiB,
.readsPerTick = 13,
.writesPerTick = 5,
.dataEnergyCost = 2048.0 / NN_MiB,
},
};
nn_Filesystem nn_defaultFloppy = (nn_Filesystem) {
.spaceTotal = 512 * NN_KiB,
.readsPerTick = 1,
.writesPerTick = 1,
.dataEnergyCost = 8.0 / NN_MiB,
};
nn_Exit nn_filesystem_handler(nn_ComponentRequest *req) {
nn_Filesystem_state *state = req->typeUserdata;
void *instance = req->compUserdata;
// NULL for FREETYPE
nn_Computer *computer = req->computer;
nn_Context ctx = state->universe->ctx;
nn_FilesystemRequest fsreq;
fsreq.userdata = state->userdata;
fsreq.instance = instance;
fsreq.computer = computer;
fsreq.fsConf = &state->fs;
const char *method = req->methodCalled;
nn_Exit err;
switch(req->action) {
case NN_COMP_FREETYPE:
nn_free(&ctx, state, sizeof(*state));
break;
case NN_COMP_INIT:
return NN_OK;
case NN_COMP_DEINIT:
fsreq.action = NN_FS_DROP;
return state->handler(&fsreq);
case NN_COMP_ENABLED:
req->methodEnabled = true;
return NN_OK;
case NN_COMP_CALL:
if(nn_strcmp(method, "spaceTotal") == 0) {
req->returnCount = 1;
return nn_pushnumber(computer, state->fs.spaceTotal);
}
if(nn_strcmp(method, "spaceUsed") == 0) {
fsreq.action = NN_FS_SPACEUSED;
err = state->handler(&fsreq);
if(err) return err;
req->returnCount = 1;
return nn_pushnumber(computer, fsreq.size);
}
if(nn_strcmp(method, "isReadOnly") == 0) {
fsreq.action = NN_FS_ISREADONLY;
err = state->handler(&fsreq);
if(err) return err;
req->returnCount = 1;
return nn_pushbool(computer, fsreq.size != 0);
}
if(nn_strcmp(method, "getLabel") == 0) {
char buf[NN_MAX_LABEL];
fsreq.action = NN_FS_GETLABEL;
fsreq.strarg1 = buf;
fsreq.strarg1len = NN_MAX_LABEL;
err = state->handler(&fsreq);
if(err) return err;
req->returnCount = 1;
if(fsreq.strarg1 == NULL) return nn_pushnull(computer);
return nn_pushlstring(computer, buf, fsreq.strarg1len);
}
if(nn_strcmp(method, "setLabel") == 0) {
if(nn_getstacksize(computer) < 1) {
nn_setError(computer, "bad argument #1 (string expected)");
return NN_EBADCALL;
}
if(!nn_isstring(computer, 0)) {
nn_setError(computer, "bad argument #1 (string expected)");
return NN_EBADCALL;
}
fsreq.action = NN_FS_SETLABEL;
// DO NOT MODIFY THE BUFFER!!!
fsreq.strarg1 = (char *)nn_tolstring(computer, 0, &fsreq.strarg1len);
err = state->handler(&fsreq);
if(err) return err;
req->returnCount = 1;
if(fsreq.strarg1 == NULL) return nn_pushnull(computer);
return nn_pushlstring(computer, fsreq.strarg1, fsreq.strarg1len);
}
if(nn_strcmp(method, "open") == 0) {
if(nn_getstacksize(computer) < 1) {
nn_setError(computer, "bad argument #1 (string expected)");
return NN_EBADCALL;
}
if(!nn_isstring(computer, 0)) {
nn_setError(computer, "bad argument #1 (string expected)");
return NN_EBADCALL;
}
if(nn_getstacksize(computer) < 2) {
err = nn_pushstring(computer, "r");
if(err) return err;
}
if(!nn_isstring(computer, 1)) {
nn_setError(computer, "bad argument #2 (string expected)");
return NN_EBADCALL;
}
size_t pathlen;
const char *path = nn_tolstring(computer, 0, &pathlen);
if(pathlen >= NN_MAX_PATH) {
nn_setError(computer, "path too long");
return NN_EBADCALL;
}
char truepath[NN_MAX_PATH];
nn_simplifyPath(path, truepath);
size_t modelen;
const char *mode = nn_tolstring(computer, 1, &modelen);
fsreq.action = NN_FS_OPEN;
fsreq.strarg1 = truepath;
fsreq.strarg1len = nn_strlen(truepath);
fsreq.strarg2 = (char *)mode;
fsreq.strarg2len = modelen;
err = state->handler(&fsreq);
if(err) return err;
req->returnCount = 1;
return nn_pushnumber(computer, fsreq.fd);
}
if(nn_strcmp(method, "close") == 0) {
if(nn_getstacksize(computer) < 1) {
nn_setError(computer, "bad argument #1 (integer expected)");
return NN_EBADCALL;
}
if(!nn_isnumber(computer, 0)) {
nn_setError(computer, "bad argument #1 (integer expected)");
return NN_EBADCALL;
}
fsreq.fd = nn_tonumber(computer, 0);
fsreq.action = NN_FS_CLOSE;
return state->handler(&fsreq);
}
if(nn_strcmp(method, "read") == 0) {
if(nn_getstacksize(computer) < 1) {
nn_setError(computer, "bad argument #1 (integer expected)");
return NN_EBADCALL;
}
if(!nn_isnumber(computer, 0)) {
nn_setError(computer, "bad argument #1 (integer expected)");
return NN_EBADCALL;
}
if(nn_getstacksize(computer) < 2) {
err = nn_pushnumber(computer, NN_MAX_READ);
if(err) return err;
}
if(!nn_isnumber(computer, 1)) {
nn_setError(computer, "bad argument #2 (integer expected)");
return NN_EBADCALL;
}
fsreq.action = NN_FS_READ;
fsreq.fd = nn_tonumber(computer, 0);
size_t requested = nn_tonumber(computer, 1);
if(requested > NN_MAX_READ) requested = NN_MAX_READ;
char buf[requested];
fsreq.strarg1 = buf;
fsreq.strarg1len = requested;
err = state->handler(&fsreq);
if(err) return err;
req->returnCount = 1;
if(fsreq.strarg1 == NULL) return nn_pushnull(computer);
return nn_pushlstring(computer, fsreq.strarg1, fsreq.strarg1len);
}
if(nn_strcmp(method, "write") == 0) {
if(nn_getstacksize(computer) < 1) {
nn_setError(computer, "bad argument #1 (integer expected)");
return NN_EBADCALL;
}
if(!nn_isnumber(computer, 0)) {
nn_setError(computer, "bad argument #1 (integer expected)");
return NN_EBADCALL;
}
if(nn_getstacksize(computer) < 2) {
nn_setError(computer, "bad argument #2 (string expected)");
return NN_EBADCALL;
}
if(!nn_isnumber(computer, 1)) {
nn_setError(computer, "bad argument #2 (string expected)");
return NN_EBADCALL;
}
fsreq.action = NN_FS_WRITE;
fsreq.fd = nn_tonumber(computer, 0);
fsreq.strarg1 = (char *)nn_tolstring(computer, 1, &fsreq.strarg1len);
err = state->handler(&fsreq);
if(err) return err;
req->returnCount = 1;
return nn_pushbool(computer, true);
}
if(nn_strcmp(method, "seek") == 0) {
if(nn_getstacksize(computer) < 1) {
nn_setError(computer, "bad argument #1 (integer expected)");
return NN_EBADCALL;
}
if(!nn_isnumber(computer, 0)) {
nn_setError(computer, "bad argument #1 (integer expected)");
return NN_EBADCALL;
}
if(nn_getstacksize(computer) < 2) {
err = nn_pushstring(computer, "cur");
if(err) return err;
}
if(!nn_isstring(computer, 1)) {
nn_setError(computer, "bad argument #2 (string expected)");
return NN_EBADCALL;
}
if(nn_getstacksize(computer) < 2) {
err = nn_pushnumber(computer, 0);
if(err) return err;
}
if(!nn_isnumber(computer, 2)) {
nn_setError(computer, "bad argument #2 (string expected)");
return NN_EBADCALL;
}
fsreq.action = NN_FS_SEEK;
fsreq.fd = nn_tonumber(computer, 0);
const char *whence = nn_tostring(computer, 1);
fsreq.off = nn_tonumber(computer, 2);
if(nn_strcmp(whence, "set") == 0) {
fsreq.whence = NN_SEEK_SET;
} else if(nn_strcmp(whence, "cur") == 0) {
fsreq.whence = NN_SEEK_CUR;
} else if(nn_strcmp(whence, "end") == 0) {
fsreq.whence = NN_SEEK_END;
} else {
nn_setError(computer, "bad seek whence");
return NN_EBADCALL;
}
err = state->handler(&fsreq);
return nn_pushnumber(computer, fsreq.off);
}
if(nn_strcmp(method, "list") == 0) {
if(nn_getstacksize(computer) < 1) {
nn_setError(computer, "bad argument #1 (string expected)");
return NN_EBADCALL;
}
if(!nn_isstring(computer, 0)) {
nn_setError(computer, "bad argument #1 (string expected)");
return NN_EBADCALL;
}
char truepath[NN_MAX_PATH];
size_t pathlen;
const char *path = nn_tolstring(computer, 0, &pathlen);
if(pathlen >= NN_MAX_PATH) {
nn_setError(computer, "path too long");
return NN_EBADCALL;
}
nn_simplifyPath(path, truepath);
int dirfd;
fsreq.action = NN_FS_OPENDIR;
fsreq.strarg1 = truepath;
fsreq.strarg1len = nn_strlen(truepath);
err = state->handler(&fsreq);
if(err) return err;
dirfd = fsreq.fd;
// this sucks hard
size_t entryCount = 0;
while(1) {
char entry[NN_MAX_PATH];
fsreq.action = NN_FS_READDIR;
fsreq.fd = dirfd;
fsreq.strarg1 = entry;
fsreq.strarg1len = NN_MAX_PATH;
err = state->handler(&fsreq);
if(err) goto list_fail;
if(fsreq.strarg1 == NULL) break;
if(fsreq.strarg1len == 1 && entry[0] == '.') continue;
if(fsreq.strarg1len == 2 && entry[0] == '.' && entry[1] == '.') continue;
err = nn_pushlstring(computer, entry, fsreq.strarg1len);
if(err) goto list_fail;
entryCount++;
}
err = nn_pusharraytable(computer, entryCount);
if(err) goto list_fail;
req->returnCount = 1;
fsreq.action = NN_FS_CLOSEDIR;
fsreq.fd = dirfd;
state->handler(&fsreq);
return NN_OK;
list_fail:
fsreq.action = NN_FS_CLOSEDIR;
fsreq.fd = dirfd;
state->handler(&fsreq);
return err;
}
if(nn_strcmp(method, "exists") == 0) {
if(nn_getstacksize(computer) < 1) {
nn_setError(computer, "bad argument #1 (string expected)");
return NN_EBADCALL;
}
if(!nn_isstring(computer, 0)) {
nn_setError(computer, "bad argument #1 (string expected)");
return NN_EBADCALL;
}
char truepath[NN_MAX_PATH];
size_t pathlen;
const char *path = nn_tolstring(computer, 0, &pathlen);
if(pathlen >= NN_MAX_PATH) {
nn_setError(computer, "path too long");
return NN_EBADCALL;
}
nn_simplifyPath(path, truepath);
fsreq.action = NN_FS_EXISTS;
fsreq.strarg1 = truepath;
fsreq.strarg1len = nn_strlen(truepath);
err = state->handler(&fsreq);
if(err) return err;
req->returnCount = 1;
return nn_pushbool(computer, fsreq.size != 0);
}
}
return NN_OK;
}
nn_ComponentType *nn_createFilesystem(nn_Universe *universe, const nn_Filesystem *filesystem, nn_FilesystemHandler *handler, void *userdata) {
nn_Context ctx = universe->ctx;
nn_Filesystem_state *state = nn_alloc(&ctx, sizeof(*state));
if(state == NULL) return NULL;
state->universe = universe;
state->userdata = userdata;
state->handler = handler;
state->fs = *filesystem;
const nn_Method methods[] = {
{"spaceTotal", "function(): number - Get the storage capacity of the filesystem.", NN_DIRECT},
{"spaceUsed", "function(): number - Get the space used by the files on the drive.", NN_DIRECT},
{"isReadOnly", "function(): boolean - Returns whether the drive is read-only.", NN_DIRECT},
{"getLabel", "function(): string - Get the filesystem label.", NN_INDIRECT},
{"setLabel", "function(label: string): string - Set the filesystem label and return what was actually set, which may be truncated.", NN_INDIRECT},
{"open", "function(path: string, mode?: string = 'r'): number - Open a file. Valid modes are 'r' (read-only), 'w' (write-only) and 'a' (append-only).", NN_INDIRECT},
{"close", "function(fd: number) - Closes a file.", NN_INDIRECT},
{"read", "function(fd: number, count?: number): string? - Reads part of a file. If there is no more data, nothing is returned.", NN_INDIRECT},
{"write", "function(fd: number, data: string): boolean - Writes the data to a file. Returns true on success.", NN_INDIRECT},
{"seek", "function(fd: number, whence: string, offset: number): number - Seeks a file. Valid whences are 'set' (relative to start), 'cur' (relative to current), 'end' (relative to EoF, backwards). Returns the new position.", NN_INDIRECT},
{"list", "function(path: string): string[] - Returns the names of the entries inside of the directory. Directories get a / appended to their names.", NN_INDIRECT},
{"exists", "function(path: string): boolean - Checks if there exists an entry at the specified path.", NN_INDIRECT},
{"size", "function(path: string) - Gets the size of the entry at the specified path.", NN_INDIRECT},
{"remove", "function(path: string): boolean - Removes the entry at the specified path.", NN_INDIRECT},
{"rename", "function(from: string, to: string): boolean - Renames or moves an entry to a new location.", NN_INDIRECT},
{"isDirectory", "function(path: string): boolean - Checks if the entry at the specified path is a directory.", NN_INDIRECT},
{"lastModified", "function(path: string): number - Returns the UNIX timestamp of the time the entry was last modified. This is stored in milliseconds, but is always a multiple of 1000.", NN_INDIRECT},
{"makeDirectory", "function(path: string): boolean - Creates a directory. Creates parent directories if necessary.", NN_INDIRECT},
{NULL, NULL, NN_INDIRECT},
};
nn_ComponentType *t = nn_createComponentType(universe, "filesystem", state, methods, nn_filesystem_handler);
if(t == NULL) {
nn_free(&ctx, state, sizeof(*state));
return NULL;
}
return t;
}