Initial commit

This commit is contained in:
2020-10-28 00:16:30 -05:00
commit 2e1b745624
34 changed files with 3540 additions and 0 deletions

18
src/cbaselib.c Normal file
View File

@@ -0,0 +1,18 @@
#include "cbaselib.h"
#include "cvalue.h"
#include "cobj.h"
void cosmoB_loadlibrary(CState *state) {
cosmoV_register(state, "print", cosmoV_newObj(cosmoO_newCFunction(state, cosmoB_print)));
}
int cosmoB_print(CState *state, int nargs, CValue *args) {
for (int i = 0; i < nargs; i++) {
CObjString *str = cosmoV_toString(state, args[i]);
printf("%s", cosmoO_readCString(str));
}
printf("\n");
return 0; // print doesn't return any args
}

9
src/cbaselib.h Normal file
View File

@@ -0,0 +1,9 @@
#ifndef COSMO_BASELIB
#define COSMO_BASELIB
#include "cstate.h"
COSMO_API void cosmoB_loadlibrary(CState *state);
COSMO_API int cosmoB_print(CState *state, int nargs, CValue *args);
#endif

70
src/cchunk.c Normal file
View File

@@ -0,0 +1,70 @@
#include "cmem.h"
#include "cchunk.h"
#include "cvalue.h"
#include "cvm.h"
CChunk *newChunk(CState* state, size_t startCapacity) {
CChunk *chunk = cosmoM_xmalloc(state, sizeof(CChunk));
initChunk(state, chunk, startCapacity);
return chunk;
}
void initChunk(CState* state, CChunk *chunk, size_t startCapacity) {
chunk->capacity = startCapacity;
chunk->lineCapacity = startCapacity;
chunk->count = 0;
chunk->buf = NULL; // when writeByteChunk is called, it'll allocate the array for us
chunk->lineInfo = NULL;
// constants
initValArray(state, &chunk->constants, ARRAY_START);
}
void cleanChunk(CState* state, CChunk *chunk) {
// first, free the chunk buffer
cosmoM_freearray(state, INSTRUCTION, chunk->buf, chunk->capacity);
// then the line info
cosmoM_freearray(state, int, chunk->lineInfo, chunk->capacity);
// free the constants
cleanValArray(state, &chunk->constants);
}
void freeChunk(CState* state, CChunk *chunk) {
cleanChunk(state, chunk);
// now, free the wrapper struct
cosmoM_free(state, CChunk, chunk);
}
int addConstant(CState* state, CChunk *chunk, CValue value) {
// before adding the constant, check if we already have it
for (int i = 0; i < chunk->constants.count; i++) {
if (cosmoV_equal(value, chunk->constants.values[i]))
return i; // we already have a matching constant!
}
cosmoM_freezeGC(state); // so our GC doesn't free it
appendValArray(state, &chunk->constants, value);
cosmoM_unfreezeGC(state);
return chunk->constants.count - 1; // return the index of the new constants
}
// ================================================================ [WRITE TO CHUNK] ================================================================
void writeu8Chunk(CState* state, CChunk *chunk, INSTRUCTION i, int line) {
// does the buffer need to be reallocated?
cosmoM_growarray(state, INSTRUCTION, chunk->buf, chunk->count, chunk->capacity);
cosmoM_growarray(state, int, chunk->lineInfo, chunk->count, chunk->lineCapacity);
// write data to the chunk :)
chunk->lineInfo[chunk->count] = line;
chunk->buf[chunk->count++] = i;
}
void writeu16Chunk(CState* state, CChunk *chunk, uint16_t i, int line) {
INSTRUCTION *buffer = (INSTRUCTION*)(&i);
int sz = sizeof(uint16_t) / sizeof(INSTRUCTION);
for (int i = 0; i < sz; i++) {
writeu8Chunk(state, chunk, buffer[i], line);
}
}

39
src/cchunk.h Normal file
View File

@@ -0,0 +1,39 @@
#ifndef CCHUNK_H
#define CCHUNK_H
#include "cosmo.h"
#include "coperators.h"
#include "cvalue.h"
typedef struct CValueArray CValueArray;
typedef struct CChunk {
size_t capacity; // the ammount of space we've allocated for
size_t count; // the space we're currently using
INSTRUCTION *buf; // whole chunk
CValueArray constants; // holds constants
size_t lineCapacity;
int *lineInfo;
} CChunk;
CChunk *newChunk(CState* state, size_t startCapacity);
void initChunk(CState* state, CChunk *chunk, size_t startCapacity);
void cleanChunk(CState* state, CChunk *chunk); // free's everything but the struct
void freeChunk(CState* state, CChunk *chunk); // free's everything including the struct
int addConstant(CState* state, CChunk *chunk, CValue value);
// write to chunk
void writeu8Chunk(CState* state, CChunk *chunk, INSTRUCTION i, int line);
void writeu16Chunk(CState* state, CChunk *chunk, uint16_t i, int line);
// read from chunk
static inline INSTRUCTION readu8Chunk(CChunk *chunk, int offset) {
return chunk->buf[offset];
}
static inline uint16_t readu16Chunk(CChunk *chunk, int offset) {
return *((uint16_t*)(&chunk->buf[offset]));
}
#endif

158
src/cdebug.c Normal file
View File

@@ -0,0 +1,158 @@
#include "cdebug.h"
#include "cvalue.h"
#include "cobj.h"
void printIndent(int indent) {
for (int i = 0; i < indent; i++)
printf("\t");
}
int simpleInstruction(const char *name, int offset) {
printf("%s", name);
return offset + 1; // consume opcode
}
int shortOperandInstruction(const char *name, CChunk *chunk, int offset) {
printf("%-16s [%03d]", name, readu8Chunk(chunk, offset + 1));
return offset + 2;
}
int longOperandInstruction(const char *name, CChunk *chunk, int offset) {
printf("%-16s [%05d]", name, readu16Chunk(chunk, offset + 1));
return offset + 1 + (sizeof(uint16_t) / sizeof(INSTRUCTION));
}
int constInstruction(const char *name, CChunk *chunk, int offset, int indent) {
int index = readu16Chunk(chunk, offset + 1);
printf("%-16s [%05d] - ", name, index);
CValue val = chunk->constants.values[index];
printValue(val);
return offset + 1 + (sizeof(uint16_t) / sizeof(INSTRUCTION)); // consume opcode + uint
}
int ABOperandInstruction(const char *name, CChunk *chunk, int offset) {
int args = readu8Chunk(chunk, offset + 1);
int nresults = readu8Chunk(chunk, offset + 2);
printf("%-16s [%03d] [%03d]", name, args, nresults);
return offset + 3;
}
// public methods in the cdebug.h header
void disasmChunk(CChunk *chunk, const char *name, int indent) {
printIndent(indent);
printf("===[[ %s ]]===\n", name);
for (int offset = 0; offset < chunk->count;) {
offset = disasmInstr(chunk, offset, indent);
printf("\n");
}
}
int disasmInstr(CChunk *chunk, int offset, int indent) {
printIndent(indent);
printf("%04d ", offset);
INSTRUCTION i = chunk->buf[offset];
int line = chunk->lineInfo[offset];
if (offset > 0 && line == chunk->lineInfo[offset - 1]) {
printf(" | ");
} else {
printf("%4d ", line);
}
switch (i) {
case OP_LOADCONST:
return constInstruction("OP_LOADCONST", chunk, offset, indent);
case OP_SETGLOBAL:
return constInstruction("OP_SETGLOBAL", chunk, offset, indent);
case OP_GETGLOBAL:
return constInstruction("OP_GETGLOBAL", chunk, offset, indent);
case OP_SETLOCAL:
return shortOperandInstruction("OP_SETLOCAL", chunk, offset);
case OP_GETLOCAL:
return shortOperandInstruction("OP_GETLOCAL", chunk, offset);
case OP_SETUPVAL:
return shortOperandInstruction("OP_SETUPVAL", chunk, offset);
case OP_GETUPVAL:
return shortOperandInstruction("OP_GETUPVAL", chunk, offset);
case OP_PEJMP:
return longOperandInstruction("OP_PEJMP", chunk, offset);
case OP_EJMP:
return longOperandInstruction("OP_EJMP", chunk, offset);
case OP_JMP:
return longOperandInstruction("OP_JMP", chunk, offset);
case OP_JMPBACK:
return longOperandInstruction("OP_JMPBACK", chunk, offset);
case OP_POP:
return shortOperandInstruction("OP_POP", chunk, offset);
case OP_CALL:
return ABOperandInstruction("OP_CALL", chunk, offset);
case OP_CLOSURE: {
int index = readu16Chunk(chunk, offset + 1);
printf("%-16s [%05d] - ", "OP_CLOSURE", index);
CValue val = chunk->constants.values[index];
CObjFunction *cobjFunc = (CObjFunction*)val.val.obj;
offset += 3; // we consumed the opcode + u16
printValue(val);
printf("\n");
// list the upvalues/locals that are captured
for (int i = 0; i < cobjFunc->upvals; i++) {
uint8_t encoding = readu8Chunk(chunk, offset++);
uint8_t index = readu8Chunk(chunk, offset++);
printIndent(indent + 1);
printf("references %s [%d]\n", encoding == OP_GETLOCAL ? "local" : "upvalue", index);
}
// print the chunk
disasmChunk(&cobjFunc->chunk, cobjFunc->name == NULL ? UNNAMEDCHUNK : cobjFunc->name->str, indent+1);
return offset;
}
case OP_CLOSE:
return simpleInstruction("OP_CLOSE", offset);
case OP_ADD:
return simpleInstruction("OP_ADD", offset);
case OP_SUB:
return simpleInstruction("OP_SUB", offset);
case OP_MULT:
return simpleInstruction("OP_MULT", offset);
case OP_DIV:
return simpleInstruction("OP_DIV", offset);
case OP_TRUE:
return simpleInstruction("OP_TRUE", offset);
case OP_FALSE:
return simpleInstruction("OP_FALSE", offset);
case OP_NIL:
return simpleInstruction("OP_NIL", offset);
case OP_NOT:
return simpleInstruction("OP_NOT", offset);
case OP_EQUAL:
return simpleInstruction("OP_EQUAL", offset);
case OP_GREATER:
return simpleInstruction("OP_GREATER", offset);
case OP_GREATER_EQUAL:
return simpleInstruction("OP_GREATER_EQUAL", offset);
case OP_LESS:
return simpleInstruction("OP_LESS", offset);
case OP_LESS_EQUAL:
return simpleInstruction("OP_LESS_EQUAL", offset);
case OP_NEGATE:
return simpleInstruction("OP_NEGATE", offset);
case OP_CONCAT:
return shortOperandInstruction("OP_CONCAT", chunk, offset);
case OP_RETURN:
return shortOperandInstruction("OP_RETURN", chunk, offset);
default:
printf("Unknown opcode! [%d]\n", i);
exit(0);
}
return 1;
}

