Major refactoring, added classes, many bug fixes

This commit is contained in:
CPunch 2020-11-09 19:44:12 -06:00
parent aa975b7330
commit c42a72dfad
21 changed files with 353 additions and 194 deletions

View File

@ -7,6 +7,7 @@
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"vector": "cpp"
"vector": "cpp",
"typeinfo": "c"
}
}

View File

@ -9,13 +9,12 @@ void cosmoB_loadlibrary(CState *state) {
cosmoM_unfreezeGC(state);
}
int cosmoB_print(CState *state, int nargs, CValue *args) {
CValue 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
return cosmoV_newNil(); // print doesn't return any args
}

View File

@ -4,6 +4,6 @@
#include "cstate.h"
COSMO_API void cosmoB_loadlibrary(CState *state);
COSMO_API int cosmoB_print(CState *state, int nargs, CValue *args);
COSMO_API CValue cosmoB_print(CState *state, int nargs, CValue *args);
#endif

View File

@ -32,14 +32,6 @@ int constInstruction(const char *name, CChunk *chunk, int offset, int indent) {
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) {
@ -91,7 +83,7 @@ int disasmInstr(CChunk *chunk, int offset, int indent) {
case OP_POP:
return shortOperandInstruction("OP_POP", chunk, offset);
case OP_CALL:
return ABOperandInstruction("OP_CALL", chunk, offset);
return shortOperandInstruction("OP_CALL", chunk, offset);
case OP_CLOSURE: {
int index = readu16Chunk(chunk, offset + 1);
printf("%-16s [%05d] - ", "OP_CLOSURE", index);
@ -153,7 +145,7 @@ int disasmInstr(CChunk *chunk, int offset, int indent) {
case OP_CONCAT:
return shortOperandInstruction("OP_CONCAT", chunk, offset);
case OP_RETURN:
return shortOperandInstruction("OP_RETURN", chunk, offset);
return simpleInstruction("OP_RETURN", offset);
default:
printf("Unknown opcode! [%d]\n", i);
exit(0);

View File

@ -12,6 +12,7 @@ CReservedWord reservedWords[] = {
{TOKEN_FALSE, "false", 5},
{TOKEN_FOR, "for", 3},
{TOKEN_FUNCTION, "function", 8},
{TOKEN_CLASS, "class", 5},
{TOKEN_IF, "if", 2},
{TOKEN_LOCAL, "local", 5},
{TOKEN_NIL, "nil", 3},
@ -21,11 +22,9 @@ CReservedWord reservedWords[] = {
{TOKEN_THEN, "then", 4},
{TOKEN_TRUE, "true", 4},
{TOKEN_VAR, "var", 3},
{TOKEN_THIS, "this", 4},
{TOKEN_WHILE, "while", 5}
};
static CToken makeToken(CLexState *state, CTokenType type) {
CToken token;
token.type = type;
@ -49,7 +48,7 @@ static CToken makeError(CLexState *state, const char *msg) {
}
static inline bool isEnd(CLexState *state) {
return state->isEnd;
return *state->currentChar == '\0';
}
static inline bool isNumerical(char c) {
@ -103,14 +102,13 @@ void skipWhitespace(CLexState *state) {
while (true) {
char c = peek(state);
switch (c) {
case '\n': // mark new line
state->line++;
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) == '-') {
@ -171,8 +169,6 @@ CLexState *cosmoL_newLexState(CState *cstate, const char *source) {
state->currentChar = (char*)source;
state->line = 1;
state->lastLine = 0;
state->openedBraces = 0;
state->isEnd = false;
state->lastType = TOKEN_ERROR;
return state;
@ -195,16 +191,12 @@ _scanTokenEnter:
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 '[': state->openedBraces++; return makeToken(state, TOKEN_LEFT_BRACKET);
case ']': state->openedBraces--; return makeToken(state, TOKEN_RIGHT_BRACKET);
case '\0':
state->isEnd = true;
if (state->lastType == TOKEN_EOS)
return makeToken(state, TOKEN_EOF);
case '(': return makeToken(state, TOKEN_LEFT_PAREN);
case ')': return makeToken(state, TOKEN_RIGHT_PAREN);
case '{': return makeToken(state, TOKEN_LEFT_BRACE);
case '}': return makeToken(state, TOKEN_RIGHT_BRACE);
case '[': return makeToken(state, TOKEN_LEFT_BRACKET);
case ']': return makeToken(state, TOKEN_RIGHT_BRACKET);
// fall through
case ';': return makeToken(state, TOKEN_EOS);
case ',': return makeToken(state, TOKEN_COMMA);
@ -212,12 +204,6 @@ _scanTokenEnter:
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);

View File

@ -46,6 +46,7 @@ typedef enum {
TOKEN_END,
TOKEN_FOR,
TOKEN_FUNCTION,
TOKEN_CLASS,
TOKEN_IF,
TOKEN_LOCAL,
TOKEN_NOT,
@ -53,7 +54,6 @@ typedef enum {
TOKEN_RETURN,
TOKEN_THEN,
TOKEN_VAR,
TOKEN_THIS,
TOKEN_WHILE,
TOKEN_ERROR,
@ -78,7 +78,6 @@ typedef struct {
char *startChar;
int line; // current line
int lastLine; // line of the previous consumed token
int openedBraces; // tracks open [], {}, or ()
bool isEnd;
CTokenType lastType;
} CLexState;

View File

@ -85,7 +85,9 @@ void blackenObject(CState *state, CObj *obj) {
break;
case COBJ_OBJECT: {
// mark everything this object is keeping track of
markTable(state, &((CObjObject*)obj)->tbl);
CObjObject *cobj = (CObjObject*)obj;
markTable(state, &cobj->tbl);
markObject(state, (CObj*)cobj->meta);
break;
}
case COBJ_UPVALUE: {
@ -99,6 +101,12 @@ void blackenObject(CState *state, CObj *obj) {
break;
}
case COBJ_METHOD: {
CObjMethod *method = (CObjMethod*)obj;
markObject(state, (CObj*)method->closure);
markObject(state, (CObj*)method->obj);
break;
}
case COBJ_CLOSURE: {
CObjClosure *closure = (CObjClosure*)obj;
markObject(state, (CObj*)closure->function);
@ -200,6 +208,7 @@ void markRoots(CState *state) {
}
markTable(state, &state->globals);
markObject(state, (CObj*)state->initString);
traceGrays(state);
}

View File

@ -5,7 +5,7 @@
#include "cstate.h"
//#define GC_STRESS
#define GC_STRESS
//#define GC_DEBUG
// arrays will grow by a factor of 2
#define GROW_FACTOR 2

View File

@ -62,6 +62,10 @@ void cosmoO_free(CState *state, CObj* obj) {
cosmoM_free(state, CObjCFunction, obj);
break;
}
case COBJ_METHOD: {
cosmoM_free(state, CObjMethod, obj); // we don't own the closure or the object so /shrug
break;
}
case COBJ_CLOSURE: {
CObjClosure* closure = (CObjClosure*)obj;
cosmoM_freearray(state, CObjUpval*, closure->upvalues, closure->upvalueCount);
@ -88,11 +92,14 @@ bool cosmoO_equal(CObj* obj1, CObj* obj2) {
}
}
CObjObject *cosmoO_newObject(CState *state, int startCap) {
CObjObject *tbl = (CObjObject*)cosmoO_allocateBase(state, sizeof(CObjObject), COBJ_OBJECT);
CObjObject *cosmoO_newObject(CState *state) {
CObjObject *obj = (CObjObject*)cosmoO_allocateBase(state, sizeof(CObjObject), COBJ_OBJECT);
obj->meta = NULL;
cosmoV_pushValue(state, cosmoV_newObj(obj)); // so out GC can keep track of it
cosmoT_initTable(state, &obj->tbl, ARRAY_START);
cosmoV_pop(state);
cosmoT_initTable(state, &tbl->tbl, startCap);
return tbl;
return obj;
}
CObjFunction *cosmoO_newFunction(CState *state) {
@ -111,6 +118,13 @@ CObjCFunction *cosmoO_newCFunction(CState *state, CosmoCFunction func) {
return cfunc;
}
CObjMethod *cosmoO_newMethod(CState *state, CObjClosure *func, CObjObject *obj) {
CObjMethod *method = (CObjMethod*)cosmoO_allocateBase(state, sizeof(CObjMethod), COBJ_METHOD);
method->closure = func;
method->obj = obj;
return method;
}
CObjClosure *cosmoO_newClosure(CState *state, CObjFunction *func) {
// intialize array of pointers
CObjUpval **upvalues = cosmoM_xmalloc(state, sizeof(CObjUpval*) * func->upvals);
@ -180,7 +194,11 @@ CObjString *cosmoO_allocateString(CState *state, const char *str, size_t sz, uin
}
bool cosmoO_getObject(CState *state, CObjObject *object, CValue key, CValue *val) {
return cosmoT_get(&object->tbl, key, val);
if (!cosmoT_get(&object->tbl, key, val) && object->meta != NULL) { // if the field doesn't exist in the object, check the meta
return cosmoO_getObject(state, object->meta, key, val);
}
return true;
}
void cosmoO_setObject(CState *state, CObjObject *object, CValue key, CValue val) {
@ -188,22 +206,22 @@ void cosmoO_setObject(CState *state, CObjObject *object, CValue key, CValue val)
*newVal = val;
}
CObjString *cosmoO_toString(CState *state, CObj *val) {
switch (val->type) {
CObjString *cosmoO_toString(CState *state, CObj *obj) {
switch (obj->type) {
case COBJ_STRING: {
return (CObjString*)val;
return (CObjString*)obj;
}
case COBJ_FUNCTION: {
CObjFunction *func = (CObjFunction*)val;
CObjFunction *func = (CObjFunction*)obj;
return func->name != NULL ? func->name : cosmoO_copyString(state, UNNAMEDCHUNK, strlen(UNNAMEDCHUNK));
}
case COBJ_OBJECT: { // TODO: maybe not safe??
char buf[64];
int sz = sprintf(buf, "<obj> %p", val) + 1; // +1 for the null character
int sz = sprintf(buf, "<obj> %p", obj) + 1; // +1 for the null character
return cosmoO_copyString(state, buf, sz);
}
default:
return cosmoO_copyString(state, "<unkn>", 6);
return cosmoO_copyString(state, "<unkn obj>", 10);
}
}
@ -229,7 +247,7 @@ void printObject(CObj *o) {
if (objFunc->name != NULL)
printf("<function> %.*s", objFunc->name->length, objFunc->name->str);
else
printf("<function> _main");
printf("<function> %s", UNNAMEDCHUNK);
break;
}
case COBJ_CFUNCTION: {
@ -237,12 +255,21 @@ void printObject(CObj *o) {
printf("<c function> %p", objCFunc->cfunc);
break;
}
case COBJ_METHOD: {
CObjMethod *method = (CObjMethod*)o;
if (method->closure->function->name != NULL) {
printf("<method> %p : %.*s", method->obj, method->closure->function->name->length, method->closure->function->name->str);
} else {
printf("<method> %p : %s", method->obj, UNNAMEDCHUNK);
}
break;
}
case COBJ_CLOSURE: {
CObjClosure *closure = (CObjClosure*)o;
printObject((CObj*)closure->function); // just print the function
break;
}
default:
printf("<unkn>");
printf("<unkn obj>");
}
}

View File

@ -13,14 +13,15 @@ typedef enum {
COBJ_OBJECT,
COBJ_FUNCTION,
COBJ_CFUNCTION,
COBJ_METHOD,
// internal use
COBJ_CLOSURE,
COBJ_UPVALUE,
} CObjType;
#define CommonHeader CObj obj;
#define CommonHeader CObj _obj;
typedef int (*CosmoCFunction)(CState *state, int argCount, CValue *args);
typedef CValue (*CosmoCFunction)(CState *state, int argCount, CValue *args);
typedef struct CObj {
CObjType type;
@ -38,7 +39,7 @@ typedef struct CObjString {
typedef struct CObjObject {
CommonHeader; // "is a" CObj
CTable tbl;
//struct CObjObject *meta; // metaobject, used to describe object behavior
struct CObjObject *meta; // metaobject, describes the behavior of the object
} CObjObject;
typedef struct CObjFunction {
@ -61,6 +62,12 @@ typedef struct CObjClosure {
int upvalueCount;
} CObjClosure;
typedef struct CObjMethod {
CommonHeader; // "is a " CObj
CObjObject *obj; // obj this method is bound too
CObjClosure *closure; // TODO: change this to a union to a pointer to a closure object or a c function object
} CObjMethod;
typedef struct CObjUpval {
CommonHeader; // "is a" CObj
CValue *val;
@ -72,12 +79,14 @@ typedef struct CObjUpval {
#define IS_TABLE(x) isObjType(x, COBJ_OBJECT)
#define IS_FUNCTION(x) isObjType(x, COBJ_FUNCTION)
#define IS_CFUNCTION(x) isObjType(x, COBJ_CFUNCTION)
#define IS_METHOD(x) isObjType(x, COBJ_METHOD)
#define IS_CLOSURE(x) isObjType(x, COBJ_CLOSURE)
#define cosmoV_readString(x) ((CObjString*)cosmoV_readObj(x))
#define cosmoV_readObject(x) ((CObjObject*)cosmoV_readObj(x))
#define cosmoV_readObject(x) ((CObjObject*)cosmoV_readObj(x))
#define cosmoV_readFunction(x) ((CObjFunction*)cosmoV_readObj(x))
#define cosmoV_readCFunction(x) (((CObjCFunction*)cosmoV_readObj(x))->cfunc)
#define cosmoV_readMethod(x) ((CObjMethod*)cosmoV_readObj(x))
#define cosmoV_readClosure(x) ((CObjClosure*)cosmoV_readObj(x))
static inline bool isObjType(CValue val, CObjType type) {
@ -89,9 +98,10 @@ void cosmoO_free(CState *state, CObj* obj);
bool cosmoO_equal(CObj* obj1, CObj* obj2);
CObjObject *cosmoO_newObject(CState *state, int startCap);
CObjObject *cosmoO_newObject(CState *state);
CObjFunction *cosmoO_newFunction(CState *state);
CObjCFunction *cosmoO_newCFunction(CState *state, CosmoCFunction func);
CObjMethod *cosmoO_newMethod(CState *state, CObjClosure *func, CObjObject *obj);
CObjClosure *cosmoO_newClosure(CState *state, CObjFunction *func);
CObjString *cosmoO_toString(CState *state, CObj *val);
CObjUpval *cosmoO_newUpvalue(CState *state, CValue *val);

View File

@ -32,6 +32,7 @@ typedef enum {
OP_NEWOBJECT,
OP_GETOBJECT,
OP_SETOBJECT,
OP_NEWCLASS,
// ARITHMETIC
OP_ADD,

View File

@ -18,6 +18,8 @@ typedef struct CObjString CObjString;
typedef struct CObjUpval CObjUpval;
typedef struct CObjFunction CObjFunction;
typedef struct CObjCFunction CObjCFunction;
typedef struct CObjMethod CObjMethod;
typedef struct CObjClass CObjClass;
typedef struct CObjClosure CObjClosure;
typedef uint8_t INSTRUCTION;

View File

@ -9,6 +9,36 @@
// we define all of this here because we only need it in this file, no need for it to be in the header /shrug
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_METHOD, // a function bounded to an object (can use "this" identifer to access the current object :pog:)
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;
typedef struct {
CLexState *lex;
CCompilerState* compiler;
@ -50,7 +80,7 @@ static void declaration(CParseState *pstate);
static void function(CParseState *pstate, FunctionType type);
static void expressionStatement(CParseState *pstate);
static ParseRule* getRule(CTokenType type);
static CObjFunction *endCompiler(CParseState *pstate, int results);
static CObjFunction *endCompiler(CParseState *pstate);
// ================================================================ [FRONT END/TALK TO LEXER] ================================================================
@ -66,16 +96,15 @@ static void initCompilerState(CParseState* pstate, CCompilerState *ccstate, Func
ccstate->type = type;
ccstate->function = cosmoO_newFunction(pstate->state);
if (type != FTYPE_SCRIPT)
ccstate->function->name = cosmoO_copyString(pstate->state, pstate->previous.start, pstate->previous.length);
// mark first local slot as used (this'll hold the CObjFunction of the current function)
// mark first local slot as used (this'll hold the CObjFunction of the current function, or if it's a method it'll hold the currently bounded object)
Local *local = &ccstate->locals[ccstate->localCount++];
local->depth = 0;
local->isCaptured = false;
local->name.length = 0;
local->name.start = "";
local->name.length = 0;
}
static void initParseState(CParseState *pstate, CCompilerState *ccstate, CState *s, const char *source) {
@ -170,6 +199,16 @@ static void inline valuePopped(CParseState *pstate, int values) {
pstate->compiler->pushedValues -= values;
}
static bool blockFollow(CToken token) {
switch (token.type) {
case TOKEN_END: case TOKEN_ELSE:
case TOKEN_ELSEIF: case TOKEN_EOS:
return true;
default:
return false;
}
}
// ================================================================ [WRITE TO CHUNK] ================================================================
CChunk* getChunk(CParseState *pstate) {
@ -491,22 +530,46 @@ static void call_(CParseState *pstate, bool canAssign) {
valuePopped(pstate, argCount + 1); // all of these values will be popped off the stack when returned (+1 for the function)
writeu8(pstate, OP_CALL);
writeu8(pstate, argCount);
// hacky hacky hacky hACKY GACJ HACK!!!!!!!!
if (pstate->compiler->pushedValues < pstate->compiler->savedPushed) { // there's empty spots on the stack waiting to be filled, lets make OP_CALL fill those spots for us
writeu8(pstate, pstate->compiler->savedPushed - pstate->compiler->pushedValues); // number of expected results
pstate->compiler->pushedValues = pstate->compiler->savedPushed; // either way the stack will be balanaced after this call
} else {
writeu8(pstate, 1); // we expect 1 result by default
valuePushed(pstate, 1);
}
valuePushed(pstate, 1);
}
static void object(CParseState *pstate, bool canAssign) {
// already consumed the beginning '{'
int entries = 0;
consume(pstate, TOKEN_RIGHT_BRACE, "Expected '}' to end object definition!");
if (!match(pstate, TOKEN_RIGHT_BRACE)) {
do {
if (match(pstate, TOKEN_IDENTIFIER)) {
uint16_t fieldIdent = identifierConstant(pstate, &pstate->previous);
// OP_NEWOBJECT expects the key on the stack before the value
writeu8(pstate, OP_LOADCONST);
writeu16(pstate, fieldIdent);
consume(pstate, TOKEN_EQUAL, "Invalid syntax!");
// parse field
expression(pstate);
valuePopped(pstate, 1);
} else if (match(pstate, TOKEN_LEFT_BRACKET)) {
// parse the key first
expression(pstate); // should parse until end bracket
consume(pstate, TOKEN_RIGHT_BRACKET, "Expected ']' to end index definition.");
consume(pstate, TOKEN_EQUAL, "Expected '='.");
// now, parse the value (until comma)
expression(pstate);
valuePopped(pstate, 2);
} else {
error(pstate, "Invalid syntax!");
}
entries++;
} while (match(pstate, TOKEN_COMMA) && !pstate->hadError);
consume(pstate, TOKEN_RIGHT_BRACE, "Expected '}' to end object definition.");
}
writeu8(pstate, OP_NEWOBJECT);
writeu8(pstate, entries);
@ -515,7 +578,7 @@ static void object(CParseState *pstate, bool canAssign) {
static void dot(CParseState *pstate, bool canAssign) {
consume(pstate, TOKEN_IDENTIFIER, "Expected property name after '.'.");
uint8_t name = identifierConstant(pstate, &pstate->previous);
uint16_t name = identifierConstant(pstate, &pstate->previous);
writeu8(pstate, OP_LOADCONST);
writeu16(pstate, name);
@ -579,6 +642,7 @@ ParseRule ruleTable[] = {
[TOKEN_END] = {NULL, NULL, PREC_NONE},
[TOKEN_FOR] = {NULL, NULL, PREC_NONE},
[TOKEN_FUNCTION] = {anonFunction, NULL, PREC_NONE},
[TOKEN_CLASS] = {NULL, NULL, PREC_NONE},
[TOKEN_IF] = {NULL, NULL, PREC_NONE},
[TOKEN_LOCAL] = {NULL, NULL, PREC_NONE},
[TOKEN_NOT] = {NULL, NULL, PREC_NONE},
@ -588,7 +652,6 @@ ParseRule ruleTable[] = {
[TOKEN_WHILE] = {NULL, NULL, PREC_NONE},
[TOKEN_ERROR] = {NULL, NULL, PREC_NONE},
[TOKEN_VAR] = {NULL, NULL, PREC_NONE},
[TOKEN_THIS] = {NULL, NULL, PREC_NONE},
[TOKEN_EOF] = {NULL, NULL, PREC_NONE}
};
@ -601,9 +664,8 @@ static void parsePrecedence(CParseState *pstate, Precedence prec) {
ParseFunc prefix = getRule(pstate->previous.type)->prefix;
if (prefix == NULL) {
return error(pstate, "Expected expression!");
}
if (prefix == NULL)
return;
bool canAssign = prec <= PREC_ASSIGNMENT;
prefix(pstate, canAssign);
@ -666,6 +728,33 @@ static void defineVariable(CParseState *pstate, uint16_t global, bool forceLocal
valuePopped(pstate, 1);
}
static void _class(CParseState *pstate) {
uint16_t var = parseVariable(pstate, "Expected identifer!", false);
int entries = 0;
while (!match(pstate, TOKEN_END) && !match(pstate, TOKEN_EOF) && !pstate->hadError) {
if (match(pstate, TOKEN_FUNCTION)) {
// define method
consume(pstate, TOKEN_IDENTIFIER, "Expected identifier!");
uint16_t fieldIdent = identifierConstant(pstate, &pstate->previous);
// OP_NEWOBJECT expects the key on the stack before the value
writeu8(pstate, OP_LOADCONST);
writeu16(pstate, fieldIdent);
function(pstate, FTYPE_METHOD);
valuePopped(pstate, 1);
}
entries++;
}
writeu8(pstate, OP_NEWOBJECT);
writeu8(pstate, entries);
valuePushed(pstate, 1);
defineVariable(pstate, var, false);
}
static void popLocals(CParseState *pstate, int toScope) {
if (pstate->hadError)
return;
@ -708,7 +797,7 @@ static void endScope(CParseState *pstate) {
// parses expressionStatements until a TOKEN_END is consumed
static void block(CParseState *pstate) {
while(!check(pstate, TOKEN_END) && !check(pstate, TOKEN_EOF)) {
while(!check(pstate, TOKEN_END) && !check(pstate, TOKEN_EOF) && !check(pstate, TOKEN_ERROR)) {
declaration(pstate);
}
@ -760,7 +849,7 @@ static void ifStatement(CParseState *pstate) {
// parse until 'end' or 'else'
beginScope(pstate);
while(!check(pstate, TOKEN_END) && !check(pstate, TOKEN_ELSE) && !check(pstate, TOKEN_ELSEIF) && !check(pstate, TOKEN_EOF)) {
while(!check(pstate, TOKEN_END) && !check(pstate, TOKEN_ELSE) && !check(pstate, TOKEN_ELSEIF) && !check(pstate, TOKEN_EOF) && !check(pstate, TOKEN_ERROR)) {
declaration(pstate);
}
@ -828,7 +917,7 @@ static void function(CParseState *pstate, FunctionType type) {
}
// parse identifier for param (force them to be a local)
uint8_t funcIdent = parseVariable(pstate, "Expected identifier for function!", true);
uint16_t funcIdent = parseVariable(pstate, "Expected identifier for function!", true);
defineVariable(pstate, funcIdent, true);
valuePushed(pstate, 1); // they *will* be populated during runtime
} while (match(pstate, TOKEN_COMMA));
@ -840,7 +929,7 @@ static void function(CParseState *pstate, FunctionType type) {
alignStack(pstate, savedPushed);
endScope(pstate);
CObjFunction *objFunc = endCompiler(pstate, 0);
CObjFunction *objFunc = endCompiler(pstate);
// push closure
writeu8(pstate, OP_CLOSURE);
@ -855,7 +944,7 @@ static void function(CParseState *pstate, FunctionType type) {
}
static void functionDeclaration(CParseState *pstate) {
uint8_t var = parseVariable(pstate, "Expected identifer!", false);
uint16_t var = parseVariable(pstate, "Expected identifer!", false);
if (pstate->compiler->scopeDepth > 0)
markInitialized(pstate, var);
@ -871,27 +960,19 @@ static void returnStatement(CParseState *pstate) {
return;
}
// can return multiple results
int results = 0;
if (!check(pstate, TOKEN_EOS)) { // make sure its not an end of a statement
do {
expression(pstate);
results++;
} while (match(pstate, TOKEN_COMMA));
if (results > UINT8_MAX) {
error(pstate, "Too many results returned!");
return;
}
if (blockFollow(pstate->current)) { // does this return have a value
writeu8(pstate, OP_NIL);
writeu8(pstate, OP_RETURN);
return;
}
expression(pstate);
writeu8(pstate, OP_RETURN);
writeu8(pstate, results);
valuePopped(pstate, results);
valuePopped(pstate, 1);
}
static void localFunction(CParseState *pstate) {
uint8_t var = parseVariable(pstate, "Expected identifer!", true);
uint16_t var = parseVariable(pstate, "Expected identifer!", true);
markInitialized(pstate, var);
function(pstate, FTYPE_FUNCTION);
@ -907,6 +988,7 @@ static void forLoop(CParseState *pstate) {
// parse initalizer
if (!match(pstate, TOKEN_EOS)) {
expressionStatement(pstate);
consume(pstate, TOKEN_EOS, "Expected ';' after initalizer!");
}
int loopStart = getChunk(pstate)->count;
@ -985,14 +1067,13 @@ static void expressionStatement(CParseState *pstate) {
forLoop(pstate);
} else if (match(pstate, TOKEN_FUNCTION)) {
functionDeclaration(pstate);
} else if (match(pstate, TOKEN_CLASS)) {
_class(pstate);
} else if (match(pstate, TOKEN_RETURN)) {
returnStatement(pstate);
} else if (check(pstate, TOKEN_EOS)) {
// do nothing, just consume it
} else {
expression(pstate);
}
consume(pstate, TOKEN_EOS, "Expected end of statement after expression.");
// realign the stack
alignStack(pstate, pstate->compiler->savedPushed);
@ -1010,10 +1091,10 @@ static void declaration(CParseState *pstate) {
synchronize(pstate);
}
static CObjFunction *endCompiler(CParseState *pstate, int results) {
static CObjFunction *endCompiler(CParseState *pstate) {
popLocals(pstate, pstate->compiler->scopeDepth); // remove the locals from other scopes
writeu8(pstate, OP_NIL);
writeu8(pstate, OP_RETURN);
writeu8(pstate, results);
// update pstate to next compiler state
CCompilerState *cachedCCState = pstate->compiler;
@ -1041,7 +1122,7 @@ CObjFunction* cosmoP_compileString(CState *state, const char *source) {
popLocals(&parser, -1); // needed to close over the values
if (parser.hadError) { // we don't free the function, the state already has a reference to it in it's linked list of objects!
endCompiler(&parser, 0);
endCompiler(&parser);
freeParseState(&parser);
// the VM still expects a result on the stack TODO: push the error string to the stack
@ -1054,7 +1135,7 @@ CObjFunction* cosmoP_compileString(CState *state, const char *source) {
// VM expects the closure on the stack :P (we do this before ending the compiler so our GC doesn't free it)
cosmoV_pushValue(state, cosmoV_newObj((CObj*)cosmoO_newClosure(state, resFunc)));
endCompiler(&parser, 0);
endCompiler(&parser);
freeParseState(&parser);
cosmoM_unfreezeGC(state);
return resFunc;

View File

@ -4,35 +4,6 @@
#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);

View File

@ -2,6 +2,7 @@
#include "cchunk.h"
#include "cobj.h"
#include "cvm.h"
#include "cmem.h"
#include <string.h>
@ -32,10 +33,16 @@ CState *cosmoV_newState() {
cosmoT_initTable(state, &state->strings, 8); // init string table
cosmoT_initTable(state, &state->globals, 8); // init global table
state->initString = NULL;
state->initString = cosmoO_copyString(state, "__init", 6);
return state;
}
void cosmoV_freeState(CState *state) {
#ifdef GC_DEBUG
printf("state %p is being free'd!\n", state);
#endif
// frees all the objects
CObj *objs = state->objects;
while (objs != NULL) {
@ -45,6 +52,7 @@ void cosmoV_freeState(CState *state) {
}
// free our string & global table
state->initString = NULL;
cosmoT_clearTable(state, &state->strings);
cosmoT_clearTable(state, &state->globals);

View File

@ -6,8 +6,6 @@
#include "cobj.h"
#include "ctable.h"
typedef struct CCompilerState CCompilerState;
typedef struct CCallFrame {
CObjClosure *closure;
INSTRUCTION *pc;
@ -32,6 +30,8 @@ typedef struct CState {
CValue stack[STACK_MAX]; // stack
CCallFrame callFrame[FRAME_MAX]; // call frames
int frameCount;
CObjString *initString;
} CState;
COSMO_API CState *cosmoV_newState();

View File

@ -46,8 +46,11 @@ COSMO_API CObjString *cosmoV_toString(CState *state, CValue val) {
case COSMO_TOBJ: {
return cosmoO_toString(state, val.val.obj);
}
case COSMO_TNIL: {
return cosmoO_copyString(state, "nil", 3);
}
default:
return cosmoO_copyString(state, "<unkn>", 6);
return cosmoO_copyString(state, "<unkn val>", 10);
}
}
@ -67,6 +70,6 @@ void printValue(CValue val) {
printf("nil");
break;
default:
printf("<unkn>");
printf("<unkn val>");
}
}

136
src/cvm.c
View File

@ -105,19 +105,40 @@ CObjString *cosmoV_concat(CState *state, CObjString *strA, CObjString *strB) {
return cosmoO_takeString(state, buf, sz);
}
int cosmoV_execute(CState *state);
bool cosmoV_execute(CState *state);
typedef enum {
CALL_CLOSURE,
CALL_CFUNCTION
} preCallResult;
int cosmoV_preCall(CState *state, int args, int nresults) {
return -1;
bool call(CState *state, CObjClosure *closure, int args) {
// 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 false;
}
// load function into callframe
pushCallFrame(state, closure, closure->function->args);
// execute
if (!cosmoV_execute(state))
return false;
// remember where the return value is
CValue* result = cosmoV_getTop(state, 0);
// pop the callframe and return result :)
popCallFrame(state);
// push the return value back onto the stack
cosmoV_pushValue(state, *result);
return true;
}
// args = # of pass parameters, nresults = # of expected results
COSMOVMRESULT cosmoV_call(CState *state, int args, int nresults) {
// args = # of pass parameters
COSMOVMRESULT cosmoV_call(CState *state, int args) {
StkPtr val = cosmoV_getTop(state, args); // function will always be right above the args
if (!(val->type == COSMO_TOBJ)) {
@ -128,9 +149,18 @@ COSMOVMRESULT cosmoV_call(CState *state, int args, int nresults) {
switch (val->val.obj->type) {
case COBJ_CLOSURE: {
CObjClosure *closure = (CObjClosure*)(val->val.obj);
if (!call(state, closure, args)) {
return COSMOVM_RUNTIME_ERR;
}
break;
}
case COBJ_METHOD: {
CObjMethod *method = (CObjMethod*)val->val.obj;
*val = cosmoV_newObj(method->obj); // sets the object on the stack so the function can reference it in it's first argument
// missmatched args, thats an obvious user error, so error.
if (args != closure->function->args) {
CObjClosure *closure = method->closure;
if (args+1 != 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;
}
@ -139,22 +169,53 @@ COSMOVMRESULT cosmoV_call(CState *state, int args, int nresults) {
pushCallFrame(state, closure, closure->function->args);
// execute
int res = cosmoV_execute(state);
if (!cosmoV_execute(state))
return COSMOVM_RUNTIME_ERR;
CValue* result = state->top;
// 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 :)
// pop the callframe and return result
popCallFrame(state);
state->top++; // adjust stack back into place
cosmoV_pushValue(state, *result); // and push return value
break;
}
case COBJ_OBJECT: {
CObjObject *metaObj = (CObjObject*)val->val.obj;
CObjObject *newObj = cosmoO_newObject(state);
newObj->meta = metaObj;
*val = cosmoV_newObj(newObj);
CValue ret;
// 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());
// check if they defined an initalizer
if (cosmoO_getObject(state, metaObj, cosmoV_newObj(state->initString), &ret) && IS_CLOSURE(ret)) {
CObjClosure *closure = cosmoV_readClosure(ret);
if (args+1 != 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
if (!cosmoV_execute(state))
return COSMOVM_RUNTIME_ERR;
// we throw away the return result, it's unused
// pop the callframe and return result :)
popCallFrame(state);
state->top++; // adjust stack back into place
} else {
// no default initalizer
if (args != 0) {
runtimeError(state, "Expected 0 parameters, got %d!", args);
return COSMOVM_RUNTIME_ERR;
}
state->top--;
}
cosmoV_pushValue(state, cosmoV_newObj(newObj));
break;
}
case COBJ_CFUNCTION: {
@ -163,21 +224,11 @@ COSMOVMRESULT cosmoV_call(CState *state, int args, int nresults) {
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);
CValue 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());
}
cosmoV_pushValue(state, res);
break;
}
default:
@ -199,12 +250,11 @@ static inline bool isFalsey(StkPtr val) {
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); \
runtimeError(state, "Expected number!"); \
} \
// returns -1 if error, otherwise returns ammount of results
int cosmoV_execute(CState *state) {
// returns false if panic
bool 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 :)
@ -285,8 +335,7 @@ int cosmoV_execute(CState *state) {
}
case OP_CALL: {
uint8_t args = READBYTE();
uint8_t results = READBYTE();
COSMOVMRESULT result = cosmoV_call(state, args, results);
COSMOVMRESULT result = cosmoV_call(state, args);
if (result != COSMOVM_OK) {
return result;
}
@ -320,12 +369,12 @@ int cosmoV_execute(CState *state) {
case OP_NEWOBJECT: {
uint8_t entries = READBYTE();
StkPtr key, val;
CObjObject *newObj = cosmoO_newObject(state, entries * 3); // start the table with enough space to hopefully prevent reallocation since that's costly
CObjObject *newObj = cosmoO_newObject(state); // start the table with enough space to hopefully prevent reallocation since that's costly
cosmoV_pushValue(state, cosmoV_newObj(newObj)); // so our GC doesn't free our new object
for (int i = 0; i < entries; i++) {
val = cosmoV_getTop(state, (i*2) + 2);
key = cosmoV_getTop(state, (i*2) + 1);
val = cosmoV_getTop(state, (i*2) + 1);
key = cosmoV_getTop(state, (i*2) + 2);
// set key/value pair
CValue *newVal = cosmoT_insert(state, &newObj->tbl, *key);
@ -351,6 +400,10 @@ int cosmoV_execute(CState *state) {
CValue val; // to hold our value
cosmoO_getObject(state, object, *key, &val);
if (IS_CLOSURE(val)) { // is it a function? if so, make it a method to the current object
CObjMethod *method = cosmoO_newMethod(state, cosmoV_readClosure(val), object);
val = cosmoV_newObj(method);
}
cosmoV_setTop(state, 2); // pops the object & the key
cosmoV_pushValue(state, val); // pushes the field result
break;
@ -453,8 +506,7 @@ int cosmoV_execute(CState *state) {
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;
return true;
}
default:
CERROR("unknown opcode!");
@ -467,7 +519,7 @@ int cosmoV_execute(CState *state) {
#undef READUINT
// we'll only reach this is state->panic is true
return COSMOVM_RUNTIME_ERR;
return false;
}
#undef BINARYOP

View File

@ -11,6 +11,6 @@ typedef enum {
} COSMOVMRESULT;
// args = # of pass parameters, nresults = # of expected results
COSMO_API COSMOVMRESULT cosmoV_call(CState *state, int args, int nresults);
COSMO_API COSMOVMRESULT cosmoV_call(CState *state, int args);
#endif

View File

@ -7,12 +7,12 @@
#include "cmem.h"
static bool _ACTIVE;
static bool _ACTIVE = false;
int cosmoB_quitRepl(CState *state, int nargs, CValue *args) {
CValue cosmoB_quitRepl(CState *state, int nargs, CValue *args) {
_ACTIVE = false;
return 0; // we don't do anything to the stack
return cosmoV_newNil(); // we don't return anything
}
static void interpret(CState *state, const char* script) {
@ -22,11 +22,10 @@ static void interpret(CState *state, const char* script) {
if (func != NULL) {
disasmChunk(&func->chunk, "_main", 0);
cosmoV_call(state, 0, 0); // 0 args being passed, 0 results expected
COSMOVMRESULT res = cosmoV_call(state, 0); // 0 args being passed
//cosmoV_printStack(state);
//cosmoT_printTable(&state->globals, "globals");
//cosmoT_printTable(&state->strings, "strings");
if (res == COSMOVM_RUNTIME_ERR)
state->panic = false; // so our repl isn't broken
}
}

View File

@ -1,7 +1,26 @@
var test = {}
class test
function __init(self, str)
self.hello = str
end
if !test.hello then
test.hello = "hello world!"
function print(self, i)
local str = self.hello
for (var x = i; x > 0; x=x-1) do
str = str .. "!"
end
print(str)
end
end
print(test.hello .. "!!!!")
var obj = test("Hello world")
for (var i = 1; i <= 10; i=i+1) do
obj.print(i)
end
test.debug = function(self)
print("hi from " .. self)
end
obj.debug()