mirror of
https://github.com/CPunch/Cosmo.git
synced 2024-12-03 12:42:20 +00:00
Initial commit
This commit is contained in:
commit
2e1b745624
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*.o
|
||||
bin
|
12
.vscode/settings.json
vendored
Normal file
12
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"array": "cpp",
|
||||
"functional": "cpp",
|
||||
"istream": "cpp",
|
||||
"ostream": "cpp",
|
||||
"tuple": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"utility": "cpp",
|
||||
"vector": "cpp"
|
||||
}
|
||||
}
|
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Seth Stubbs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
48
Makefile
Normal file
48
Makefile
Normal file
@ -0,0 +1,48 @@
|
||||
# make clean && make && ./bin/cosmo
|
||||
|
||||
CC=clang
|
||||
CFLAGS=-fPIE -O3 #-g3
|
||||
LDFLAGS=#-fsanitize=address
|
||||
OUT=bin/cosmo
|
||||
|
||||
CHDR=\
|
||||
src/cchunk.h\
|
||||
src/cdebug.h\
|
||||
src/clex.h\
|
||||
src/cmem.h\
|
||||
src/coperators.h\
|
||||
src/cosmo.h\
|
||||
src/cparse.h\
|
||||
src/cstate.h\
|
||||
src/cvalue.h\
|
||||
src/ctable.h\
|
||||
src/cvm.h\
|
||||
src/cobj.h\
|
||||
src/cbaselib.h\
|
||||
|
||||
CSRC=\
|
||||
src/cchunk.c\
|
||||
src/cdebug.c\
|
||||
src/clex.c\
|
||||
src/cmem.c\
|
||||
src/coperators.c\
|
||||
src/cparse.c\
|
||||
src/cstate.c\
|
||||
src/cvalue.c\
|
||||
src/ctable.c\
|
||||
src/cvm.c\
|
||||
src/cobj.c\
|
||||
src/cbaselib.c\
|
||||
src/main.c\
|
||||
|
||||
COBJ=$(CSRC:.c=.o)
|
||||
|
||||
.c.o:
|
||||
$(CC) -c $(CFLAGS) $< -o $@
|
||||
|
||||
$(OUT): $(COBJ) $(CHDR)
|
||||
mkdir -p bin
|
||||
$(CC) $(COBJ) $(LDFLAGS) -o $(OUT)
|
||||
|
||||
clean:
|
||||
rm -rf $(COBJ) $(OUT)
|
7
README.md
Normal file
7
README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Cosmo
|
||||
Cosmo is a portable scripting language loosely based off of Lua. Designed for embeddability, Cosmo will have a built-in C++ wrapper for ease of use for embedding in in C++ applications.
|
||||
|
||||
# Why Cosmo?
|
||||
While C++ wrappers for Lua exist (see: SolLua), they're all maintained by outside entitties while Cosmo writes it's own first party wrapper. Additionally, Cosmo is very easily modifiable having been written in clean C99 with well documented code; this makes it a great candidate for early language hackers and researchers alike.
|
||||
|
||||
However Cosmo is not just a friendly developer tool, Cosmo's easy syntax and readability makes it a great scripting language for everyone to use.
|
13
fortest.lua
Normal file
13
fortest.lua
Normal file
@ -0,0 +1,13 @@
|
||||
local function fact(num)
|
||||
var total = 1
|
||||
for (var i = num; i > 0; i = i - 1) do
|
||||
total = total * i
|
||||
end
|
||||
return total
|
||||
end
|
||||
|
||||
for (var x = 0; x < 1000; x=x+1) do
|
||||
for (var z = 0; z < 100; z=z+1) do
|
||||
print("The factorial of " .. z .. " is " .. fact(z))
|
||||
end
|
||||
end
|
18
src/cbaselib.c
Normal file
18
src/cbaselib.c
Normal 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
9
src/cbaselib.h
Normal 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
70
src/cchunk.c
Normal 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
39
src/cchunk.h
Normal 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
158
src/cdebug.c
Normal 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
9
src/cdebug.h
Normal 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
239
src/clex.c
Normal 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
88
src/clex.h
Normal 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
217
src/cmem.c
Normal 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
47
src/cmem.h
Normal 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
209
src/cobj.c
Normal 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
98
src/cobj.h
Normal 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
1
src/coperators.c
Normal file
@ -0,0 +1 @@
|
||||
#include "coperators.h"
|
59
src/coperators.h
Normal file
59
src/coperators.h
Normal 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
35
src/cosmo.h
Normal 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
1017
src/cparse.c
Normal file
File diff suppressed because it is too large
Load Diff
39
src/cparse.h
Normal file
39
src/cparse.h
Normal 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
74
src/cstate.c
Normal 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
63
src/cstate.h
Normal 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
193
src/ctable.c
Normal 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
29
src/ctable.h
Normal 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
72
src/cvalue.c
Normal 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
62
src/cvalue.h
Normal 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
417
src/cvm.c
Normal 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
16
src/cvm.h
Normal 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
131
src/main.c
Normal 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;
|
||||
}
|
21
test.lua
Normal file
21
test.lua
Normal file
@ -0,0 +1,21 @@
|
||||
local function fact(i)
|
||||
local total = 1
|
||||
local x = i
|
||||
|
||||
while (x > 1) do
|
||||
total = total * x
|
||||
x = x - 1
|
||||
end
|
||||
|
||||
return total
|
||||
end
|
||||
|
||||
local i = 1
|
||||
while i < 1000 do
|
||||
local x = 1
|
||||
while x < 100 do
|
||||
print("The factorial of " .. x .. " is " .. fact(x))
|
||||
x = x + 1
|
||||
end
|
||||
i = i + 1
|
||||
end
|
Loading…
Reference in New Issue
Block a user