9
src/cdebug.h Normal file
View File

@@ -0,0 +1,9 @@
#ifndef CDEBUG_H
#define CDEBUG_H
#include "cchunk.h"
COSMO_API void disasmChunk(CChunk *chunk, const char *name, int indent);
COSMO_API int disasmInstr(CChunk *chunk, int offset, int indent);
#endif

239
src/clex.c Normal file
View File

@@ -0,0 +1,239 @@
#include "clex.h"
#include "cmem.h"
#include <string.h>
CReservedWord reservedWords[] = {
{TOKEN_AND, "and", 3},
{TOKEN_DO, "do", 2},
{TOKEN_ELSE, "else", 4},
{TOKEN_ELSEIF, "elseif", 6},
{TOKEN_END, "end", 3},
{TOKEN_FALSE, "false", 5},
{TOKEN_FOR, "for", 3},
{TOKEN_FUNCTION, "function", 8},
{TOKEN_IF, "if", 2},
{TOKEN_LOCAL, "local", 5},
{TOKEN_NIL, "nil", 3},
{TOKEN_NOT, "not", 3},
{TOKEN_OR, "or", 2},
{TOKEN_RETURN, "return", 6},
{TOKEN_THEN, "then", 4},
{TOKEN_TRUE, "true", 4},
{TOKEN_VAR, "var", 3},
{TOKEN_WHILE, "while", 5}
};
static CToken makeToken(CLexState *state, CTokenType type) {
CToken token;
token.type = type;
token.start = state->startChar;
token.length = state->currentChar - state->startChar; // delta between start & current
token.line = state->line;
state->lastType = type;
return token;
}
static CToken makeError(CLexState *state, const char *msg) {
CToken token;
token.type = TOKEN_ERROR;
token.start = (char*)msg;
token.length = strlen(msg);
token.line = state->line;
return token;
}
static inline bool isEnd(CLexState *state) {
return state->isEnd;
}
static inline bool isNumerical(char c) {
return c >= '0' && c <= '9';
}
static bool isAlpha(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; // identifiers can have '_'
}
static bool match(CLexState *state, char expected) {
if (isEnd(state) || *state->currentChar != expected)
return false;
// it matched, so increment the currentChar and return true
state->currentChar++;
return true;
}
char peek(CLexState *state) {
return *state->currentChar;
}
static char peekNext(CLexState *state) {
if (isEnd(state))
return '\0';
return state->currentChar[1];
}
char next(CLexState *state) {
state->currentChar++;
return state->currentChar[-1];
}
CTokenType identifierType(CLexState *state) {
int length = state->currentChar - state->startChar;
// check against reserved word list
for (int i = 0; i < sizeof(reservedWords) / sizeof(CReservedWord); i++) {
// it matches the reserved word
if (reservedWords[i].len == length && memcmp(state->startChar, reservedWords[i].word, length) == 0)
return reservedWords[i].type;
}
// else, it's an identifier
return TOKEN_IDENTIFIER;
}
void skipWhitespace(CLexState *state) {
while (true) {
char c = peek(state);
switch (c) {
case ' ':
case '\r':
case '\t':
next(state); // consume the whitespace
break;
case '\n': // mark new line, make the main loop consume it
state->line++;
return;
case '-': // consume comments
if (peekNext(state) == '-') {
// skip to next line (also let \n be consumed on the next iteration to properly handle that)
while (!isEnd(state) && peek(state) != '\n' && peek(state) != '\0') // if it's not a newline or null terminator
next(state);
break;
}
return; // it's a TOKEN_SLASH, let the main body handle that
default: // it's no longer whitespace, return!
return;
}
}
}
CToken parseString(CLexState *state) {
while (peek(state) != '"' && !isEnd(state)) {
if (peek(state) == '\n') // strings can't stretch across lines
return makeError(state, "Unterminated string!");
next(state); // consume
}
if (isEnd(state))
return makeError(state, "Unterminated string!");
next(state); // consume closing quote
return makeToken(state, TOKEN_STRING);
}
CToken parseNumber(CLexState *state) {
// consume number
while (isNumerical(peek(state)))
next(state);
if (peek(state) == '.' && isNumerical(peekNext(state))) {
next(state); // consume '.'
// consume number
while (isNumerical(peek(state)))
next(state);
}
return makeToken(state, TOKEN_NUMBER);
}
CToken parseIdentifier(CLexState *state) {
// read literal
while ((isAlpha(peek(state)) || isNumerical(peek(state))) && !isEnd(state))
next(state);
return makeToken(state, identifierType(state)); // is it a reserved word?
}
CLexState *cosmoL_newLexState(CState *cstate, const char *source) {
CLexState *state = cosmoM_xmalloc(cstate, sizeof(CLexState));
state->startChar = (char*)source;
state->currentChar = (char*)source;
state->line = 1;
state->lastLine = 0;
state->openedBraces = 0;
state->isEnd = false;
state->lastType = TOKEN_ERROR;
return state;
}
void cosmoL_freeLexState(CState *state, CLexState *lstate) {
cosmoM_free(state, CLexState, lstate);
}
CToken cosmoL_scanToken(CLexState *state) {
_scanTokenEnter:
skipWhitespace(state);
state->startChar = state->currentChar;
if (isEnd(state))
return makeToken(state, TOKEN_EOF);
char c = next(state);
switch (c) {
// single character tokens
case '(': state->openedBraces++; return makeToken(state, TOKEN_LEFT_PAREN);
case ')': state->openedBraces--; return makeToken(state, TOKEN_RIGHT_PAREN);
case '{': state->openedBraces++; return makeToken(state, TOKEN_LEFT_BRACE);
case '}': state->openedBraces--; return makeToken(state, TOKEN_RIGHT_BRACE);
case '\0':
state->isEnd = true;
if (state->lastType == TOKEN_EOS)
return makeToken(state, TOKEN_EOF);
// fall through
case ';': return makeToken(state, TOKEN_EOS);
case ',': return makeToken(state, TOKEN_COMMA);
case '+': return makeToken(state, TOKEN_PLUS);
case '-': return makeToken(state, TOKEN_MINUS);
case '*': return makeToken(state, TOKEN_STAR);
case '/': return makeToken(state, TOKEN_SLASH);
case '\n': { // might be treated like a TOKEN_EOS
if (state->openedBraces == 0 && state->lastType != TOKEN_EOS)
return makeToken(state, TOKEN_EOS);
else // go back to the start
goto _scanTokenEnter;
}
// two character tokens
case '.':
return match(state, '.') ? makeToken(state, TOKEN_DOT_DOT) : makeToken(state, TOKEN_DOT);
case '!':
return match(state, '=') ? makeToken(state, TOKEN_BANG_EQUAL) : makeToken(state, TOKEN_BANG);
case '=':
return match(state, '=') ? makeToken(state, TOKEN_EQUAL_EQUAL) : makeToken(state, TOKEN_EQUAL);
case '>':
return match(state, '=') ? makeToken(state, TOKEN_GREATER_EQUAL) : makeToken(state, TOKEN_GREATER);
case '<':
return match(state, '=') ? makeToken(state, TOKEN_LESS_EQUAL) : makeToken(state, TOKEN_LESS);
// literals
case '"': return parseString(state);
default:
if (isNumerical(c))
return parseNumber(state);
if (isAlpha(c))
return parseIdentifier(state);
}
return makeError(state, "Unknown symbol!");
}

88
src/clex.h Normal file
View File

@@ -0,0 +1,88 @@
#ifndef CLEX_H
#define CLEX_H
#include "cosmo.h"
typedef enum {
// single character tokens
TOKEN_LEFT_PAREN,
TOKEN_RIGHT_PAREN,
TOKEN_LEFT_BRACE,
TOKEN_RIGHT_BRACE,
TOKEN_COMMA,
TOKEN_DOT,
TOKEN_DOT_DOT,
TOKEN_MINUS,
TOKEN_PLUS,
TOKEN_SLASH,
TOKEN_STAR,
TOKEN_EOS, // end of statement
// equality operators
TOKEN_BANG,
TOKEN_BANG_EQUAL,
TOKEN_EQUAL,
TOKEN_EQUAL_EQUAL,
TOKEN_GREATER,
TOKEN_GREATER_EQUAL,
TOKEN_LESS,
TOKEN_LESS_EQUAL,
// literals
TOKEN_IDENTIFIER,
TOKEN_STRING,
TOKEN_NUMBER,
TOKEN_NIL,
TOKEN_TRUE,
TOKEN_FALSE,
// keywords & reserved words
TOKEN_AND,
TOKEN_DO,
TOKEN_ELSE,
TOKEN_ELSEIF,
TOKEN_END,
TOKEN_FOR,
TOKEN_FUNCTION,
TOKEN_IF,
TOKEN_LOCAL,
TOKEN_NOT,
TOKEN_OR,
TOKEN_RETURN,
TOKEN_THEN,
TOKEN_VAR,
TOKEN_WHILE,
TOKEN_ERROR,
TOKEN_EOF
} CTokenType;
typedef struct {
CTokenType type;
const char *word;
int len;
} CReservedWord;
typedef struct {
CTokenType type;
char *start;
int length;
int line;
} CToken;
typedef struct {
char *currentChar;
char *startChar;
int line; // current line
int lastLine; // line of the previous consumed token
int openedBraces; // tracks open [], {}, or ()
bool isEnd;
CTokenType lastType;
} CLexState;
CLexState *cosmoL_newLexState(CState *state, const char *source);
void cosmoL_freeLexState(CState *state, CLexState *lstate);
CToken cosmoL_scanToken(CLexState *state);
#endif

217
src/cmem.c Normal file
View File

@@ -0,0 +1,217 @@
#include "cmem.h"
#include "cstate.h"
#include "cvalue.h"
#include "ctable.h"
#include "cparse.h"
#include "cobj.h"
/*
copy buffer to new larger buffer, and free the old buffer
*/
void *cosmoM_reallocate(CState* state, void *buf, size_t oldSize, size_t newSize) {
state->allocatedBytes += newSize - oldSize;
if (newSize == 0) { // it needs to be free'd
free(buf);
return NULL;
}
#ifdef GC_STRESS
if (!(cosmoM_isFrozen(state)) && newSize > oldSize) {
cosmoM_collectGarbage(state);
}
#else
// if the state isn't frozen && we've reached the GC event
if (!(cosmoM_isFrozen(state)) && state->allocatedBytes > state->nextGC) {
cosmoM_collectGarbage(state); // cya lol
}
#endif
// otherwise just use realloc to do all the heavy lifting
void *newBuf = realloc(buf, newSize);
if (newBuf == NULL) {
CERROR("failed to allocate memory!");
exit(1);
}
return newBuf;
}
void markObject(CState *state, CObj *obj);
void markValue(CState *state, CValue val);
void markTable(CState *state, CTable *tbl) {
if (tbl->table == NULL) // table is still being initialized
return;
for (int i = 0; i < tbl->capacity; i++) {
CTableEntry *entry = &tbl->table[i];
markValue(state, entry->key);
markValue(state, entry->val);
}
}
// free's white members from the table
void tableRemoveWhite(CState *state, CTable *tbl) {
if (tbl->table == NULL) // table is still being initialized
return;
for (int i = 0; i < tbl->capacity; i++) {
CTableEntry *entry = &tbl->table[i];
if (IS_OBJ(entry->key) && !(entry->key.val.obj)->isMarked) { // if the key is a object and it's white (unmarked), remove it from the table
cosmoT_remove(tbl, entry->key);
}
}
}
void markArray(CState *state, CValueArray *array) {
for (int i = 0; i < array->count; i++) {
markValue(state, array->values[i]);
}
}
// mark all references associated with the object
void blackenObject(CState *state, CObj *obj) {
switch (obj->type) {
case COBJ_STRING:
case COBJ_CFUNCTION:
// stubbed
break;
case COBJ_UPVALUE: {
markValue(state, ((CObjUpval*)obj)->closed);
break;
}
case COBJ_FUNCTION: {
CObjFunction *func = (CObjFunction*)obj;
markObject(state, (CObj*)func->name);
markArray(state, &func->chunk.constants);
break;
}
case COBJ_CLOSURE: {
CObjClosure *closure = (CObjClosure*)obj;
markObject(state, (CObj*)closure->function);
// mark all upvalues
for (int i = 0; i < closure->upvalueCount; i++) {
markObject(state, (CObj*)closure->upvalues[i]);
}
break;
}
default:
printf("Unknown type in blackenObject with %p, type %d\n", obj, obj->type);
break;
}
}
void markObject(CState *state, CObj *obj) {
if (obj == NULL || obj->isMarked) // skip if NULL or already marked
return;
obj->isMarked = true;
#ifdef GC_DEBUG
printf("marking %p, [", obj);
printObject(obj);
printf("]\n");
#endif
// they don't need to be added to the gray stack, they don't reference any other CObjs
if (obj->type == COBJ_CFUNCTION || obj->type == COBJ_STRING)
return;
// we don't use cosmoM_growarray because we don't want to trigger another GC event while in the GC!
if (state->grayCount >= state->grayCapacity || state->grayStack == NULL) {
int old = state->grayCapacity;
state->grayCapacity = old * GROW_FACTOR;
state->grayStack = (CObj**)realloc(state->grayStack, sizeof(CObj*) * state->grayCapacity);
if (state->grayStack == NULL) {
CERROR("failed to allocate memory for grayStack!");
exit(1);
}
}
state->grayStack[state->grayCount++] = obj;
}
void markValue(CState *state, CValue val) {
if (IS_OBJ(val))
markObject(state, cosmoV_readObj(val));
}
// trace our gray references
void traceGrays(CState *state) {
while (state->grayCount > 0) {
CObj* obj = state->grayStack[--state->grayCount];
blackenObject(state, obj);
}
}
void sweep(CState *state) {
CObj *prev = NULL;
CObj *object = state->objects;
while (object != NULL) {
if (object->isMarked) { // skip over it
object->isMarked = false; // rest to white
prev = object;
object = object->next;
} else { // free it!
CObj *oldObj = object;
object = object->next;
if (prev == NULL) {
state->objects = object;
} else {
prev->next = object;
}
cosmoO_freeObject(state, oldObj);
}
}
}
void markRoots(CState *state) {
// mark all values on the stack
for (StkPtr value = state->stack; value < state->top; value++) {
markValue(state, *value);
}
// mark all active callframe closures
for (int i = 0; i < state->frameCount; i++) {
markObject(state, (CObj*)state->callFrame[i].closure);
}
// mark all open upvalues
for (CObjUpval *upvalue = state->openUpvalues; upvalue != NULL; upvalue = upvalue->next) {
markObject(state, (CObj*)upvalue);
}
markTable(state, &state->globals);
traceGrays(state);
}
COSMO_API void cosmoM_collectGarbage(CState *state) {
#ifdef GC_DEBUG
printf("-- GC start\n");
size_t start = state->allocatedBytes;
#endif
markRoots(state);
tableRemoveWhite(state, &state->strings); // make sure we aren't referencing any strings that are about to be free'd
// now finally, free all the unmarked objects
sweep(state);
// set our next GC event
state->nextGC = state->allocatedBytes * HEAP_GROW_FACTOR;
#ifdef GC_DEBUG
printf("-- GC end, reclaimed %ld bytes (started at %ld, ended at %ld), next garbage collection scheduled at %ld bytes\n",
start - state->allocatedBytes, start, state->allocatedBytes, state->nextGC);
#endif
}

47
src/cmem.h Normal file
View File

@@ -0,0 +1,47 @@
#ifndef CMEME_C
#define CMEME_C // meme lol
#include "cosmo.h"
#include "cstate.h"
//#define GC_STRESS
//#define GC_DEBUG
// arrays will grow by a factor of 2
#define GROW_FACTOR 2
#define HEAP_GROW_FACTOR 2
#define ARRAY_START 8
#define cosmoM_freearray(state, type, buf, capacity) \
cosmoM_reallocate(state, buf, sizeof(type) *capacity, 0)
#define cosmoM_growarray(state, type, buf, count, capacity) \
if (count >= capacity || buf == NULL) { \
int old = capacity; \
capacity = old *GROW_FACTOR; \
buf = (type*)cosmoM_reallocate(state, buf, sizeof(type) *old, sizeof(type) *capacity); \
}
#define cosmoM_free(state, type, x) \
cosmoM_reallocate(state, x, sizeof(type), 0)
#define cosmoM_isFrozen(state) \
state->freezeGC > 0
#define cosmoM_freezeGC(state) \
state->freezeGC++
#define cosmoM_unfreezeGC(state) \
state->freezeGC--
COSMO_API void *cosmoM_reallocate(CState* state, void *buf, size_t oldSize, size_t newSize);
COSMO_API void cosmoM_collectGarbage(CState* state);
/*
wrapper for cosmoM_reallocate so we can track our memory usage (it's also safer :P)
*/
static inline void *cosmoM_xmalloc(CState *state, size_t sz) {
return cosmoM_reallocate(state, NULL, 0, sz);
}
#endif

209
src/cobj.c Normal file
View File

@@ -0,0 +1,209 @@
#include "cstate.h"
#include "ctable.h"
#include "cobj.h"
#include "cmem.h"
#include <string.h>
// we don't actually hash the whole string :eyes:
uint32_t hashString(const char *str, size_t sz) {
uint32_t hash = sz;
size_t step = (sz>>5)+1;
for (int i = sz; i >= step; i-=step)
hash = ((hash << 5) + (hash>>2)) + str[i-1];
return hash;
}
CObj *cosmoO_allocateObject(CState *state, size_t sz, CObjType type) {
CObj* obj = (CObj*)cosmoM_xmalloc(state, sz);
obj->type = type;
obj->isMarked = false;
obj->next = state->objects;
state->objects = obj;
return obj;
}
void cosmoO_freeObject(CState *state, CObj* obj) {
#ifdef GC_DEBUG
printf("freeing %p [", obj);
printObject(obj);
printf("]\n");
#endif
switch(obj->type) {
case COBJ_STRING: {
CObjString *objStr = (CObjString*)obj;
cosmoM_freearray(state, char, objStr->str, objStr->length);
cosmoM_free(state, CObjString, objStr);
break;
}
case COBJ_UPVALUE: {
cosmoM_free(state, CObjUpval, obj);
break;
}
case COBJ_FUNCTION: {
CObjFunction *objFunc = (CObjFunction*)obj;
cleanChunk(state, &objFunc->chunk);
cosmoM_free(state, CObjFunction, objFunc);
break;
}
case COBJ_CFUNCTION: {
cosmoM_free(state, CObjCFunction, obj);
break;
}
case COBJ_CLOSURE: {
CObjClosure* closure = (CObjClosure*)obj;
cosmoM_freearray(state, CObjUpval*, closure->upvalues, closure->upvalueCount);
cosmoM_free(state, CObjClosure, closure);
break;
}
}
}
bool cosmoO_equalObject(CObj* obj1, CObj* obj2) {
if (obj1->type != obj2->type)
return false;
switch (obj1->type) {
case COBJ_STRING:
return obj1 == obj2; // compare pointers because we already intern all strings :)
default:
return false; // they're some unknown type, probably malformed :(
}
}
CObjFunction *cosmoO_newFunction(CState *state) {
CObjFunction *func = (CObjFunction*)cosmoO_allocateObject(state, sizeof(CObjFunction), COBJ_FUNCTION);
func->args = 0;
func->upvals = 0;
func->name = NULL;
initChunk(state, &func->chunk, ARRAY_START);
return func;
}
CObjCFunction *cosmoO_newCFunction(CState *state, CosmoCFunction func) {
CObjCFunction *cfunc = (CObjCFunction*)cosmoO_allocateObject(state, sizeof(CObjCFunction), COBJ_CFUNCTION);
cfunc->cfunc = func;
return cfunc;
}
CObjClosure *cosmoO_newClosure(CState *state, CObjFunction *func) {
// intialize array of pointers
CObjUpval **upvalues = cosmoM_xmalloc(state, sizeof(CObjUpval*) * func->upvals);
for (int i = 0; i < func->upvals; i++) {
upvalues[i] = NULL;
}
CObjClosure *closure = (CObjClosure*)cosmoO_allocateObject(state, sizeof(CObjClosure), COBJ_CLOSURE);
closure->function = func;
closure->upvalues = upvalues;
closure->upvalueCount = func->upvals;
return closure;
}
CObjUpval *cosmoO_newUpvalue(CState *state, CValue *val) {
CObjUpval *upval = (CObjUpval*)cosmoO_allocateObject(state, sizeof(CObjUpval), COBJ_UPVALUE);
upval->val = val;
upval->closed = cosmoV_newNil();
upval->next = NULL;
return upval;
}
CObjString *cosmoO_copyString(CState *state, const char *str, size_t sz) {
uint32_t hash = hashString(str, sz);
CObjString *lookup = cosmoT_lookupString(&state->strings, str, sz, hash);
// have we already interned this string?
if (lookup != NULL)
return lookup;
char *buf = cosmoM_xmalloc(state, sizeof(char) * (sz + 1)); // +1 for null terminator
memcpy(buf, str, sz); // copy string to heap
buf[sz] = '\0'; // don't forget our null terminator
return cosmoO_allocateString(state, buf, sz, hash);
}
CObjString *cosmoO_takeString(CState *state, char *str, size_t sz) {
uint32_t hash = hashString(str, sz);
CObjString *lookup = cosmoT_lookupString(&state->strings, str, sz, hash);
// have we already interned this string?
if (lookup != NULL) {
cosmoM_freearray(state, char, str, sz); // free our passed character array, it's unneeded!
return lookup;
}
return cosmoO_allocateString(state, str, sz, hash);
}
CObjString *cosmoO_allocateString(CState *state, const char *str, size_t sz, uint32_t hash) {
CObjString *strObj = (CObjString*)cosmoO_allocateObject(state, sizeof(CObjString), COBJ_STRING);
strObj->str = (char*)str;
strObj->length = sz;
strObj->hash = hash;
// we push & pop the string so our GC can find it (we don't use freezeGC/unfreezeGC because we *want* a GC event to happen)
cosmoV_pushValue(state, cosmoV_newObj(strObj));
cosmoT_insert(state, &state->strings, cosmoV_newObj((CObj*)strObj));
cosmoV_pop(state);
return strObj;
}
CObjString *cosmoO_toString(CState *state, CObj *val) {
switch (val->type) {
case COBJ_STRING: {
return (CObjString*)val;
}
case COBJ_FUNCTION: {
CObjFunction *func = (CObjFunction*)val;
return func->name != NULL ? func->name : cosmoO_copyString(state, UNNAMEDCHUNK, strlen(UNNAMEDCHUNK));
}
default:
return cosmoO_copyString(state, "<unkn>", 6);
}
}
void printObject(CObj *o) {
switch (o->type) {
case COBJ_STRING: {
CObjString *objStr = (CObjString*)o;
printf("\"%.*s\"", objStr->length, objStr->str);
break;
}
case COBJ_UPVALUE: {
CObjUpval *upval = (CObjUpval*)o;
printf("<upvalue %p> -> ", upval->val);
printValue(*upval->val);
break;
}
case COBJ_FUNCTION: {
CObjFunction *objFunc = (CObjFunction*)o;
if (objFunc->name != NULL)
printf("<function> %.*s", objFunc->name->length, objFunc->name->str);
else
printf("<function> _main");
break;
}
case COBJ_CFUNCTION: {
CObjCFunction *objCFunc = (CObjCFunction*)o;
printf("<c function> %p", objCFunc->cfunc);
break;
}
case COBJ_CLOSURE: {
CObjClosure *closure = (CObjClosure*)o;
printObject((CObj*)closure->function); // just print the function
break;
}
default:
printf("<unkn>");
}
}

98
src/cobj.h Normal file
View File

@@ -0,0 +1,98 @@
#ifndef COBJ_H
#define COBJ_H
#include "cosmo.h"
#include "cchunk.h"
#include "cvalue.h"
typedef struct CState CState;
typedef enum {
COBJ_STRING,
COBJ_UPVALUE,
COBJ_FUNCTION,
COBJ_CFUNCTION,
COBJ_CLOSURE
} CObjType;
#define CommonHeader CObj obj;
typedef int (*CosmoCFunction)(CState *state, int argCount, CValue *args);
typedef struct CObj {
CObjType type;
bool isMarked; // for the GC
struct CObj *next;
} CObj;
typedef struct CObjString {
CommonHeader; // "is a" CObj
int length;
char *str;
uint32_t hash; // for hashtable lookup
} CObjString;
typedef struct CObjUpval {
CommonHeader; // "is a" CObj
CValue *val;
CValue closed;
struct CObjUpval *next;
} CObjUpval;
typedef struct CObjFunction {
CommonHeader; // "is a" CObj
CChunk chunk;
int args;
int upvals;
CObjString *name;
} CObjFunction;
typedef struct CObjCFunction {
CommonHeader; // "is a" CObj
CosmoCFunction cfunc;
} CObjCFunction;
typedef struct CObjClosure {
CommonHeader;
CObjFunction *function;
CObjUpval **upvalues;
int upvalueCount;
} CObjClosure;
#define IS_STRING(x) isObjType(x, COBJ_STRING)
#define IS_FUNCTION(x) isObjType(x, COBJ_FUNCTION)
#define IS_CFUNCTION(x) isObjType(x, COBJ_CFUNCTION)
#define IS_CLOSURE(x) isObjType(x, COBJ_CLOSURE)
#define cosmoV_readString(x) ((CObjString*)cosmoV_readObj(x))
#define cosmoV_readFunction(x) ((CObjFunction*)cosmoV_readObj(x))
#define cosmoV_readCFunction(x) (((CObjCFunction*)cosmoV_readObj(x))->cfunc)
#define cosmoV_readClosure(x) ((CObjClosure*)cosmoV_readObj(x))
static inline bool isObjType(CValue val, CObjType type) {
return IS_OBJ(val) && cosmoV_readObj(val)->type == type;
}
CObj *cosmoO_allocateObject(CState *state, size_t sz, CObjType type);
void cosmoO_freeObject(CState *state, CObj* obj);
bool cosmoO_equalObject(CObj* obj1, CObj* obj2);
CObjFunction *cosmoO_newFunction(CState *state);
CObjCFunction *cosmoO_newCFunction(CState *state, CosmoCFunction func);
CObjClosure *cosmoO_newClosure(CState *state, CObjFunction *func);
CObjString *cosmoO_toString(CState *state, CObj *val);
CObjUpval *cosmoO_newUpvalue(CState *state, CValue *val);
// copies the *str buffer to the heap and returns a CObjString struct which is also on the heap
CObjString *cosmoO_copyString(CState *state, const char *str, size_t sz);
// pass an already allocated str buffer!
CObjString *cosmoO_takeString(CState *state, char *str, size_t sz);
// allocates a CObjStruct pointing directly to *str
CObjString *cosmoO_allocateString(CState *state, const char *str, size_t sz, uint32_t hash);
COSMO_API void printObject(CObj *o);
#define cosmoO_readCString(x) ((CObjString*)x)->str
#endif

1
src/coperators.c Normal file
View File

@@ -0,0 +1 @@
#include "coperators.h"

59
src/coperators.h Normal file
View File

@@ -0,0 +1,59 @@
#ifndef COPERATORS_H
#define COPERATORS_H
#include "cosmo.h"
// instruction types
typedef enum {
I_O, // just the operand (uint8_t)
I_OBYTE, // operand (uint8_t) + uint8_t
I_OSHORT, // operand (uint8_t) + uint16_t
} InstructionType;
// instructions
typedef enum {
// STACK MANIPULATION
OP_LOADCONST,
OP_SETGLOBAL,
OP_GETGLOBAL,
OP_SETLOCAL,
OP_GETLOCAL,
OP_GETUPVAL,
OP_SETUPVAL,
OP_PEJMP, // pops, if false jumps uint16_t
OP_EJMP, // if peek(0) is falsey jumps uint16_t
OP_JMP, // always jumps uint16_t
OP_JMPBACK, // jumps -uint16_t
OP_POP, // - pops[uint8_t] from stack
OP_CALL, // calls top[-uint8_t]
OP_CLOSURE,
OP_CLOSE,
// ARITHMETIC
OP_ADD,
OP_SUB,
OP_MULT,
OP_DIV,
OP_NOT,
OP_NEGATE,
OP_CONCAT, // concats uint8_t vars on the stack
// EQUALITY
OP_EQUAL,
OP_LESS,
OP_GREATER,
OP_LESS_EQUAL,
OP_GREATER_EQUAL,
// LITERALS
OP_TRUE,
OP_FALSE,
OP_NIL,
OP_RETURN,
OP_NONE // used as an error result
} COPCODE;
#endif

35
src/cosmo.h Normal file
View File

@@ -0,0 +1,35 @@
#ifndef COSMOMAIN_H
#define COSMOMAIN_H
#include <stdlib.h>
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
// forward declare *most* stuff so our headers are cleaner
typedef struct CState CState;
typedef struct CChunk CChunk;
typedef struct CValue CValue;
// objs
typedef struct CObj CObj;
typedef struct CObjString CObjString;
typedef struct CObjUpval CObjUpval;
typedef struct CObjFunction CObjFunction;
typedef struct CObjCFunction CObjCFunction;
typedef struct CObjClosure CObjClosure;
typedef uint8_t INSTRUCTION;
#define COSMOMAX_UPVALS 80
#define FRAME_MAX 64
#define STACK_MAX (256 * FRAME_MAX)
#define COSMO_API extern
#define UNNAMEDCHUNK "_main"
#define CERROR(err) \
printf("%s : %s\n", "[ERROR]", err)
#endif

1017
src/cparse.c Normal file

File diff suppressed because it is too large Load Diff

39
src/cparse.h Normal file
View File

@@ -0,0 +1,39 @@
#ifndef CPARSE_H
#define CPARSE_H
#include "cosmo.h"
#include "clex.h"
typedef struct {
CToken name;
int depth;
bool isCaptured; // is the Local referenced in an upvalue?
} Local;
typedef struct {
uint8_t index;
bool isLocal;
} Upvalue;
typedef enum {
FTYPE_FUNCTION,
FTYPE_SCRIPT
} FunctionType;
typedef struct CCompilerState {
CObjFunction *function;
FunctionType type;
Local locals[256];
Upvalue upvalues[256];
int localCount;
int scopeDepth;
int pushedValues;
int savedPushed;
struct CCompilerState* enclosing;
} CCompilerState;
// compiles source into CChunk, if NULL is returned, a syntaxical error has occured and pushed onto the stack
CObjFunction* cosmoP_compileString(CState *state, const char *source);
#endif

74
src/cstate.c Normal file
View File

@@ -0,0 +1,74 @@
#include "cstate.h"
#include "cchunk.h"
#include "cobj.h"
#include "cvm.h"
#include <string.h>
CState *cosmoV_newState() {
// we use C's malloc because we don't want to trigger a GC with an invalid state
CState *state = malloc(sizeof(CState));
if (state == NULL) {
CERROR("failed to allocate memory!");
exit(1);
}
state->panic = false;
state->freezeGC = false;
// GC
state->objects = NULL;
state->grayCount = 0;
state->grayCapacity = 2;
state->grayStack = NULL;
state->allocatedBytes = 0;
state->nextGC = 1024 * 8; // threshhold starts at 8kb
// init stack
state->top = state->stack;
state->frameCount = 0;
state->openUpvalues = NULL;
cosmoT_initTable(state, &state->strings, 8); // init string table
cosmoT_initTable(state, &state->globals, 8); // init global table
return state;
}
void cosmoV_freeState(CState *state) {
// frees all the objects
CObj *objs = state->objects;
while (objs != NULL) {
CObj *next = objs->next;
cosmoO_freeObject(state, objs);
objs = next;
}
// free our string & global table
cosmoT_clearTable(state, &state->strings);
cosmoT_clearTable(state, &state->globals);
// free our gray stack & finally free the state structure
free(state->grayStack);
free(state);
}
void cosmoV_register(CState *state, const char *identifier, CValue val) {
// we push the values so the garbage collector can find them
cosmoV_pushValue(state, cosmoV_newObj(cosmoO_copyString(state, identifier, strlen(identifier))));
cosmoV_pushValue(state, val);
CValue *oldVal = cosmoT_insert(state, &state->globals, *cosmoV_getTop(state, 1));
*oldVal = val;
cosmoV_setTop(state, 2); // pops the 2 values off the stack
}
void cosmoV_printStack(CState *state) {
printf("==== [[ stack dump ]] ====\n");
for (CValue *top = state->top - 1; top >= state->stack; top--) {
printf("%d: ", (int)(top - state->stack));
printValue(*top);
printf("\n");
}
}

63
src/cstate.h Normal file
View File

@@ -0,0 +1,63 @@
#ifndef CSTATE_H
#define CSTATE_H
#include "cosmo.h"
#include "cvalue.h"
#include "cobj.h"
#include "ctable.h"
typedef struct CCompilerState CCompilerState;
typedef struct CCallFrame {
CObjClosure *closure;
INSTRUCTION *pc;
CValue* base;
} CCallFrame;
typedef struct CState {
bool panic;
int freezeGC; // when > 0, GC events will be ignored (for internal use)
CObj *objects; // tracks all of our allocated objects
CObj **grayStack; // keeps track of which objects *haven't yet* been traversed in our GC, but *have been* found
int grayCount;
int grayCapacity;
size_t allocatedBytes;
size_t nextGC; // when allocatedBytes reaches this threshhold, trigger a GC event
CObjUpval *openUpvalues; // tracks all of our still open (meaning still on the stack) upvalues
CTable strings;
CTable globals;
CValue *top; // top of the stack
CValue stack[STACK_MAX]; // stack
CCallFrame callFrame[FRAME_MAX]; // call frames
int frameCount;
} CState;
COSMO_API CState *cosmoV_newState();
COSMO_API void cosmoV_register(CState *state, const char *identifier, CValue val);
COSMO_API void cosmoV_freeState(CState *state);
COSMO_API void cosmoV_printStack(CState *state);
// pushes value to the stack
static inline void cosmoV_pushValue(CState *state, CValue val) {
*(state->top++) = val;
}
// sets stack->top to stack->top - indx
static inline StkPtr cosmoV_setTop(CState *state, int indx) {
state->top -= indx;
return state->top;
}
// returns stack->top - indx - 1
static inline StkPtr cosmoV_getTop(CState *state, int indx) {
return &state->top[-(indx + 1)];
}
// pops 1 value off the stack
static inline StkPtr cosmoV_pop(CState *state) {
return cosmoV_setTop(state, 1);
}
#endif

193
src/ctable.c Normal file
View File

@@ -0,0 +1,193 @@
#include "ctable.h"
#include "cmem.h"
#include "cvalue.h"
#include "cobj.h"
#include <string.h>
#define MAX_TABLE_FILL 0.75
void cosmoT_initTable(CState *state, CTable *tbl, int startCap) {
tbl->capacity = startCap;
tbl->count = 0;
tbl->table = NULL; // to let out GC know we're initalizing
tbl->table = cosmoM_xmalloc(state, sizeof(CTableEntry) * startCap);
// init everything to NIL
for (int i = 0; i < startCap; i++) {
tbl->table[i].key = cosmoV_newNil();
tbl->table[i].val = cosmoV_newNil();
}
}
void cosmoT_addTable(CState *state, CTable *from, CTable *to) {
for (int i = 0; i < from->capacity; i++) {
CTableEntry *entry = &from->table[i];
if (!(IS_NIL(entry->key))) {
CValue *newVal = cosmoT_insert(state, to, entry->key);
*newVal = entry->val;
}
}
}
void cosmoT_clearTable(CState *state, CTable *tbl) {
cosmoM_freearray(state, CTableEntry, tbl->table, tbl->capacity);
}
uint32_t getObjectHash(CObj *obj) {
switch(obj->type) {
case COBJ_STRING:
return ((CObjString*)obj)->hash;
default:
return 0;
}
}
uint32_t getValueHash(CValue *val) {
switch (val->type) {
case COSMO_TOBJ:
return getObjectHash(val->val.obj);
case COSMO_TNUMBER:
// how the fuck
// TODO: add support for other types
default:
return 0;
}
}
// mask should always be (capacity - 1)
static CTableEntry *findEntry(CTableEntry *entries, int mask, CValue key) {
uint32_t hash = getValueHash(&key);
uint32_t indx = hash & mask; // since we know the capacity will *always* be a power of 2, we can use bitwise & to perform a MUCH faster mod operation
CTableEntry *tomb = NULL;
// keep looking for an open slot in the entries array
while (true) {
CTableEntry *entry = &entries[indx];
if (IS_NIL(entry->key)) {
// check if it's an empty bucket or a tombstone
if (IS_NIL(entry->val)) {
// it's empty! if we found a tombstone, return that so it'll be reused
return tomb != NULL ? tomb : entry;
} else {
// its a tombstone!
tomb = entry;
}
} else if (cosmoV_equal(entry->key, key)) {
return entry;
}
indx = (indx + 1) & mask; // fast mod here too
}
}
static void growTbl(CState *state, CTable *tbl, size_t newCapacity) {
CTableEntry *entries = cosmoM_xmalloc(state, sizeof(CTableEntry) * newCapacity);
int newCount;
// set all nodes as NIL : NIL
for (int i = 0; i < newCapacity; i++) {
entries[i].key = cosmoV_newNil();
entries[i].val = cosmoV_newNil();
}
// move over old values to the new buffer
for (int i = 0; i < tbl->capacity; i++) {
CTableEntry *oldEntry = &tbl->table[i];
if (IS_NIL(oldEntry->key))
continue; // skip empty keys
// get new entry location & update the node
CTableEntry *newEntry = findEntry(entries, newCapacity - 1, oldEntry->key);
newEntry->key = oldEntry->key;
newEntry->val = oldEntry->val;
newCount++; // inc count
}
// free the old table
cosmoM_freearray(state, CTableEntry, tbl->table, tbl->capacity);
tbl->table = entries;
tbl->capacity = newCapacity;
tbl->count = newCount;
}
// returns a pointer to the allocated value
COSMO_API CValue* cosmoT_insert(CState *state, CTable *tbl, CValue key) {
// make sure we have enough space allocated
if (tbl->count + 1 > tbl->capacity * MAX_TABLE_FILL) {
int newCap = tbl->capacity * GROW_FACTOR;
growTbl(state, tbl, newCap);
}
// insert into the table
CTableEntry *entry = findEntry(tbl->table, tbl->capacity - 1, key); // -1 for our capacity mask
if (IS_NIL(entry->key) && IS_NIL(entry->val)) // is it empty?
tbl->count++;
entry->key = key;
return &entry->val;
}
bool cosmoT_get(CTable *tbl, CValue key, CValue *val) {
if (tbl->count == 0) {
*val = cosmoV_newNil();
return false; // sanity check
}
CTableEntry *entry = findEntry(tbl->table, tbl->capacity - 1, key);
*val = entry->val;
return !(IS_NIL(entry->key));
}
bool cosmoT_remove(CTable *tbl, CValue key) {
if (tbl->count == 0) return 0; // sanity check
CTableEntry *entry = findEntry(tbl->table, tbl->capacity - 1, key);
if (IS_NIL(entry->key)) // sanity check
return false;
// crafts tombstone
entry->key = cosmoV_newNil(); // this has to be nil
entry->val = cosmoV_newBoolean(false); // doesn't reall matter what this is, as long as it isn't nil
return true;
}
CObjString *cosmoT_lookupString(CTable *tbl, const char *str, size_t length, uint32_t hash) {
if (tbl->count == 0) return 0; // sanity check
uint32_t indx = hash & (tbl->capacity - 1); // since we know the capacity will *always* be a power of 2, we can use bitwise & to perform a MUCH faster mod operation
// keep looking for an open slot in the entries array
while (true) {
CTableEntry *entry = &tbl->table[indx];
// check if it's an empty slot (meaning we dont have it in the table)
if (IS_NIL(entry->key) && IS_NIL(entry->val)) {
return NULL;
} else if (IS_STRING(entry->key) && cosmoV_readString(entry->key)->length == length && memcmp(cosmoV_readString(entry->key)->str, str, length) == 0) {
// it's a match!
return (CObjString*)entry->key.val.obj;
}
indx = (indx + 1) & (tbl->capacity - 1); // fast mod here too
}
}
// for debugging purposes
void cosmoT_printTable(CTable *tbl, const char *name) {
printf("==== [[%s]] ====\n", name);
for (int i = 0; i < tbl->capacity; i++) {
CTableEntry *entry = &tbl->table[i];
if (!(IS_NIL(entry->key))) {
printValue(entry->key);
printf(" - ");
printValue(entry->val);
printf("\n");
}
}
}

29
src/ctable.h Normal file
View File

@@ -0,0 +1,29 @@
#ifndef CTABLE_H
#define CTABLE_H
#include "cosmo.h"
#include "cvalue.h"
typedef struct CTableEntry {
CValue key;
CValue val;
} CTableEntry;
typedef struct CTable {
int count;
int capacity;
CTableEntry *table;
} CTable;
COSMO_API void cosmoT_initTable(CState *state, CTable *tbl, int startCap);
COSMO_API void cosmoT_clearTable(CState *state, CTable *tbl);
COSMO_API void cosmoT_addTable(CState *state, CTable *from, CTable *to);
COSMO_API CValue *cosmoT_insert(CState *state, CTable *tbl, CValue key);
CObjString *cosmoT_lookupString(CTable *tbl, const char *str, size_t length, uint32_t hash);
bool cosmoT_get(CTable *tbl, CValue key, CValue *val);
bool cosmoT_remove(CTable *tbl, CValue key);
void cosmoT_printTable(CTable *tbl, const char *name);
#endif

72
src/cvalue.c Normal file
View File

@@ -0,0 +1,72 @@
#include "cmem.h"
#include "cvalue.h"
#include "cobj.h"
void initValArray(CState *state, CValueArray *val, size_t startCapacity) {
val->count = 0;
val->capacity = startCapacity;
val->values = NULL;
}
void cleanValArray(CState *state, CValueArray *array) {
cosmoM_freearray(state, CValue, array->values, array->capacity);
}
void appendValArray(CState *state, CValueArray *array, CValue val) {
cosmoM_growarray(state, CValue, array->values, array->count, array->capacity);
array->values[array->count++] = val;
}
bool cosmoV_equal(CValue valA, CValue valB) {
if (valA.type != valB.type) // are they the same type?
return false;
// compare
switch (valA.type) {
case COSMO_TBOOLEAN: return valA.val.b == valB.val.b;
case COSMO_TNUMBER: return valA.val.num == valB.val.num;
case COSMO_TOBJ: return cosmoO_equalObject(valA.val.obj, valB.val.obj);
case COSMO_TNIL: return true;
default:
return false;
}
}
COSMO_API CObjString *cosmoV_toString(CState *state, CValue val) {
switch (val.type) {
case COSMO_TNUMBER: {
char buf[32];
int size = snprintf((char*)&buf, 32, "%.14g", val.val.num);
return cosmoO_copyString(state, (char*)&buf, size);
}
case COSMO_TBOOLEAN: {
return val.val.b ? cosmoO_copyString(state, "true", 4) : cosmoO_copyString(state, "false", 5);
}
case COSMO_TOBJ: {
return cosmoO_toString(state, val.val.obj);
}
default:
return cosmoO_copyString(state, "<unkn>", 6);
}
}
void printValue(CValue val) {
switch (val.type) {
case COSMO_TNUMBER:
printf("%g", val.val.num);
break;
case COSMO_TBOOLEAN:
printf(cosmoV_readBoolean(val) ? "true" : "false");
break;
case COSMO_TOBJ: {
printObject(val.val.obj);
break;
}
case COSMO_TNIL:
printf("nil");
break;
default:
printf("<unkn>");
}
}

62
src/cvalue.h Normal file
View File

@@ -0,0 +1,62 @@
#ifndef CVALUE_H
#define CVALUE_H
#include "cosmo.h"
typedef enum {
COSMO_TNIL,
COSMO_TBOOLEAN,
COSMO_TNUMBER,
COSMO_TOBJ,
COSMO_TUSERDATA
} CosmoType;
typedef double cosmo_Number;
/*
holds primitive cosmo types
*/
typedef struct CValue {
CosmoType type;
union {
cosmo_Number num;
bool b; // boolean
void *ptr; // userdata
CObj *obj;
} val;
} CValue;
typedef CValue* StkPtr;
typedef struct CValueArray {
size_t capacity;
size_t count;
CValue *values;
} CValueArray;
COSMO_API void initValArray(CState *state, CValueArray *val, size_t startCapacity);
COSMO_API void cleanValArray(CState *state, CValueArray *array); // cleans array
COSMO_API void appendValArray(CState *state, CValueArray *array, CValue val);
COSMO_API void printValue(CValue val);
COSMO_API bool cosmoV_equal(CValue valA, CValue valB);
COSMO_API CObjString *cosmoV_toString(CState *state, CValue val);
#define IS_NUMBER(x) x.type == COSMO_TNUMBER
#define IS_BOOLEAN(x) x.type == COSMO_TBOOLEAN
#define IS_NIL(x) x.type == COSMO_TNIL
#define IS_OBJ(x) x.type == COSMO_TOBJ
// create CValues
#define cosmoV_newNumber(x) ((CValue){COSMO_TNUMBER, {.num = x}})
#define cosmoV_newBoolean(x) ((CValue){COSMO_TBOOLEAN, {.b = x}})
#define cosmoV_newObj(x) ((CValue){COSMO_TOBJ, {.obj = (CObj*)x}})
#define cosmoV_newNil() ((CValue){COSMO_TNIL, {.num = 0}})
// read CValues
#define cosmoV_readNumber(x) ((cosmo_Number)x.val.num)
#define cosmoV_readBoolean(x) ((bool)x.val.b)
#define cosmoV_readObj(x) ((CObj*)x.val.obj)
#endif

417
src/cvm.c Normal file
View File

@@ -0,0 +1,417 @@
#include "cvm.h"
#include "cstate.h"
#include "cdebug.h"
#include "cmem.h"
#include <stdarg.h>
#include <string.h>
void runtimeError(CState *state, const char *format, ...) {
if (state->panic)
return;
// print stack trace
for (int i = 0; i < state->frameCount; i++) {
CCallFrame *frame = &state->callFrame[i];
CObjFunction *function = frame->closure->function;
CChunk *chunk = &function->chunk;
int line = chunk->lineInfo[frame->pc - chunk->buf - 1];
if (i == state->frameCount - 1) { // it's the last call frame, prepare for the objection to be printed
fprintf(stderr, "Objection on [line %d] in ", line);
if (function->name == NULL) { // unnamed chunk
fprintf(stderr, "%s\n\t", UNNAMEDCHUNK);
} else {
fprintf(stderr, "%.*s()\n\t", function->name->length, function->name->str);
}
} else {
fprintf(stderr, "[line %d] in ", line);
if (function->name == NULL) { // unnamed chunk
fprintf(stderr, "%s\n", UNNAMEDCHUNK);
} else {
fprintf(stderr, "%.*s()\n", function->name->length, function->name->str);
}
}
}
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
fputs("\n", stderr);
// TODO: push error onto the stack :P
state->panic = true;
}
CObjUpval *captureUpvalue(CState *state, CValue *local) {
CObjUpval *prev = NULL;
CObjUpval *upvalue = state->openUpvalues;
while (upvalue != NULL && upvalue->val > local) { // while upvalue exists and is higher on the stack than local
prev = upvalue;
upvalue = upvalue->next;
}
if (upvalue != NULL && upvalue->val == local) { // we found the local we were going to capture
return upvalue;
}
CObjUpval *newUpval = cosmoO_newUpvalue(state, local);
newUpval->next = upvalue;
// the list is sorted, so insert it at our found upvalue
if (prev == NULL) {
state->openUpvalues = newUpval;
} else {
prev->next = newUpval;
}
return newUpval;
}
void closeUpvalues(CState *state, CValue *local) {
while (state->openUpvalues != NULL && state->openUpvalues->val >= local) { // for every upvalue that points to the local or anything above it
CObjUpval *upvalue = state->openUpvalues;
upvalue->closed = *upvalue->val;
upvalue->val = &upvalue->closed; // upvalue now points to itself :P
state->openUpvalues = upvalue->next;
}
}
void pushCallFrame(CState *state, CObjClosure *closure, int args) {
CCallFrame *frame = &state->callFrame[state->frameCount++];
frame->base = state->top - args - 1; // - 1 for the function
frame->pc = closure->function->chunk.buf;
frame->closure = closure;
}
void popCallFrame(CState *state) {
closeUpvalues(state, state->callFrame[state->frameCount - 1].base); // close any upvalue still open
state->top = state->callFrame[state->frameCount - 1].base; // resets the stack
state->frameCount--;
}
CObjString *cosmoV_concat(CState *state, CObjString *strA, CObjString *strB) {
size_t sz = strA->length + strB->length;
char *buf = cosmoM_xmalloc(state, sz + 1); // +1 for null terminator
memcpy(buf, strA->str, strA->length);
memcpy(buf + strA->length, strB->str, strB->length);
buf[sz] = '\0';
return cosmoO_takeString(state, buf, sz);
}
int cosmoV_execute(CState *state);
typedef enum {
CALL_CLOSURE,
CALL_CFUNCTION
} preCallResult;
int cosmoV_preCall(CState *state, int args, int nresults) {
return -1;
}
// args = # of pass parameters, nresults = # of expected results
COSMOVMRESULT cosmoV_call(CState *state, int args, int nresults) {
StkPtr val = cosmoV_getTop(state, args); // function will always be right above the args
if (!(val->type == COSMO_TOBJ)) {
runtimeError(state, "Cannot call non-function value!");
return COSMOVM_RUNTIME_ERR;
}
switch (val->val.obj->type) {
case COBJ_CLOSURE: {
CObjClosure *closure = (CObjClosure*)(val->val.obj);
// missmatched args, thats an obvious user error, so error.
if (args != closure->function->args) {
runtimeError(state, "Expected %d parameters for %s, got %d!", closure->function->args, closure->function->name == NULL ? UNNAMEDCHUNK : closure->function->name->str, args);
return COSMOVM_RUNTIME_ERR;
}
// load function into callframe
pushCallFrame(state, closure, closure->function->args);
// execute
int res = cosmoV_execute(state);
// so, since we can have any # of results, we need to move the expected results to the original call frame (that means popping/adding however many results)
CValue* results = state->top;
// pop the callframe and return result :)
popCallFrame(state);
// return the results to the stack
for (int i = 1; i <= nresults; i++) {
if (i <= res)
cosmoV_pushValue(state, results[-i]);
else
cosmoV_pushValue(state, cosmoV_newNil());
}
break;
}
case COBJ_CFUNCTION: {
// it's a C function, so call it
CosmoCFunction cfunc = ((CObjCFunction*)(val->val.obj))->cfunc;
CValue *savedBase = state->top - args - 1;
cosmoM_freezeGC(state); // we don't want a GC event during c api because we don't actually trust the user to know how to evade the GC
int res = cfunc(state, args, state->top - args);
cosmoM_unfreezeGC(state);
// so, since we can have any # of results, we need to move the expected results to the original call frame
CValue* results = state->top;
state->top = savedBase;
// return the results to the stack
for (int i = 1; i <= nresults; i++) {
if (i <= res)
cosmoV_pushValue(state, results[-i]);
else
cosmoV_pushValue(state, cosmoV_newNil());
}
break;
}
default:
runtimeError(state, "Cannot call non-function value!");
return COSMOVM_RUNTIME_ERR;
}
return state->panic ? COSMOVM_RUNTIME_ERR : COSMOVM_OK;
}
static inline bool isFalsey(StkPtr val) {
return val->type == COSMO_TNIL || (val->type == COSMO_TBOOLEAN && !val->val.b);
}
#define BINARYOP(typeConst, op) \
StkPtr valA = cosmoV_getTop(state, 1); \
StkPtr valB = cosmoV_getTop(state, 0); \
if (valA->type == COSMO_TNUMBER && valB->type == COSMO_TNUMBER) { \
cosmoV_setTop(state, 2); /* pop the 2 values */ \
cosmoV_pushValue(state, typeConst((valA->val.num) op (valB->val.num))); \
} else { \
runtimeError(state, "Expected number! got %d and %d", valA->type, valB->type); \
} \
// returns -1 if error, otherwise returns ammount of results
int cosmoV_execute(CState *state) {
CCallFrame* frame = &state->callFrame[state->frameCount - 1]; // grabs the current frame
CValue *constants = frame->closure->function->chunk.constants.values; // cache the pointer :)
#define READBYTE() *frame->pc++
#define READUINT() (frame->pc += 2, *(uint16_t*)(&frame->pc[-2]))
while (!state->panic) {
/*disasmInstr(&frame->closure->function->chunk, frame->pc - frame->closure->function->chunk.buf, 0);
printf("\n");*/
switch (READBYTE()) {
case OP_LOADCONST: { // push const[uint] to stack
uint16_t indx = READUINT();
cosmoV_pushValue(state, constants[indx]);
break;
}
case OP_SETGLOBAL: {
uint16_t indx = READUINT();
CValue ident = constants[indx]; // grabs identifier
CValue *val = cosmoT_insert(state, &state->globals, ident);
*val = *cosmoV_pop(state); // sets the value in the hash table
break;
}
case OP_GETGLOBAL: {
uint16_t indx = READUINT();
CValue ident = constants[indx]; // grabs identifier
CValue val; // to hold our value
cosmoT_get(&state->globals, ident, &val);
cosmoV_pushValue(state, val); // pushes the value to the stack
break;
}
case OP_SETLOCAL: {
frame->base[READBYTE()] = *cosmoV_pop(state);
break;
}
case OP_GETLOCAL: {
cosmoV_pushValue(state, frame->base[READBYTE()]);
break;
}
case OP_GETUPVAL: {
uint8_t indx = READBYTE();
cosmoV_pushValue(state, *frame->closure->upvalues[indx]->val);
break;
}
case OP_SETUPVAL: {
uint8_t indx = READBYTE();
*frame->closure->upvalues[indx]->val = *cosmoV_pop(state);
break;
}
case OP_PEJMP: {
uint16_t offset = READUINT();
if (isFalsey(cosmoV_pop(state))) { // pop, if the condition is false, jump!
frame->pc += offset;
}
break;
}
case OP_EJMP: {
uint16_t offset = READUINT();
if (isFalsey(cosmoV_getTop(state, 0))) { // if the condition is false, jump!
frame->pc += offset;
}
break;
}
case OP_JMP: {
uint16_t offset = READUINT();
frame->pc += offset;
break;
}
case OP_JMPBACK: {
uint16_t offset = READUINT();
frame->pc -= offset;
break;
}
case OP_POP: { // pops value off the stack
cosmoV_setTop(state, READBYTE());
break;
}
case OP_CALL: {
uint8_t args = READBYTE();
uint8_t results = READBYTE();
COSMOVMRESULT result = cosmoV_call(state, args, results);
if (result != COSMOVM_OK) {
return result;
}
break;
}
case OP_CLOSURE: {
uint16_t index = READUINT();
CObjFunction *func = cosmoV_readFunction(constants[index]);
CObjClosure *closure = cosmoO_newClosure(state, func);
cosmoV_pushValue(state, cosmoV_newObj((CObj*)closure));
for (int i = 0; i < closure->upvalueCount; i++) {
uint8_t encoding = READBYTE();
uint8_t index = READBYTE();
if (encoding == OP_GETUPVAL) {
// capture upvalue from current frame's closure
closure->upvalues[i] = frame->closure->upvalues[index];
} else {
// capture local
closure->upvalues[i] = captureUpvalue(state, frame->base + index);
}
}
break;
}
case OP_CLOSE: {
closeUpvalues(state, state->top - 1);
cosmoV_pop(state);
break;
}
case OP_ADD: { // pop 2 values off the stack & try to add them together
BINARYOP(cosmoV_newNumber, +);
break;
}
case OP_SUB: { // pop 2 values off the stack & try to subtracts them
BINARYOP(cosmoV_newNumber, -)
break;
}
case OP_MULT: { // pop 2 values off the stack & try to multiplies them together
BINARYOP(cosmoV_newNumber, *)
break;
}
case OP_DIV: { // pop 2 values off the stack & try to divides them
BINARYOP(cosmoV_newNumber, /)
break;
}
case OP_NOT: {
cosmoV_pushValue(state, cosmoV_newBoolean(isFalsey(cosmoV_pop(state))));
break;
}
case OP_NEGATE: { // pop 1 value off the stack & try to negate
StkPtr val = cosmoV_getTop(state, 0);
if (val->type == COSMO_TNUMBER) {
cosmoV_pop(state);
cosmoV_pushValue(state, cosmoV_newNumber(-(val->val.num)));
} else {
runtimeError(state, "Expected number!");
}
break;
}
case OP_CONCAT: {
uint8_t vals = READBYTE();
StkPtr start = state->top - vals;
StkPtr end = cosmoV_getTop(state, 0);
StkPtr current;
CObjString *result = cosmoV_toString(state, *start);
for (StkPtr current = start + 1; current <= end; current++) {
cosmoV_pushValue(state, cosmoV_newObj(result)); // so our GC can find our current result string
CObjString *otherStr = cosmoV_toString(state, *current);
cosmoV_pushValue(state, cosmoV_newObj(otherStr)); // also so our GC won't free otherStr
result = cosmoV_concat(state, result, otherStr);
cosmoV_setTop(state, 2); // pop result & otherStr off the stack
}
state->top = start;
cosmoV_pushValue(state, cosmoV_newObj(result));
break;
}
case OP_EQUAL: {
// pop vals
StkPtr valB = cosmoV_pop(state);
StkPtr valA = cosmoV_pop(state);
// compare & push
cosmoV_pushValue(state, cosmoV_newBoolean(cosmoV_equal(*valA, *valB)));
break;
}
case OP_GREATER: {
BINARYOP(cosmoV_newBoolean, >)
break;
}
case OP_LESS: {
BINARYOP(cosmoV_newBoolean, <)
break;
}
case OP_GREATER_EQUAL: {
BINARYOP(cosmoV_newBoolean, >=)
break;
}
case OP_LESS_EQUAL: {
BINARYOP(cosmoV_newBoolean, <=)
break;
}
case OP_TRUE: cosmoV_pushValue(state, cosmoV_newBoolean(true)); break;
case OP_FALSE: cosmoV_pushValue(state, cosmoV_newBoolean(false)); break;
case OP_NIL: cosmoV_pushValue(state, cosmoV_newNil()); break;
case OP_RETURN: {
uint8_t results = READBYTE();
return results;
}
default:
CERROR("unknown opcode!");
exit(0);
}
//cosmoV_printStack(state);
}
#undef READBYTE
#undef READUINT
// we'll only reach this is state->panic is true
return COSMOVM_RUNTIME_ERR;
}
#undef BINARYOP

16
src/cvm.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef COSMOVM_H
#define COSMOVM_H
#include "cosmo.h"
#include "cstate.h"
typedef enum {
COSMOVM_OK,
COSMOVM_RUNTIME_ERR,
COSMOVM_BUILDTIME_ERR
} COSMOVMRESULT;
// args = # of pass parameters, nresults = # of expected results
COSMO_API COSMOVMRESULT cosmoV_call(CState *state, int args, int nresults);
#endif

131
src/main.c Normal file
View File

@@ -0,0 +1,131 @@
#include "cosmo.h"
#include "cchunk.h"
#include "cdebug.h"
#include "cvm.h"
#include "cparse.h"
#include "cbaselib.h"
#include "cmem.h"
static void interpret(const char* script) {
CState *state = cosmoV_newState();
// cosmoP_compileString pushes the result onto the stack (NIL or COBJ_FUNCTION)
CObjFunction* func = cosmoP_compileString(state, script);
cosmoB_loadlibrary(state);
if (func != NULL) {
disasmChunk(&func->chunk, "_main", 0);
cosmoV_call(state, 0, 0); // 0 args being passed, 0 results expected
//cosmoV_printStack(state);
//cosmoT_printTable(&state->globals, "globals");
//cosmoT_printTable(&state->strings, "strings");
}
cosmoV_freeState(state);
}
static void repl() {
char line[1024];
while (true) {
printf("> ");
if (!fgets(line, sizeof(line), stdin)) { // better than gets()
printf("\n> ");
break;
}
interpret(line);
}
}
static char *readFile(const char* path) {
FILE* file = fopen(path, "rb");
if (file == NULL) {
fprintf(stderr, "Could not open file \"%s\".\n", path);
exit(74);
}
// first, we need to know how big our file is
fseek(file, 0L, SEEK_END);
size_t fileSize = ftell(file);
rewind(file);
char *buffer = (char*)malloc(fileSize + 1); // make room for the null byte
if (buffer == NULL) {
fprintf(stderr, "failed to allocate!");
exit(1);
}
size_t bytesRead = fread(buffer, sizeof(char), fileSize, file);
if (bytesRead < fileSize) {
printf("failed to read file \"%s\"!\n", path);
exit(74);
}
buffer[bytesRead] = '\0'; // place our null terminator
// close the file handler and return the script buffer
fclose(file);
return buffer;
}
static void runFile(const char* fileName) {
char* script = readFile(fileName);
interpret(script);
free(script);
}
int main(int argc, const char *argv[]) {
//interpret("\"hello world!\"");
if (argc == 1) {
repl();
} else if (argc >= 2) { // they passed a file (or more lol)
for (int i = 1; i < argc; i++) {
runFile(argv[i]);
}
}
/*
CChunk *chnk = newChunk(1);
CState *state = cosmoV_newState();
// adds our constant values
int constIndx = addConstant(chnk, cosmoV_newNumber(2));
int const2Indx = addConstant(chnk, cosmoV_newNumber(4));
// pushes constant to the stack
writeu8Chunk(chnk, OP_LOADCONST, 1);
writeu16Chunk(chnk, constIndx, 1);
writeu8Chunk(chnk, OP_LOADCONST, 1);
writeu16Chunk(chnk, const2Indx, 1);
// pops 2 values off the stack, multiples them together and pushes the result
writeu8Chunk(chnk, OP_MULT, 1);
// pops a value off the stack, negates it, and pushes the result
writeu8Chunk(chnk, OP_NEGATE, 2);
// prints to the console
writeu8Chunk(chnk, OP_RETURN, 2);
disasmChunk(chnk, "test");
// load chunk to the state & run it
cosmoV_loadChunk(state, chnk);
cosmoV_execute(state, 0);
// clean up :)
freeChunk(chnk);
cosmoV_freeState(state);*/
return 0;
}