Merge pull request #32 from ThakeeNathees/test-optimizations

Iterations were heavily optimized
This commit is contained in:
Thakee Nathees 2021-05-27 22:53:30 +05:30 committed by GitHub
commit 897a08b3cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 874 additions and 523 deletions

View File

@ -4,7 +4,7 @@
<img src="https://user-images.githubusercontent.com/41085900/117528974-88fa8d00-aff2-11eb-8001-183c14786362.png" width="500" > <img src="https://user-images.githubusercontent.com/41085900/117528974-88fa8d00-aff2-11eb-8001-183c14786362.png" width="500" >
</p> </p>
**PocketLang** is a simple and fast programming language written in C. It's syntactically similar to Ruby and it can be learned in less than an hour. Including the compiler, bytecode VM and runtime, it's a standalone executable with zero external dependecies just as it's self descriptive name. The pocketlang VM can be embedded in another hosting program very easily, as static/shared library or a generated single header version of the source, which makes it even effortless. **PocketLang** is a small (~3000 semicollons) and fast programming language written in C. It's syntactically similar to Ruby and it can be learned in less than an hour. Including the compiler, bytecode VM and runtime, it's a standalone executable with zero external dependecies just as it's self descriptive name. The pocketlang VM can be embedded in another hosting program very easily, as static/shared library or a generated single header version of the source, which makes it even effortless.
The language is written using [Wren Language](https://wren.io/) and their wonderful book [craftinginterpreters](http://www.craftinginterpreters.com/) as a reference. The language is written using [Wren Language](https://wren.io/) and their wonderful book [craftinginterpreters](http://www.craftinginterpreters.com/) as a reference.

View File

@ -5,6 +5,7 @@
- -v --version - -v --version
- emit opcodes - emit opcodes
- maybe write a similer .pyc file for pocket - maybe write a similer .pyc file for pocket
- --docs to generate docs from cstring.
- Ignore line with '\' character. - Ignore line with '\' character.
- Implement resolve path in cli. - Implement resolve path in cli.

View File

@ -9,6 +9,7 @@
#include <stdio.h> #include <stdio.h>
void stdPathAbspath(PKVM* vm) { void stdPathAbspath(PKVM* vm) {
PkVar path; PkVar path;
if (!pkGetArgValue(vm, 1, PK_STRING, &path)) return; if (!pkGetArgValue(vm, 1, PK_STRING, &path)) return;

View File

@ -118,5 +118,6 @@ int main(int argc, char** argv) {
PkInterpretResult result = pkInterpret(vm, argv[1]); PkInterpretResult result = pkInterpret(vm, argv[1]);
pkFreeVM(vm); pkFreeVM(vm);
return result; return result;
} }

View File

@ -8,42 +8,56 @@
#include "utils.h" #include "utils.h"
#include "vm.h" #include "vm.h"
#define DEFINE_BUFFER(M__NAME, M__NAME_L, M__TYPE) \ // TODO: Replace bufferFill with bufferWrite, and (maybe) shrink the buffer if
void M__NAME_L##BufferInit(M__NAME##Buffer* self) { \ // it's more than enough.
self->data = NULL; \
self->count = 0; \
self->capacity = 0; \
} \
\
void M__NAME_L##BufferClear(M__NAME##Buffer* self, \
PKVM* vm) { \
vmRealloc(vm, self->data, self->capacity * sizeof(M__TYPE), 0); \
self->data = NULL; \
self->count = 0; \
self->capacity = 0; \
} \
\
void M__NAME_L##BufferFill(M__NAME##Buffer* self, PKVM* vm, \
M__TYPE data, int count) { \
\
if (self->capacity < self->count + count) { \
int capacity = utilPowerOf2Ceil((int)self->count + count); \
if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY; \
self->data = (M__TYPE*)vmRealloc(vm, self->data, \
self->capacity * sizeof(M__TYPE), capacity * sizeof(M__TYPE)); \
self->capacity = capacity; \
} \
\
for (int i = 0; i < count; i++) { \
self->data[self->count++] = data; \
} \
} \
\
void M__NAME_L##BufferWrite(M__NAME##Buffer* self, \
PKVM* vm, M__TYPE data) { \
M__NAME_L##BufferFill(self, vm, data, 1); \
} \
#define DEFINE_BUFFER(M__NAME, M__NAME_L, M__TYPE) \
void M__NAME_L##BufferInit(M__NAME##Buffer* self) { \
self->data = NULL; \
self->count = 0; \
self->capacity = 0; \
} \
\
void M__NAME_L##BufferClear(M__NAME##Buffer* self, \
PKVM* vm) { \
vmRealloc(vm, self->data, self->capacity * sizeof(M__TYPE), 0); \
self->data = NULL; \
self->count = 0; \
self->capacity = 0; \
} \
\
void M__NAME_L##BufferReserve(M__NAME##Buffer* self, PKVM* vm, size_t size) { \
if (self->capacity < size) { \
int capacity = utilPowerOf2Ceil((int)size); \
if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY; \
self->data = (M__TYPE*)vmRealloc(vm, self->data, \
self->capacity * sizeof(M__TYPE), capacity * sizeof(M__TYPE)); \
self->capacity = capacity; \
} \
} \
\
void M__NAME_L##BufferFill(M__NAME##Buffer* self, PKVM* vm, \
M__TYPE data, int count) { \
\
M__NAME_L##BufferReserve(self, vm, self->count + count); \
\
for (int i = 0; i < count; i++) { \
self->data[self->count++] = data; \
} \
} \
\
void M__NAME_L##BufferWrite(M__NAME##Buffer* self, \
PKVM* vm, M__TYPE data) { \
M__NAME_L##BufferFill(self, vm, data, 1); \
} \
void ByteBufferAddString(ByteBuffer* self, PKVM* vm, const char* str,
uint32_t length) {
byteBufferReserve(self, vm, self->count + length);
for (uint32_t i = 0; i < length; i++) {
self->data[self->count++] = *(str++);
}
}
DEFINE_BUFFER(Uint, uint, uint32_t) DEFINE_BUFFER(Uint, uint, uint32_t)
DEFINE_BUFFER(Byte, byte, uint8_t) DEFINE_BUFFER(Byte, byte, uint8_t)

View File

@ -9,28 +9,30 @@
#include "common.h" #include "common.h"
#include "include/pocketlang.h" #include "include/pocketlang.h"
#define DECLARE_BUFFER(M__NAME, M__NAME_L, M__TYPE) \ #define DECLARE_BUFFER(M__NAME, M__NAME_L, M__TYPE) \
typedef struct { \ typedef struct { \
M__TYPE* data; \ M__TYPE* data; \
uint32_t count; \ uint32_t count; \
uint32_t capacity; \ uint32_t capacity; \
} M__NAME##Buffer; \ } M__NAME##Buffer; \
\ \
/* Initialize a new buffer int instance. */ \ /* Initialize a new buffer int instance. */ \
void M__NAME_L##BufferInit(M__NAME##Buffer* self); \ void M__NAME_L##BufferInit(M__NAME##Buffer* self); \
\ \
/* Clears the allocated elementes from the VM's realloc function. */ \ /* Clears the allocated elementes from the VM's realloc function. */ \
void M__NAME_L##BufferClear(M__NAME##Buffer* self, \ void M__NAME_L##BufferClear(M__NAME##Buffer* self, PKVM* vm); \
PKVM* vm); \ \
\ /* Ensure the capacity is greater than [size], if not resize. */ \
/* Fill the buffer at the end of it with provided data if the capacity */ \ void M__NAME_L##BufferReserve(M__NAME##Buffer* self, PKVM* vm, size_t size); \
/* isn't enough using VM's realloc function. */ \ \
void M__NAME_L##BufferFill(M__NAME##Buffer* self, PKVM* vm, \ /* Fill the buffer at the end of it with provided data if the capacity */ \
M__TYPE data, int count); \ /* isn't enough using VM's realloc function. */ \
\ void M__NAME_L##BufferFill(M__NAME##Buffer* self, PKVM* vm, \
/* Write to the buffer with provided data at the end of the buffer.*/ \ M__TYPE data, int count); \
void M__NAME_L##BufferWrite(M__NAME##Buffer* self, \ \
PKVM* vm, M__TYPE data); \ /* Write to the buffer with provided data at the end of the buffer.*/ \
void M__NAME_L##BufferWrite(M__NAME##Buffer* self, \
PKVM* vm, M__TYPE data); \
DECLARE_BUFFER(Uint, uint, uint32_t) DECLARE_BUFFER(Uint, uint, uint32_t)
@ -39,6 +41,11 @@ DECLARE_BUFFER(Var, var, Var)
DECLARE_BUFFER(String, string, String*) DECLARE_BUFFER(String, string, String*)
DECLARE_BUFFER(Function, function, Function*) DECLARE_BUFFER(Function, function, Function*)
// Add all the characters to the buffer, byte buffer can also be used as a
// buffer to write string (like a string stream).
void ByteBufferAddString(ByteBuffer* self, PKVM* vm, const char* str,
uint32_t length);
#undef DECLARE_BUFFER #undef DECLARE_BUFFER
#endif // BUFFERS_TEMPLATE_H #endif // BUFFERS_TEMPLATE_H

View File

@ -8,12 +8,28 @@
#include "include/pocketlang.h" #include "include/pocketlang.h"
// Commenly used c standard headers across the sources. Don't include any
// headers that are specific to a single source here, instead include them in
// their source files explicitly (canno't be implicitly included by another
// header). And don't include any C standard headers in any of the pocket lang
// headers.
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <float.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
// __STDC_LIMIT_MACROS and __STDC_CONSTANT_MACROS are a workaround to
// allow C++ programs to use stdint.h macros specified in the C99
// standard that aren't in the C++ standard.
#define __STDC_LIMIT_MACROS
#include <stdint.h>
/*****************************************************************************/
/* INTERNAL CONFIGURATIONS */
/*****************************************************************************/
// Set this to dump compiled opcodes of each functions. // Set this to dump compiled opcodes of each functions.
#define DEBUG_DUMP_COMPILED_CODE 0 #define DEBUG_DUMP_COMPILED_CODE 0
@ -21,12 +37,47 @@
// Set this to dump stack frame before executing the next instruction. // Set this to dump stack frame before executing the next instruction.
#define DEBUG_DUMP_CALL_STACK 0 #define DEBUG_DUMP_CALL_STACK 0
#include <stdio.h> //< Only needed for ASSERT() macro and for release mode // Nan-Tagging could be disable for debugging/portability purposes. see "var.h"
// header for more information on Nan-tagging.
#define VAR_NAN_TAGGING 1
// The factor by which a buffer will grow when it's capacity reached.
#define GROW_FACTOR 2
// The initial minimum capacity of a buffer to allocate.
#define MIN_CAPACITY 8
/*****************************************************************************/
/* ALLOCATION MACROS */
/*****************************************************************************/
// Allocate object of [type] using the vmRealloc function.
#define ALLOCATE(vm, type) \
((type*)vmRealloc(vm, NULL, 0, sizeof(type)))
// Allocate object of [type] which has a dynamic tail array of type [tail_type]
// with [count] entries.
#define ALLOCATE_DYNAMIC(vm, type, count, tail_type) \
((type*)vmRealloc(vm, NULL, 0, sizeof(type) + sizeof(tail_type) * (count)))
// Allocate [count] ammount of object of [type] array.
#define ALLOCATE_ARRAY(vm, type, count) \
((type*)vmRealloc(vm, NULL, 0, sizeof(type) * (count)))
// Deallocate a pointer allocated by vmRealloc before.
#define DEALLOCATE(vm, pointer) \
vmRealloc(vm, pointer, 0, 0)
/*****************************************************************************/
/* COMMON MACROS */
/*****************************************************************************/
#include <stdio.h> //< Only needed here for ASSERT() macro and for release mode
//< TODO; macro use this to print a crash report. //< TODO; macro use this to print a crash report.
// The internal assertion macro, this will print error and break regardless of // The internal assertion macro, this will print error and break regardless of
// the build target (debug or release). Use ASSERT() for debug assertion and // the build target (debug or release). Use ASSERT() for debug assertion and
// use __ASSERT() for TODOs and assetion's in public methods (to indicate that // use __ASSERT() for TODOs and assetions in public methods (to indicate that
// the host application did something wrong). // the host application did something wrong).
#define __ASSERT(condition, message) \ #define __ASSERT(condition, message) \
do { \ do { \
@ -60,9 +111,9 @@
#else #else
#define DEBUG_BREAK() #define DEBUG_BREAK() ((void*)0)
#define ASSERT(condition, message) do { } while (false) #define ASSERT(condition, message) ((void*)0)
#define ASSERT_INDEX(index, size) do {} while (false) #define ASSERT_INDEX(index, size) ((void*)0)
// Reference : https://github.com/wren-lang/ // Reference : https://github.com/wren-lang/
#if defined( _MSC_VER ) #if defined( _MSC_VER )
@ -70,45 +121,28 @@
#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) #elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5))
#define UNREACHABLE() __builtin_unreachable() #define UNREACHABLE() __builtin_unreachable()
#else #else
#define UNREACHABLE() #define UNREACHABLE() ((void*)0)
#endif #endif
#endif // DEBUG #endif // DEBUG
#if defined( _MSC_VER )
#define forceinline __forceinline
#else
#define forceinline __attribute__((always_inline))
#endif
// Using __ASSERT() for make it crash in release binary too. // Using __ASSERT() for make it crash in release binary too.
#define TODO __ASSERT(false, "TODO: It hasn't implemented yet.") #define TODO __ASSERT(false, "TODO: It hasn't implemented yet.")
#define OOPS "Oops a bug!! report plese." #define OOPS "Oops a bug!! report plese."
#define STRINGIFY(x) TOSTRING(x)
#define TOSTRING(x) #x #define TOSTRING(x) #x
#define M_CONC(a, b) M_CONC_(a, b) #define STRINGIFY(x) TOSTRING(x)
#define M_CONC_(a, b) a##b #define STR_NUM_BUFF_SIZE (3 + DBL_MANT_DIG - DBL_MIN_EXP)
// The factor by which a buffer will grow when it's capacity reached. /*****************************************************************************/
#define GROW_FACTOR 2 /* INTERNAL TYPE DEFINES */
/*****************************************************************************/
// The initial capacity of a buffer.
#define MIN_CAPACITY 8
// Allocate object of [type] using the vmRealloc function.
#define ALLOCATE(vm, type) \
((type*)vmRealloc(vm, NULL, 0, sizeof(type)))
// Allocate object of [type] which has a dynamic tail array of type [tail_type]
// with [count] entries.
#define ALLOCATE_DYNAMIC(vm, type, count, tail_type) \
((type*)vmRealloc(vm, NULL, 0, sizeof(type) + sizeof(tail_type) * (count)))
// Allocate [count] ammount of object of [type] array.
#define ALLOCATE_ARRAY(vm, type, count) \
((type*)vmRealloc(vm, NULL, 0, sizeof(type) * (count)))
// Deallocate a pointer allocated by vmRealloc before.
#define DEALLOCATE(vm, pointer) \
vmRealloc(vm, pointer, 0, 0)
// Unique number to identify for various cases.
typedef uint32_t ID;
#if VAR_NAN_TAGGING #if VAR_NAN_TAGGING
typedef uint64_t Var; typedef uint64_t Var;

View File

@ -5,9 +5,6 @@
#include "compiler.h" #include "compiler.h"
#include <stdio.h>
#include <string.h>
#include "core.h" #include "core.h"
#include "buffers.h" #include "buffers.h"
#include "utils.h" #include "utils.h"
@ -107,7 +104,6 @@ typedef enum {
TK_END, // end TK_END, // end
TK_NULL, // null TK_NULL, // null
TK_SELF, // self
TK_IN, // in TK_IN, // in
TK_AND, // and TK_AND, // and
TK_OR, // or TK_OR, // or
@ -170,7 +166,6 @@ static _Keyword _keywords[] = {
{ "func", 4, TK_FUNC }, { "func", 4, TK_FUNC },
{ "end", 3, TK_END }, { "end", 3, TK_END },
{ "null", 4, TK_NULL }, { "null", 4, TK_NULL },
{ "self", 4, TK_SELF },
{ "in", 2, TK_IN }, { "in", 2, TK_IN },
{ "and", 3, TK_AND }, { "and", 3, TK_AND },
{ "or", 2, TK_OR }, { "or", 2, TK_OR },
@ -993,7 +988,6 @@ GrammarRule rules[] = { // Prefix Infix Infix Precedence
/* TK_FUNC */ { exprFunc, NULL, NO_INFIX }, /* TK_FUNC */ { exprFunc, NULL, NO_INFIX },
/* TK_END */ NO_RULE, /* TK_END */ NO_RULE,
/* TK_NULL */ { exprValue, NULL, NO_INFIX }, /* TK_NULL */ { exprValue, NULL, NO_INFIX },
/* TK_SELF */ { exprValue, NULL, NO_INFIX },
/* TK_IN */ { NULL, exprBinaryOp, PREC_IN }, /* TK_IN */ { NULL, exprBinaryOp, PREC_IN },
/* TK_AND */ { NULL, exprAnd, PREC_LOGICAL_AND }, /* TK_AND */ { NULL, exprAnd, PREC_LOGICAL_AND },
/* TK_OR */ { NULL, exprOr, PREC_LOGICAL_OR }, /* TK_OR */ { NULL, exprOr, PREC_LOGICAL_OR },
@ -1408,7 +1402,6 @@ static void exprValue(Compiler* compiler, bool can_assign) {
TokenType op = compiler->previous.type; TokenType op = compiler->previous.type;
switch (op) { switch (op) {
case TK_NULL: emitOpcode(compiler, OP_PUSH_NULL); return; case TK_NULL: emitOpcode(compiler, OP_PUSH_NULL); return;
case TK_SELF: emitOpcode(compiler, OP_PUSH_SELF); return;
case TK_TRUE: emitOpcode(compiler, OP_PUSH_TRUE); return; case TK_TRUE: emitOpcode(compiler, OP_PUSH_TRUE); return;
case TK_FALSE: emitOpcode(compiler, OP_PUSH_FALSE); return; case TK_FALSE: emitOpcode(compiler, OP_PUSH_FALSE); return;
default: default:
@ -1514,9 +1507,9 @@ static int compilerAddVariable(Compiler* compiler, const char* name,
static void compilerAddForward(Compiler* compiler, int instruction, Fn* fn, static void compilerAddForward(Compiler* compiler, int instruction, Fn* fn,
const char* name, int length, int line) { const char* name, int length, int line) {
if (compiler->forwards_count == MAX_VARIABLES) { if (compiler->forwards_count == MAX_FORWARD_NAMES) {
parseError(compiler, "A script should contain at most %d implict forward " parseError(compiler, "A script should contain at most %d implict forward "
"function declarations.", MAX_VARIABLES); "function declarations.", MAX_FORWARD_NAMES);
return; return;
} }
@ -1543,7 +1536,7 @@ static int compilerAddConstant(Compiler* compiler, Var value) {
varBufferWrite(literals, compiler->vm, value); varBufferWrite(literals, compiler->vm, value);
} else { } else {
parseError(compiler, "A script should contain at most %d " parseError(compiler, "A script should contain at most %d "
"unique constants.", MAX_CONSTANTS); "unique constants.", MAX_CONSTANTS);
} }
return (int)literals->count - 1; return (int)literals->count - 1;
} }
@ -2180,11 +2173,10 @@ static void compileForStatement(Compiler* compiler) {
int container = compilerAddVariable(compiler, "@container", 10, iter_line); int container = compilerAddVariable(compiler, "@container", 10, iter_line);
compileExpression(compiler); compileExpression(compiler);
// Add iterator to locals. It would initially be null and once the loop // Add iterator to locals. It's an increasing integer indicating that the
// started it'll be an increasing integer indicating that the current // current loop is nth starting from 0.
// loop is nth.
int iterator = compilerAddVariable(compiler, "@iterator", 9, iter_line); int iterator = compilerAddVariable(compiler, "@iterator", 9, iter_line);
emitOpcode(compiler, OP_PUSH_NULL); emitOpcode(compiler, OP_PUSH_0);
// Add the iteration value. It'll be updated to each element in an array of // Add the iteration value. It'll be updated to each element in an array of
// each character in a string etc. // each character in a string etc.
@ -2192,6 +2184,9 @@ static void compileForStatement(Compiler* compiler) {
iter_line); iter_line);
emitOpcode(compiler, OP_PUSH_NULL); emitOpcode(compiler, OP_PUSH_NULL);
// Start the iteration, and check if the sequence is iterable.
emitOpcode(compiler, OP_ITER_TEST);
Loop loop; Loop loop;
loop.start = (int)_FN->opcodes.count; loop.start = (int)_FN->opcodes.count;
loop.patch_count = 0; loop.patch_count = 0;
@ -2205,8 +2200,8 @@ static void compileForStatement(Compiler* compiler) {
compileBlockBody(compiler, BLOCK_LOOP); compileBlockBody(compiler, BLOCK_LOOP);
emitLoopJump(compiler); emitLoopJump(compiler); //< Loop back to iteration.
patchJump(compiler, forpatch); patchJump(compiler, forpatch); //< Patch exit iteration address.
// Patch break statement. // Patch break statement.
for (int i = 0; i < compiler->loop->patch_count; i++) { for (int i = 0; i < compiler->loop->patch_count; i++) {
@ -2356,7 +2351,6 @@ bool compile(PKVM* vm, Script* script, const char* source) {
skipNewLines(compiler); skipNewLines(compiler);
} }
// TODO: the stack already has a null remove one (at vm.c or here).
emitOpcode(compiler, OP_PUSH_NULL); emitOpcode(compiler, OP_PUSH_NULL);
emitOpcode(compiler, OP_RETURN); emitOpcode(compiler, OP_RETURN);
emitOpcode(compiler, OP_END); emitOpcode(compiler, OP_END);

View File

@ -59,13 +59,14 @@ void pkModuleAddFunction(PKVM* vm, PkHandle* module, const char* name,
// Check for errors in before calling the get arg public api function. // Check for errors in before calling the get arg public api function.
#define CHECK_GET_ARG_API_ERRORS() \ #define CHECK_GET_ARG_API_ERRORS() \
__ASSERT(vm->fiber != NULL, "This function can only be called at runtime."); \ __ASSERT(vm->fiber != NULL, "This function can only be called at runtime."); \
__ASSERT(arg > 0 || arg <= ARGC, "Invalid argument index."); \ __ASSERT(arg > 0 || arg <= ARGC, "Invalid argument index."); \
__ASSERT(value != NULL, "Parameter [value] was NULL."); \ __ASSERT(value != NULL, "Parameter [value] was NULL."); \
((void*)0) ((void*)0)
#define ERR_INVALID_ARG_TYPE(m_type) \ #define ERR_INVALID_ARG_TYPE(m_type) \
do { \ do { \
/* 12 chars is enought for a 4 byte integer string.*/ \ /* 12 chars is enought for a 4 byte integer string */ \
/* including the negative sign.*/ \
char buff[12]; \ char buff[12]; \
sprintf(buff, "%d", arg); \ sprintf(buff, "%d", arg); \
vm->fiber->error = stringFormat(vm, "Expected a " m_type \ vm->fiber->error = stringFormat(vm, "Expected a " m_type \
@ -141,14 +142,12 @@ void pkReturnValue(PKVM* vm, PkVar value) {
RET(*(Var*)value); RET(*(Var*)value);
} }
// ---------------------------------------------------------------------------- /*****************************************************************************/
/* CORE INTERNAL */
/*****************************************************************************/
// Convert number var as int32_t. Check if it's number before using it.
#define _AS_INTEGER(var) (int32_t)trunc(AS_NUM(var))
static void initializeBuiltinFN(PKVM* vm, BuiltinFn* bfn, const char* name, static void initializeBuiltinFN(PKVM* vm, BuiltinFn* bfn, const char* name,
int length, int arity, pkNativeFn ptr) { int length, int arity, pkNativeFn ptr) {
bfn->name = name; bfn->name = name;
bfn->length = length; bfn->length = length;
@ -239,7 +238,6 @@ static inline bool validateIndex(PKVM* vm, int32_t index, int32_t size,
/* BUILTIN FUNCTIONS API */ /* BUILTIN FUNCTIONS API */
/*****************************************************************************/ /*****************************************************************************/
Function* getBuiltinFunction(PKVM* vm, int index) { Function* getBuiltinFunction(PKVM* vm, int index) {
ASSERT_INDEX(index, vm->builtins_count); ASSERT_INDEX(index, vm->builtins_count);
return vm->builtins[index].fn; return vm->builtins[index].fn;
@ -288,10 +286,17 @@ FN_IS_OBJ_TYPE(Function, OBJ_FUNC)
FN_IS_OBJ_TYPE(Script, OBJ_SCRIPT) FN_IS_OBJ_TYPE(Script, OBJ_SCRIPT)
FN_IS_OBJ_TYPE(UserObj, OBJ_USER) FN_IS_OBJ_TYPE(UserObj, OBJ_USER)
PK_DOC(coreTypeName,
"type_name(value:var) -> string\n"
"Returns the type name of the of the value.");
void coreTypeName(PKVM* vm) { void coreTypeName(PKVM* vm) {
RET(VAR_OBJ(&newString(vm, varTypeName(ARG1))->_super)); RET(VAR_OBJ(&newString(vm, varTypeName(ARG1))->_super));
} }
PK_DOC(coreAssert,
"assert(condition:bool [, msg:string]) -> void\n"
"If the condition is false it'll terminate the current fiber with the "
"optional error message");
void coreAssert(PKVM* vm) { void coreAssert(PKVM* vm) {
int argc = ARGC; int argc = ARGC;
if (argc != 1 && argc != 2) { if (argc != 1 && argc != 2) {
@ -299,12 +304,13 @@ void coreAssert(PKVM* vm) {
return; return;
} }
if (!toBool(ARG1)) { if (!toBool(ARG1)) {
String* msg = NULL; String* msg = NULL;
if (argc == 2) { if (argc == 2) {
if (AS_OBJ(ARG2)->type != OBJ_STRING) { if (AS_OBJ(ARG2)->type != OBJ_STRING) {
msg = toString(vm, ARG2, false); msg = toString(vm, ARG2);
} else { } else {
msg = (String*)AS_OBJ(ARG2); msg = (String*)AS_OBJ(ARG2);
} }
@ -317,20 +323,17 @@ void coreAssert(PKVM* vm) {
} }
} }
// Return the has value of the variable, if it's not hashable it'll return null. PK_DOC(coreToString,
void coreHash(PKVM* vm) { "to_string(value:var) -> string\n"
if (IS_OBJ(ARG1)) { "Returns the string representation of the value.");
if (!isObjectHashable(AS_OBJ(ARG1)->type)) {
RET(VAR_NULL);
}
}
RET(VAR_NUM((double)varHashValue(ARG1)));
}
void coreToString(PKVM* vm) { void coreToString(PKVM* vm) {
RET(VAR_OBJ(&toString(vm, ARG1, false)->_super)); RET(VAR_OBJ(&toString(vm, ARG1)->_super));
} }
PK_DOC(corePrint,
"print(...) -> void\n"
"Write each argument as comma seperated to the stdout and ends with a "
"newline.");
void corePrint(PKVM* vm) { void corePrint(PKVM* vm) {
// If the host appliaction donesn't provide any write function, discard the // If the host appliaction donesn't provide any write function, discard the
// output. // output.
@ -344,7 +347,7 @@ void corePrint(PKVM* vm) {
if (IS_OBJ(arg) && AS_OBJ(arg)->type == OBJ_STRING) { if (IS_OBJ(arg) && AS_OBJ(arg)->type == OBJ_STRING) {
str = (String*)AS_OBJ(arg); str = (String*)AS_OBJ(arg);
} else { } else {
str = toString(vm, arg, false); str = toString(vm, arg);
} }
if (i != 1) vm->config.write_fn(vm, " "); if (i != 1) vm->config.write_fn(vm, " ");
@ -507,13 +510,43 @@ void stdLangWrite(PKVM* vm) {
if (IS_OBJ(arg) && AS_OBJ(arg)->type == OBJ_STRING) { if (IS_OBJ(arg) && AS_OBJ(arg)->type == OBJ_STRING) {
str = (String*)AS_OBJ(arg); str = (String*)AS_OBJ(arg);
} else { } else {
str = toString(vm, arg, false); str = toString(vm, arg);
} }
vm->config.write_fn(vm, str->data); vm->config.write_fn(vm, str->data);
} }
} }
// 'math' library methods.
// -----------------------
void stdMathFloor(PKVM* vm) {
double num;
if (!validateNumeric(vm, ARG1, &num, "Parameter 1")) return;
RET(VAR_NUM(floor(num)));
}
void stdMathCeil(PKVM* vm) {
double num;
if (!validateNumeric(vm, ARG1, &num, "Parameter 1")) return;
RET(VAR_NUM(ceil(num)));
}
PK_DOC(stdMathHash,
"hash(value:var) -> num\n"
"Return the hash value of the variable, if it's not hashable it'll "
"return null.");
void stdMathHash(PKVM* vm) {
if (IS_OBJ(ARG1)) {
if (!isObjectHashable(AS_OBJ(ARG1)->type)) {
RET(VAR_NULL);
}
}
RET(VAR_NUM((double)varHashValue(ARG1)));
}
/*****************************************************************************/ /*****************************************************************************/
/* CORE INITIALIZATION */ /* CORE INITIALIZATION */
/*****************************************************************************/ /*****************************************************************************/
@ -526,6 +559,7 @@ void initializeCore(PKVM* vm) {
// Initialize builtin functions. // Initialize builtin functions.
INITALIZE_BUILTIN_FN("type_name", coreTypeName, 1); INITALIZE_BUILTIN_FN("type_name", coreTypeName, 1);
// TOOD: (maybe remove is_*() functions) suspend by type_name.
INITALIZE_BUILTIN_FN("is_null", coreIsNull, 1); INITALIZE_BUILTIN_FN("is_null", coreIsNull, 1);
INITALIZE_BUILTIN_FN("is_bool", coreIsBool, 1); INITALIZE_BUILTIN_FN("is_bool", coreIsBool, 1);
INITALIZE_BUILTIN_FN("is_num", coreIsNum, 1); INITALIZE_BUILTIN_FN("is_num", coreIsNum, 1);
@ -539,11 +573,10 @@ void initializeCore(PKVM* vm) {
INITALIZE_BUILTIN_FN("is_userobj", coreIsUserObj, 1); INITALIZE_BUILTIN_FN("is_userobj", coreIsUserObj, 1);
INITALIZE_BUILTIN_FN("assert", coreAssert, -1); INITALIZE_BUILTIN_FN("assert", coreAssert, -1);
INITALIZE_BUILTIN_FN("hash", coreHash, 1);
INITALIZE_BUILTIN_FN("to_string", coreToString, 1); INITALIZE_BUILTIN_FN("to_string", coreToString, 1);
INITALIZE_BUILTIN_FN("print", corePrint, -1); INITALIZE_BUILTIN_FN("print", corePrint, -1);
// string functions. // String functions.
INITALIZE_BUILTIN_FN("str_lower", coreStrLower, 1); INITALIZE_BUILTIN_FN("str_lower", coreStrLower, 1);
INITALIZE_BUILTIN_FN("str_upper", coreStrUpper, 1); INITALIZE_BUILTIN_FN("str_upper", coreStrUpper, 1);
INITALIZE_BUILTIN_FN("str_strip", coreStrStrip, 1); INITALIZE_BUILTIN_FN("str_strip", coreStrStrip, 1);
@ -560,7 +593,14 @@ void initializeCore(PKVM* vm) {
moduleAddFunctionInternal(vm, lang, "clock", stdLangClock, 0); moduleAddFunctionInternal(vm, lang, "clock", stdLangClock, 0);
moduleAddFunctionInternal(vm, lang, "gc", stdLangGC, 0); moduleAddFunctionInternal(vm, lang, "gc", stdLangGC, 0);
moduleAddFunctionInternal(vm, lang, "write", stdLangWrite, -1); moduleAddFunctionInternal(vm, lang, "write", stdLangWrite, -1);
#ifdef DEBUG
moduleAddFunctionInternal(vm, lang, "debug_break", stdLangDebugBreak, 0); moduleAddFunctionInternal(vm, lang, "debug_break", stdLangDebugBreak, 0);
#endif
Script* math = newModuleInternal(vm, "math");
moduleAddFunctionInternal(vm, math, "floor", stdMathFloor, 1);
moduleAddFunctionInternal(vm, math, "ceil", stdMathCeil, 1);
moduleAddFunctionInternal(vm, math, "hash", stdMathHash, 1);
} }
/*****************************************************************************/ /*****************************************************************************/
@ -588,7 +628,7 @@ Var varAdd(PKVM* vm, Var v1, Var v2) {
case OBJ_STRING: case OBJ_STRING:
{ {
if (o2->type == OBJ_STRING) { if (o2->type == OBJ_STRING) {
return VAR_OBJ(stringFormat(vm, "@@", (String*)o1, (String*)o2)); return VAR_OBJ(stringJoin(vm, (String*)o1, (String*)o2));
} }
} break; } break;
@ -731,16 +771,35 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
case OBJ_MAP: case OBJ_MAP:
{ {
Var value = mapGet((Map*)obj, VAR_OBJ(&attrib->_super)); TODO; // Not sure should I allow this(below).
if (IS_UNDEF(value)) { //Var value = mapGet((Map*)obj, VAR_OBJ(&attrib->_super));
vm->fiber->error = stringFormat(vm, "Key (\"@\") not exists.", attrib); //if (IS_UNDEF(value)) {
return VAR_NULL; // vm->fiber->error = stringFormat(vm, "Key (\"@\") not exists.", attrib);
} // return VAR_NULL;
return value; //}
//return value;
} }
case OBJ_RANGE: case OBJ_RANGE:
TODO; {
Range* range = (Range*)obj;
if (IS_ATTRIB("as_list")) {
List* list;
if (range->from < range->to) {
list = newList(vm, (uint32_t)(range->to - range->from));
for (double i = range->from; i < range->to; i++) {
varBufferWrite(&list->elements, vm, VAR_NUM(i));
}
} else {
newList(vm, 0);
}
return VAR_OBJ(&list->_super);
}
ERR_NO_ATTRIB();
return VAR_NULL;
}
case OBJ_SCRIPT: { case OBJ_SCRIPT: {
Script* scr = (Script*)obj; Script* scr = (Script*)obj;
@ -897,7 +956,7 @@ Var varGetSubscript(PKVM* vm, Var on, Var key) {
Var value = mapGet((Map*)obj, key); Var value = mapGet((Map*)obj, key);
if (IS_UNDEF(value)) { if (IS_UNDEF(value)) {
String* key_str = toString(vm, key, true); String* key_str = toString(vm, key);
vmPushTempRef(vm, &key_str->_super); vmPushTempRef(vm, &key_str->_super);
if (IS_OBJ(key) && !isObjectHashable(AS_OBJ(key)->type)) { if (IS_OBJ(key) && !isObjectHashable(AS_OBJ(key)->type)) {
vm->fiber->error = stringFormat(vm, "Invalid key '@'.", key_str); vm->fiber->error = stringFormat(vm, "Invalid key '@'.", key_str);
@ -969,107 +1028,3 @@ void varsetSubscript(PKVM* vm, Var on, Var key, Var value) {
CHECK_MISSING_OBJ_TYPE(7); CHECK_MISSING_OBJ_TYPE(7);
UNREACHABLE(); UNREACHABLE();
} }
bool varIterate(PKVM* vm, Var seq, Var* iterator, Var* value) {
#ifdef DEBUG
int32_t _temp;
ASSERT(IS_NUM(*iterator) || IS_NULL(*iterator), OOPS);
if (IS_NUM(*iterator)) {
ASSERT(validateIngeger(vm, *iterator, &_temp, "Assetion."), OOPS);
}
#endif
// Primitive types are not iterable.
if (!IS_OBJ(seq)) {
if (IS_NULL(seq)) {
vm->fiber->error = newString(vm, "Null is not iterable.");
} else if (IS_BOOL(seq)) {
vm->fiber->error = newString(vm, "Boolenan is not iterable.");
} else if (IS_NUM(seq)) {
vm->fiber->error = newString(vm, "Number is not iterable.");
} else {
UNREACHABLE();
}
*value = VAR_NULL;
return false;
}
Object* obj = AS_OBJ(seq);
uint32_t iter = 0; //< Nth iteration.
if (IS_NUM(*iterator)) {
iter = _AS_INTEGER(*iterator);
}
switch (obj->type) {
case OBJ_STRING: {
// TODO: // Need to consider utf8.
String* str = ((String*)obj);
if (iter >= str->length) {
return false; //< Stop iteration.
}
// TODO: Or I could add char as a type for efficiency.
*value = VAR_OBJ(newStringLength(vm, str->data + iter, 1));
*iterator = VAR_NUM((double)iter + 1);
return true;
}
case OBJ_LIST: {
VarBuffer* elems = &((List*)obj)->elements;
if (iter >= elems->count) {
return false; //< Stop iteration.
}
*value = elems->data[iter];
*iterator = VAR_NUM((double)iter + 1);
return true;
}
case OBJ_MAP: {
Map* map = (Map*)obj;
// Find the next valid key.
for (; iter < map->capacity; iter++) {
if (!IS_UNDEF(map->entries[iter].key)) {
break;
}
}
if (iter >= map->capacity) {
return false; //< Stop iteration.
}
*value = map->entries[iter].key;
*iterator = VAR_NUM((double)iter + 1);
return true;
}
case OBJ_RANGE: {
double from = ((Range*)obj)->from;
double to = ((Range*)obj)->to;
if (from == to) return false;
double current;
if (from <= to) { //< Straight range.
current = from + (double)iter;
} else { //< Reversed range.
current = from - (double)iter;
}
if (current == to) return false;
*value = VAR_NUM(current);
*iterator = VAR_NUM((double)iter + 1);
return true;
}
case OBJ_SCRIPT:
case OBJ_FUNC:
case OBJ_FIBER:
case OBJ_USER:
TODO;
break;
default:
UNREACHABLE();
}
CHECK_MISSING_OBJ_TYPE(7);
UNREACHABLE();
return false;
}

View File

@ -43,11 +43,5 @@ void varSetAttrib(PKVM* vm, Var on, String* name, Var value);
Var varGetSubscript(PKVM* vm, Var on, Var key); Var varGetSubscript(PKVM* vm, Var on, Var key);
void varsetSubscript(PKVM* vm, Var on, Var key, Var value); void varsetSubscript(PKVM* vm, Var on, Var key, Var value);
// Functions //////////////////////////////////////////////////////////////////
// Parameter [iterator] should be VAR_NULL before starting the iteration.
// If an element is obtained by iteration it'll return true otherwise returns
// false indicating that the iteration is over.
bool varIterate(PKVM* vm, Var seq, Var* iterator, Var* value);
#endif // CORE_H #endif // CORE_H

View File

@ -3,10 +3,10 @@
* Licensed under: MIT License * Licensed under: MIT License
*/ */
#include <stdio.h>
#include "core.h"
#include "debug.h" #include "debug.h"
#include <stdio.h>
#include "core.h"
#include "vm.h" #include "vm.h"
// To limit maximum elements to be dumpin in a map or a list. // To limit maximum elements to be dumpin in a map or a list.
@ -167,7 +167,7 @@ void dumpFunctionCode(PKVM* vm, Function* func) {
} }
case OP_PUSH_NULL: case OP_PUSH_NULL:
case OP_PUSH_SELF: case OP_PUSH_0:
case OP_PUSH_TRUE: case OP_PUSH_TRUE:
case OP_PUSH_FALSE: case OP_PUSH_FALSE:
case OP_SWAP: case OP_SWAP:
@ -251,6 +251,8 @@ void dumpFunctionCode(PKVM* vm, Function* func) {
printf("%5d (argc)\n", READ_SHORT()); printf("%5d (argc)\n", READ_SHORT());
break; break;
case OP_ITER_TEST: NO_ARGS(); break;
case OP_ITER: case OP_ITER:
case OP_JUMP: case OP_JUMP:
case OP_JUMP_IF: case OP_JUMP_IF:

View File

@ -14,7 +14,6 @@ void dumpValue(PKVM* vm, Var value);
// Dump opcodes of the given function. // Dump opcodes of the given function.
void dumpFunctionCode(PKVM* vm, Function* func); void dumpFunctionCode(PKVM* vm, Function* func);
// Dump the all the global values of the script. // Dump the all the global values of the script.
void dumpGlobalValues(PKVM* vm); void dumpGlobalValues(PKVM* vm);

View File

@ -10,8 +10,8 @@
extern "C" { extern "C" {
#endif #endif
#include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
/*****************************************************************************/ /*****************************************************************************/
@ -26,7 +26,7 @@ extern "C" {
#define PK_VERSION_MINOR 1 #define PK_VERSION_MINOR 1
#define PK_VERSION_PATCH 0 #define PK_VERSION_PATCH 0
// String representation of the value. // String representation of the version.
#define PK_VERSION_STRING "0.1.0" #define PK_VERSION_STRING "0.1.0"
// Pocketlang visibility macros. define PK_DLL for using pocketlang as a // Pocketlang visibility macros. define PK_DLL for using pocketlang as a
@ -54,18 +54,18 @@ extern "C" {
#define PK_PUBLIC #define PK_PUBLIC
#endif #endif
// A convinent macro to define documentation of funcions. Use it to document
// your native functions.
//
// PK_DOC(foo, "The function will print 'foo' on the console.");
// void foo(PKVM* vm) { printf("foo\n"); }
//
#define PK_DOC(func, doc) char __pkdoc__##func[] = doc
/*****************************************************************************/ /*****************************************************************************/
/* POCKETLANG TYPES */ /* POCKETLANG TYPES */
/*****************************************************************************/ /*****************************************************************************/
// Nan-Tagging could be disable for debugging/portability purposes only when
// compiling the compiler. Do not change this if using the pocketlang library
// for embedding. To disable when compiling the compiler, define
// `VAR_NAN_TAGGING 0`, otherwise it defaults to Nan-Tagging.
#ifndef VAR_NAN_TAGGING
#define VAR_NAN_TAGGING 1
#endif
// PocketLang Virtual Machine. It'll contain the state of the execution, stack, // PocketLang Virtual Machine. It'll contain the state of the execution, stack,
// heap, and manage memory allocations. // heap, and manage memory allocations.
typedef struct PKVM PKVM; typedef struct PKVM PKVM;

View File

@ -20,8 +20,8 @@ OPCODE(PUSH_CONSTANT, 2, 1)
// Push null on the stack. // Push null on the stack.
OPCODE(PUSH_NULL, 0, 1) OPCODE(PUSH_NULL, 0, 1)
// Push self on the stack. If the runtime don't have self it'll push null. // Push number 0 on the stack.
OPCODE(PUSH_SELF, 0, 1) OPCODE(PUSH_0, 0, 1)
// Push true on the stack. // Push true on the stack.
OPCODE(PUSH_TRUE, 0, 1) OPCODE(PUSH_TRUE, 0, 1)
@ -96,34 +96,27 @@ OPCODE(PUSH_BUILTIN_FN, 2, 1)
// Pop the stack top. // Pop the stack top.
OPCODE(POP, 0, -1) OPCODE(POP, 0, -1)
// Calls a function using stack's top N values as the arguments and once it
// done the stack top should be stored otherwise it'll be disregarded. The
// function should set the 0 th argment to return value. Locals at 0 to 8
// marked explicitly since it's performance criticle.
// params: n bytes argc.
// Pop the path from the stack, import the module at the path and push the // Pop the path from the stack, import the module at the path and push the
// script in the script. If the script is imported for the first time (not // script in the script. If the script is imported for the first time (not
// cached) the script's body will be executed. // cached) the script's body will be executed.
OPCODE(IMPORT, 0, 0) OPCODE(IMPORT, 0, 0)
// TODO: may be later. // Calls a function using stack's top N values as the arguments and once it
//OPCODE(CALL_0, 0, 0) //< Push null call null will be the return value. // done the stack top should be stored otherwise it'll be disregarded. The
//OPCODE(CALL_1, 0, -1) //< Push null and arg1. arg1 will be popped. // function should set the 0 th argment to return value.
//OPCODE(CALL_2, 0, -2) //< And so on. // params: n bytes argc.
//OPCODE(CALL_3, 0, -3)
//OPCODE(CALL_4, 0, -4)
//OPCODE(CALL_5, 0, -5)
//OPCODE(CALL_6, 0, -6)
//OPCODE(CALL_7, 0, -7)
//OPCODE(CALL_8, 0, -8)
OPCODE(CALL, 2, -0) //< Will calculated at compile time. OPCODE(CALL, 2, -0) //< Will calculated at compile time.
// Starts the iteration and test the sequence if it's iterable, before the
// iteration instead of checking it everytime.
OPCODE(ITER_TEST, 0, 0)
// The stack top will be iteration value, next one is iterator (integer) and // The stack top will be iteration value, next one is iterator (integer) and
// next would be the container. It'll update those values but not push or pop // next would be the container. It'll update those values but not push or pop
// any values. We need to ensure that stack state at the point. // any values. We need to ensure that stack state at the point.
// param: 1 byte iterate type (will be set by ITER_TEST at runtime).
// param: 2 bytes jump offset if the iteration should stop. // param: 2 bytes jump offset if the iteration should stop.
OPCODE(ITER, 2, 0) OPCODE(ITER, 3, 0)
// The address offset to jump to. It'll add the offset to ip. // The address offset to jump to. It'll add the offset to ip.
// param: 2 bytes jump address offset. // param: 2 bytes jump address offset.
@ -195,8 +188,6 @@ OPCODE(GTEQ, 0, -1)
OPCODE(RANGE, 0, -1) //< Pop 2 integer make range push. OPCODE(RANGE, 0, -1) //< Pop 2 integer make range push.
OPCODE(IN, 0, -1) OPCODE(IN, 0, -1)
// TODO: literal list, map
// A sudo instruction which will never be called. A function's last opcode // A sudo instruction which will never be called. A function's last opcode
// used for debugging. // used for debugging.
OPCODE(END, 0, 0) OPCODE(END, 0, 0)

View File

@ -25,6 +25,13 @@ bool utilIsDigit(char c) {
return ('0' <= c && c <= '9'); return ('0' <= c && c <= '9');
} }
// A union to reinterpret a double as raw bits and back.
typedef union {
uint64_t bits64;
uint32_t bits32[2];
double num;
} _DoubleBitsConv;
uint64_t utilDoubleToBits(double value) { uint64_t utilDoubleToBits(double value) {
_DoubleBitsConv bits; _DoubleBitsConv bits;
bits.num = value; bits.num = value;

View File

@ -18,13 +18,6 @@ bool utilIsName(char c);
// Returns true if `c` is [0-9]. // Returns true if `c` is [0-9].
bool utilIsDigit(char c); bool utilIsDigit(char c);
// A union to reinterpret a double as raw bits and back.
typedef union {
uint64_t bits64;
uint32_t bits32[2];
double num;
} _DoubleBitsConv;
// Return Reinterpreted bits of the double value. // Return Reinterpreted bits of the double value.
uint64_t utilDoubleToBits(double value); uint64_t utilDoubleToBits(double value);

228
src/var.c
View File

@ -3,10 +3,10 @@
* Licensed under: MIT License * Licensed under: MIT License
*/ */
#include <stdio.h>
#include "utils.h"
#include "var.h" #include "var.h"
#include <stdio.h>
#include "utils.h"
#include "vm.h" #include "vm.h"
/*****************************************************************************/ /*****************************************************************************/
@ -257,6 +257,7 @@ static String* _allocateString(PKVM* vm, size_t length) {
varInitObject(&string->_super, vm, OBJ_STRING); varInitObject(&string->_super, vm, OBJ_STRING);
string->length = (uint32_t)length; string->length = (uint32_t)length;
string->data[length] = '\0'; string->data[length] = '\0';
string->capacity = (uint32_t)(length + 1);
return string; return string;
} }
@ -274,12 +275,14 @@ String* newStringLength(PKVM* vm, const char* text, uint32_t length) {
List* newList(PKVM* vm, uint32_t size) { List* newList(PKVM* vm, uint32_t size) {
List* list = ALLOCATE(vm, List); List* list = ALLOCATE(vm, List);
vmPushTempRef(vm, &list->_super);
varInitObject(&list->_super, vm, OBJ_LIST); varInitObject(&list->_super, vm, OBJ_LIST);
varBufferInit(&list->elements); varBufferInit(&list->elements);
if (size > 0) { if (size > 0) {
varBufferFill(&list->elements, vm, VAR_NULL, size); varBufferFill(&list->elements, vm, VAR_NULL, size);
list->elements.count = 0; list->elements.count = 0;
} }
vmPopTempRef(vm);
return list; return list;
} }
@ -760,140 +763,192 @@ bool isObjectHashable(ObjectType type) {
return type != OBJ_LIST && type != OBJ_MAP; return type != OBJ_LIST && type != OBJ_MAP;
} }
String* toString(PKVM* vm, Var v, bool recursive) { // This will prevent recursive list/map from crash when calling to_string, by
// checking if the current sequence is in the outer sequence linked list.
struct OuterSequence {
struct OuterSequence* outer;
// If false it'll be map. If we have multiple sequence we should use an enum
// here but we only ever support list and map as builtin sequence (thus bool).
bool is_list;
union {
List* list;
Map* map;
};
};
typedef struct OuterSequence OuterSequence;
static void toStringInternal(PKVM* vm, Var v, ByteBuffer* buff,
OuterSequence* outer) {
if (IS_NULL(v)) { if (IS_NULL(v)) {
return newStringLength(vm, "null", 4); ByteBufferAddString(buff, vm, "null", 4);
return;
} else if (IS_BOOL(v)) { } else if (IS_BOOL(v)) {
if (AS_BOOL(v)) { if (AS_BOOL(v)) ByteBufferAddString(buff, vm, "true", 4);
return newStringLength(vm, "true", 4); else ByteBufferAddString(buff, vm, "false", 5);
} else { return;
return newStringLength(vm, "false", 5);
}
} else if (IS_NUM(v)) { } else if (IS_NUM(v)) {
char buff[TO_STRING_BUFF_SIZE]; char num_buff[TO_STRING_BUFF_SIZE];
int length = sprintf(buff, "%.14g", AS_NUM(v)); int length = sprintf(num_buff, "%.14g", AS_NUM(v));
ASSERT(length < TO_STRING_BUFF_SIZE, "Buffer overflowed."); __ASSERT(length < TO_STRING_BUFF_SIZE, "Buffer overflowed.");
return newStringLength(vm, buff, length); ByteBufferAddString(buff, vm, num_buff, length); return;
} else if (IS_OBJ(v)) { } else if (IS_OBJ(v)) {
Object* obj = AS_OBJ(v); Object* obj = AS_OBJ(v);
switch (obj->type) { switch (obj->type) {
case OBJ_STRING: case OBJ_STRING:
{ {
String* str = (String*)obj;
if (outer == NULL) {
ByteBufferAddString(buff, vm, str->data, str->length);
return;
}
// If recursive return with quotes (ex: [42, "hello", 0..10]). // If recursive return with quotes (ex: [42, "hello", 0..10]).
String* string = newStringLength(vm, ((String*)obj)->data, ((String*)obj)->length); byteBufferWrite(buff, vm, '"');
if (!recursive) return string; ByteBufferAddString(buff, vm, str->data, str->length);
vmPushTempRef(vm, &string->_super); byteBufferWrite(buff, vm, '"');
String* repr = stringFormat(vm, "\"@\"", string); return;
vmPopTempRef(vm);
return repr;
} }
case OBJ_LIST: case OBJ_LIST:
{ {
List* list = (List*)obj; List* list = (List*)obj;
if (list->elements.count == 0) return newStringLength(vm, "[]", 2); if (list->elements.count == 0) {
ByteBufferAddString(buff, vm, "[]", 2);
String* result = newStringLength(vm, "[", 1); return;
vmPushTempRef(vm, &result->_super); // result
for (uint32_t i = 0; i < list->elements.count; i++) {
const char* fmt = (i != 0) ? "@, @" : "@@";
String* elem_str = toString(vm, list->elements.data[i], true);
vmPushTempRef(vm, &elem_str->_super); // elem_str
result = stringFormat(vm, fmt, result, elem_str);
vmPopTempRef(vm); // elem_str
vmPopTempRef(vm); // result (old pointer)
vmPushTempRef(vm, &result->_super); // result (old pointer)
} }
result = stringFormat(vm, "@]", result); // Check if the list is recursive.
vmPopTempRef(vm); // result (last pointer) OuterSequence* seq = outer;
return result; while (seq != NULL) {
if (seq->is_list) {
if (seq->list == list) {
ByteBufferAddString(buff, vm, "[...]", 5);
return;
}
}
seq = seq->outer;
}
OuterSequence seq_list;
seq_list.outer = outer; seq_list.is_list = true; seq_list.list = list;
byteBufferWrite(buff, vm, '[');
for (uint32_t i = 0; i < list->elements.count; i++) {
if (i != 0) ByteBufferAddString(buff, vm, ", ", 2);
toStringInternal(vm, list->elements.data[i], buff, &seq_list);
}
byteBufferWrite(buff, vm, ']');
return;
} }
case OBJ_MAP: case OBJ_MAP:
{ {
Map* map = (Map*)obj; Map* map = (Map*)obj;
if (map->entries == NULL) return newStringLength(vm, "{}", 2); if (map->entries == NULL) {
ByteBufferAddString(buff, vm, "{}", 2);
return;
}
String* result = newStringLength(vm, "{", 1); // Check if the map is recursive.
vmPushTempRef(vm, &result->_super); // result OuterSequence* seq = outer;
while (seq != NULL) {
if (!seq->is_list) {
if (seq->map == map) {
ByteBufferAddString(buff, vm, "{...}", 5);
return;
}
}
seq = seq->outer;
}
OuterSequence seq_map;
seq_map.outer = outer; seq_map.is_list = false; seq_map.map = map;
uint32_t i = 0; byteBufferWrite(buff, vm, '{');
uint32_t i = 0; // Index of the current entry to iterate.
bool _first = true; // For first element no ',' required. bool _first = true; // For first element no ',' required.
do { do {
// Get the next valid key index. // Get the next valid key index.
bool _done = false; //< To break from inner loop. bool _done = false; //< To break from inner loop if we don't have anymore keys.
while (IS_UNDEF(map->entries[i].key)) { while (IS_UNDEF(map->entries[i].key)) {
i++; if (++i >= map->capacity) {
if (i >= map->capacity) {
_done = true; _done = true;
break; break;
} }
} }
if (_done) break; if (_done) break;
const char* fmt = (!_first) ? "@, @:@" : "@@:@"; if (!_first) {
ByteBufferAddString(buff, vm, ", ", 2);
String* key_str = toString(vm, map->entries[i].key, true); _first = false;
vmPushTempRef(vm, &key_str->_super); // key_str }
toStringInternal(vm, map->entries[i].key, buff, &seq_map);
String* val_str = toString(vm, map->entries[i].value, true); byteBufferWrite(buff, vm, ':');
vmPushTempRef(vm, &val_str->_super); // val_str toStringInternal(vm, map->entries[i].value, buff, &seq_map);
result = stringFormat(vm, fmt, result, key_str, val_str);
vmPopTempRef(vm); // val_str
vmPopTempRef(vm); // key_str
vmPopTempRef(vm); // result (old pointer)
vmPushTempRef(vm, &result->_super); // result (new pointer)
_first = false;
i++; i++;
} while (i < map->capacity); } while (i < map->capacity);
byteBufferWrite(buff, vm, '}');
result = stringFormat(vm, "@}", result); return;
vmPopTempRef(vm); // result (last pointer)
return result;
} }
case OBJ_RANGE: case OBJ_RANGE:
{ {
Range* range = (Range*)obj; Range* range = (Range*)obj;
// FIXME: Validate I might need some review on the below one. char buff_from[STR_NUM_BUFF_SIZE];
char buff_from[50]; int len_from = snprintf(buff_from, sizeof(buff_from), "%f", range->from);
snprintf(buff_from, 50, "%f", range->from); char buff_to[STR_NUM_BUFF_SIZE];
char buff_to[50]; int len_to = snprintf(buff_to, sizeof(buff_to), "%f", range->to);
snprintf(buff_to, 50, "%f", range->to); ByteBufferAddString(buff, vm, "[Range:", 7);
ByteBufferAddString(buff, vm, buff_from, len_from);
return stringFormat(vm, "[Range:$..$]", buff_from, buff_to); ByteBufferAddString(buff, vm, "..", 2);
ByteBufferAddString(buff, vm, buff_to, len_to);
byteBufferWrite(buff, vm, ']');
return;
} }
case OBJ_SCRIPT: { case OBJ_SCRIPT: {
Script* scr = ((Script*)obj); Script* scr = ((Script*)obj);
ByteBufferAddString(buff, vm, "[Module:", 8);
if (scr->moudle != NULL) { if (scr->moudle != NULL) {
return stringFormat(vm, "[Lib:@]", scr->moudle); ByteBufferAddString(buff, vm, scr->moudle->data, scr->moudle->length);
} else { } else {
return stringFormat(vm, "[Lib:\"@\"]", scr->path); byteBufferWrite(buff, vm, '"');
ByteBufferAddString(buff, vm, scr->path->data, scr->path->length);
byteBufferWrite(buff, vm, '"');
} }
byteBufferWrite(buff, vm, ']');
return;
} }
case OBJ_FUNC: return stringFormat(vm, "[Func:$]", ((Function*)obj)->name);
case OBJ_FIBER: return newStringLength(vm, "[Fiber]", 7); // TODO; case OBJ_FUNC: {
case OBJ_USER: return newStringLength(vm, "[UserObj]", 9); // TODO; Function* func = (Function*)obj;
ByteBufferAddString(buff, vm, "[Func:", 6);
ByteBufferAddString(buff, vm, func->name, (uint32_t)strlen(func->name));
byteBufferWrite(buff, vm, ']');
return;
}
// TODO: Maybe add address with %p.
case OBJ_FIBER: ByteBufferAddString(buff, vm, "[Fiber]", 7); return;
case OBJ_USER: ByteBufferAddString(buff, vm, "[UserObj]", 9); return;
break; break;
} }
} }
UNREACHABLE(); UNREACHABLE();
return NULL; return;
}
String* toString(PKVM* vm, Var v) {
ByteBuffer buff;
byteBufferInit(&buff);
toStringInternal(vm, v, &buff, NULL);
return newStringLength(vm, (const char*)buff.data, buff.count);
} }
bool toBool(Var v) { bool toBool(Var v) {
@ -976,6 +1031,23 @@ String* stringFormat(PKVM* vm, const char* fmt, ...) {
return result; return result;
} }
String* stringJoin(PKVM* vm, String* str1, String* str2) {
// Optimize end case.
if (str1->length == 0) return str2;
if (str2->length == 0) return str1;
size_t length = (size_t)str1->length + (size_t)str2->length;
String* string = _allocateString(vm, length);
memcpy(string->data, str1->data, str1->length);
memcpy(string->data + str1->length, str2->data, str2->length);
// Null byte already existed. From _allocateString.
string->hash = utilHashString(string->data);
return string;
}
uint32_t scriptAddName(Script* self, PKVM* vm, const char* name, uint32_t scriptAddName(Script* self, PKVM* vm, const char* name,
uint32_t length) { uint32_t length) {

View File

@ -7,29 +7,19 @@
#define VAR_H #define VAR_H
/** @file /** @file
* A simple single header dynamic type system library for small dynamic typed * A simple dynamic type system library for small dynamic typed languages using
* languages using a technique called NaN-tagging (optional). The method is * a technique called NaN-tagging (optional). The method is inspired from the
* inspired from the wren (https://wren.io/) an awsome language written by the * wren (https://wren.io/) an awsome language written by Bob Nystrom the author
* author of "Crafting Interpreters" Bob Nystrom and it's contrbuters. * of "Crafting Interpreters" and it's contrbuters.
* Reference: * Reference:
* https://github.com/wren-lang/wren/blob/main/src/vm/wren_value.h * https://github.com/wren-lang/wren/blob/main/src/vm/wren_value.h
* https://leonardschuetz.ch/blog/nan-boxing/ * https://leonardschuetz.ch/blog/nan-boxing/
* *
* The previous implementation was to add a type field to every \ref var * The previous implementation was to add a type field to every var and use
* and use smart pointers(C++17) to object with custom destructors, * smart pointers(C++17) to object with custom destructors, which makes the
* which makes the programme in effect for small types such null, bool, * programme inefficient for small types such null, bool, int and float.
* int and float.
*/ */
/** __STDC_LIMIT_MACROS and __STDC_CONSTANT_MACROS are a workaround to
* allow C++ programs to use stdint.h macros specified in the C99
* standard that aren't in the C++ standard */
#define __STDC_LIMIT_MACROS
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "common.h" #include "common.h"
#include "buffers.h" #include "buffers.h"
@ -205,7 +195,7 @@ typedef enum {
struct Object { struct Object {
ObjectType type; //< Type of the object in \ref var_Object_Type. ObjectType type; //< Type of the object in \ref var_Object_Type.
bool is_marked; //< Marked when garbage collection's marking phase. bool is_marked; //< Marked when garbage collection's marking phase.
//Class* is; //< The class the object IS. // No OOP in PK (yet?). //Class* is; //< The class the object IS. // No OOP in PK.
Object* next; //< Next object in the heap allocated link list. Object* next; //< Next object in the heap allocated link list.
}; };
@ -267,6 +257,10 @@ struct Script {
functions: [ fn1, fn2 ] functions: [ fn1, fn2 ]
0 1 0 1
*/ */
// TODO: (maybe) join the function buffer and variable buffers.
// and make if possible to override functions. (also have to allow builtin).
VarBuffer globals; //< Script level global variables. VarBuffer globals; //< Script level global variables.
UintBuffer global_names; //< Name map to index in globals. UintBuffer global_names; //< Name map to index in globals.
VarBuffer literals; //< Script literal constant values. VarBuffer literals; //< Script literal constant values.
@ -451,9 +445,8 @@ uint32_t varHashValue(Var v);
// Return true if the object type is hashable. // Return true if the object type is hashable.
bool isObjectHashable(ObjectType type); bool isObjectHashable(ObjectType type);
// Returns the string version of the value. Note: pass false as [_recursive] // Returns the string version of the value.
// It's for internal use (or may be I could make a wrapper around). String* toString(PKVM* vm, Var v);
String* toString(PKVM* vm, Var v, bool _recursive);
// Returns the truthy value of the var. // Returns the truthy value of the var.
bool toBool(Var v); bool toBool(Var v);
@ -464,6 +457,10 @@ bool toBool(Var v);
// @ - a String object // @ - a String object
String* stringFormat(PKVM* vm, const char* fmt, ...); String* stringFormat(PKVM* vm, const char* fmt, ...);
// Create a new string by joining the 2 given string and return the result.
// Which would be faster than using "@@" format.
String* stringJoin(PKVM* vm, String* str1, String* str2);
// Add the name (string literal) to the string buffer if not already exists and // Add the name (string literal) to the string buffer if not already exists and
// return it's index in the buffer. // return it's index in the buffer.
uint32_t scriptAddName(Script* self, PKVM* vm, const char* name, uint32_t scriptAddName(Script* self, PKVM* vm, const char* name,

246
src/vm.c
View File

@ -5,6 +5,7 @@
#include "vm.h" #include "vm.h"
#include <math.h>
#include "core.h" #include "core.h"
#include "utils.h" #include "utils.h"
@ -12,6 +13,7 @@
#include "debug.h" //< Wrap around debug macro. #include "debug.h" //< Wrap around debug macro.
#endif #endif
// Evaluvated to true if a runtime error set on the current fiber.
#define HAS_ERROR() (vm->fiber->error != NULL) #define HAS_ERROR() (vm->fiber->error != NULL)
// Initially allocated call frame capacity. Will grow dynamically. // Initially allocated call frame capacity. Will grow dynamically.
@ -31,7 +33,9 @@
// allocated so far plus the fill factor of it. // allocated so far plus the fill factor of it.
#define HEAP_FILL_PERCENT 75 #define HEAP_FILL_PERCENT 75
static void* defaultRealloc(void* memory, size_t new_size, void* user_data) { // The default allocator that will be used to initialize the vm's configuration
// if the host doesn't provided any allocators for us.
static forceinline void* defaultRealloc(void* memory, size_t new_size, void* user_data) {
if (new_size == 0) { if (new_size == 0) {
free(memory); free(memory);
return NULL; return NULL;
@ -82,7 +86,7 @@ PKVM* pkNewVM(pkConfiguration* config) {
vm->gray_list_count = 0; vm->gray_list_count = 0;
vm->gray_list_capacity = MIN_CAPACITY; vm->gray_list_capacity = MIN_CAPACITY;
vm->gray_list = (Object**)vm->config.realloc_fn( vm->gray_list = (Object**)vm->config.realloc_fn(
NULL, sizeof(Object*) * vm->gray_list_capacity, NULL); NULL, sizeof(Object*) * vm->gray_list_capacity, NULL);
vm->next_gc = INITIAL_GC_SIZE; vm->next_gc = INITIAL_GC_SIZE;
vm->min_heap_size = MIN_HEAP_SIZE; vm->min_heap_size = MIN_HEAP_SIZE;
vm->heap_fill_percent = HEAP_FILL_PERCENT; vm->heap_fill_percent = HEAP_FILL_PERCENT;
@ -142,18 +146,6 @@ void pkReleaseHandle(PKVM* vm, PkHandle* handle) {
/* VM INTERNALS */ /* VM INTERNALS */
/*****************************************************************************/ /*****************************************************************************/
void vmPushTempRef(PKVM* self, Object* obj) {
ASSERT(obj != NULL, "Cannot reference to NULL.");
ASSERT(self->temp_reference_count < MAX_TEMP_REFERENCE,
"Too many temp references");
self->temp_reference[self->temp_reference_count++] = obj;
}
void vmPopTempRef(PKVM* self) {
ASSERT(self->temp_reference_count > 0, "Temporary reference is empty to pop.");
self->temp_reference_count--;
}
PkHandle* vmNewHandle(PKVM* self, Var value) { PkHandle* vmNewHandle(PKVM* self, Var value) {
PkHandle* handle = (PkHandle*)ALLOCATE(self, PkHandle); PkHandle* handle = (PkHandle*)ALLOCATE(self, PkHandle);
handle->value = value; handle->value = value;
@ -242,7 +234,7 @@ void pkSetUserData(PKVM* vm, void* user_data) {
vm->config.user_data = user_data; vm->config.user_data = user_data;
} }
static Script* getScript(PKVM* vm, String* path) { static forceinline Script* getScript(PKVM* vm, String* path) {
Var scr = mapGet(vm->scripts, VAR_OBJ(&path->_super)); Var scr = mapGet(vm->scripts, VAR_OBJ(&path->_super));
if (IS_UNDEF(scr)) return NULL; if (IS_UNDEF(scr)) return NULL;
ASSERT(AS_OBJ(scr)->type == OBJ_SCRIPT, OOPS); ASSERT(AS_OBJ(scr)->type == OBJ_SCRIPT, OOPS);
@ -257,7 +249,7 @@ static Script* getScript(PKVM* vm, String* path) {
// to the string which is the path that has to be resolved and once it resolved // to the string which is the path that has to be resolved and once it resolved
// the provided result's string's on_done() will be called and, it's string will // the provided result's string's on_done() will be called and, it's string will
// be updated with the new resolved path string. // be updated with the new resolved path string.
static bool resolveScriptPath(PKVM* vm, pkStringPtr* path_string) { static forceinline bool resolveScriptPath(PKVM* vm, pkStringPtr* path_string) {
if (vm->config.resolve_path_fn == NULL) return true; if (vm->config.resolve_path_fn == NULL) return true;
const char* path = path_string->string; const char* path = path_string->string;
@ -282,7 +274,7 @@ static bool resolveScriptPath(PKVM* vm, pkStringPtr* path_string) {
// Import and return Script object as Var. If the script is imported and // Import and return Script object as Var. If the script is imported and
// compiled here it'll set [is_new_script] to true oterwise (using the cached // compiled here it'll set [is_new_script] to true oterwise (using the cached
// script) set to false. // script) set to false.
static Var importScript(PKVM* vm, String* path_name) { static forceinline Var importScript(PKVM* vm, String* path_name) {
// Check in the core libs. // Check in the core libs.
Script* scr = getCoreLib(vm, path_name); Script* scr = getCoreLib(vm, path_name);
@ -301,14 +293,12 @@ static Var importScript(PKVM* vm, String* path_name) {
return VAR_NULL; return VAR_NULL;
} }
static void ensureStackSize(PKVM* vm, int size) { static forceinline void growStack(PKVM* vm, int size) {
Fiber* fiber = vm->fiber; Fiber* fiber = vm->fiber;
ASSERT(fiber->stack_size <= size, OOPS);
if (fiber->stack_size > size) return;
int new_size = utilPowerOf2Ceil(size); int new_size = utilPowerOf2Ceil(size);
Var* old_rbp = fiber->stack; //< Old stack base pointer. Var* old_rbp = fiber->stack; //< Old stack base pointer.
fiber->stack = (Var*)vmRealloc(vm, fiber->stack, fiber->stack = (Var*)vmRealloc(vm, fiber->stack,
sizeof(Var) * fiber->stack_size, sizeof(Var) * fiber->stack_size,
sizeof(Var) * new_size); sizeof(Var) * new_size);
@ -337,39 +327,37 @@ static void ensureStackSize(PKVM* vm, int size) {
= fiber->stack + ( old_ptr - old_rbp ) */ = fiber->stack + ( old_ptr - old_rbp ) */
#define MAP_PTR(old_ptr) (fiber->stack + ((old_ptr) - old_rbp)) #define MAP_PTR(old_ptr) (fiber->stack + ((old_ptr) - old_rbp))
// Update the stack top pointer. // Update the stack top pointer and the return pointer.
fiber->sp = MAP_PTR(fiber->sp); fiber->sp = MAP_PTR(fiber->sp);
fiber->ret = MAP_PTR(fiber->ret);
// Update the stack base pointer of the call frames. // Update the stack base pointer of the call frames.
for (int i = 0; i < fiber->frame_capacity; i++) { for (int i = 0; i < fiber->frame_count; i++) {
CallFrame* frame = fiber->frames + i; CallFrame* frame = fiber->frames + i;
frame->rbp = MAP_PTR(frame->rbp); frame->rbp = MAP_PTR(frame->rbp);
} }
} }
static inline void pushCallFrame(PKVM* vm, Function* fn) { static forceinline void pushCallFrame(PKVM* vm, Function* fn) {
ASSERT(!fn->is_native, "Native function shouldn't use call frames."); ASSERT(!fn->is_native, "Native function shouldn't use call frames.");
// Grow the stack frame if needed. /* Grow the stack frame if needed. */
if (vm->fiber->frame_count + 1 > vm->fiber->frame_capacity) { if (vm->fiber->frame_count + 1 > vm->fiber->frame_capacity) {
int new_capacity = vm->fiber->frame_capacity * 2; int new_capacity = vm->fiber->frame_capacity << 1;
vm->fiber->frames = (CallFrame*)vmRealloc(vm, vm->fiber->frames, vm->fiber->frames = (CallFrame*)vmRealloc(vm, vm->fiber->frames,
sizeof(CallFrame) * vm->fiber->frame_capacity, sizeof(CallFrame) * vm->fiber->frame_capacity,
sizeof(CallFrame) * new_capacity); sizeof(CallFrame) * new_capacity);
vm->fiber->frame_capacity = new_capacity; vm->fiber->frame_capacity = new_capacity;
} }
// Grow the stack if needed. /* Grow the stack if needed. */
int stack_size = (int)(vm->fiber->sp - vm->fiber->stack); int needed = fn->fn->stack_size + (int)(vm->fiber->sp - vm->fiber->stack);
int needed = stack_size + fn->fn->stack_size; if (vm->fiber->stack_size <= needed) growStack(vm, needed);
// TODO: set stack overflow maybe?.
ensureStackSize(vm, needed);
CallFrame* frame = &vm->fiber->frames[vm->fiber->frame_count++]; CallFrame* frame = vm->fiber->frames + vm->fiber->frame_count++;
frame->rbp = vm->fiber->ret; frame->rbp = vm->fiber->ret;
frame->fn = fn; frame->fn = fn;
frame->ip = fn->fn->opcodes.data; frame->ip = fn->fn->opcodes.data;
} }
void pkSetRuntimeError(PKVM* vm, const char* message) { void pkSetRuntimeError(PKVM* vm, const char* message) {
@ -397,7 +385,7 @@ void vmReportError(PKVM* vm) {
// This function is responsible to call on_done function if it's done with the // This function is responsible to call on_done function if it's done with the
// provided string pointers. // provided string pointers.
static PkInterpretResult interpretSource(PKVM* vm, pkStringPtr source, static forceinline PkInterpretResult interpretSource(PKVM* vm, pkStringPtr source,
pkStringPtr path) { pkStringPtr path) {
String* path_name = newString(vm, path.string); String* path_name = newString(vm, path.string);
if (path.on_done) path.on_done(vm, path); if (path.on_done) path.on_done(vm, path);
@ -543,19 +531,16 @@ PkInterpretResult vmRunScript(PKVM* vm, Script* _script) {
#define DEBUG_CALL_STACK() ((void*)0) #define DEBUG_CALL_STACK() ((void*)0)
#endif #endif
#define SWITCH(code) \ #define SWITCH() Opcode instruction; switch (instruction = (Opcode)READ_BYTE())
L_vm_main_loop: \
DEBUG_CALL_STACK(); \
switch (code = (Opcode)READ_BYTE())
#define OPCODE(code) case OP_##code #define OPCODE(code) case OP_##code
#define DISPATCH() goto L_vm_main_loop #define DISPATCH() goto L_vm_main_loop
PUSH(VAR_NULL); // Return value of the script body. PUSH(VAR_NULL); // Return value of the script body.
LOAD_FRAME(); LOAD_FRAME();
Opcode instruction; L_vm_main_loop:
SWITCH(instruction) { DEBUG_CALL_STACK();
SWITCH() {
OPCODE(PUSH_CONSTANT): OPCODE(PUSH_CONSTANT):
{ {
@ -569,6 +554,10 @@ PkInterpretResult vmRunScript(PKVM* vm, Script* _script) {
PUSH(VAR_NULL); PUSH(VAR_NULL);
DISPATCH(); DISPATCH();
OPCODE(PUSH_0):
PUSH(VAR_NUM(0));
DISPATCH();
OPCODE(PUSH_TRUE): OPCODE(PUSH_TRUE):
PUSH(VAR_TRUE); PUSH(VAR_TRUE);
DISPATCH(); DISPATCH();
@ -579,11 +568,9 @@ PkInterpretResult vmRunScript(PKVM* vm, Script* _script) {
OPCODE(SWAP): OPCODE(SWAP):
{ {
Var top1 = *(vm->fiber->sp - 1); Var tmp = *(vm->fiber->sp - 1);
Var top2 = *(vm->fiber->sp - 2); *(vm->fiber->sp - 1) = *(vm->fiber->sp - 2);
*(vm->fiber->sp - 2) = tmp;
*(vm->fiber->sp - 1) = top2;
*(vm->fiber->sp - 2) = top1;
DISPATCH(); DISPATCH();
} }
@ -621,15 +608,12 @@ PkInterpretResult vmRunScript(PKVM* vm, Script* _script) {
if (IS_OBJ(key) && !isObjectHashable(AS_OBJ(key)->type)) { if (IS_OBJ(key) && !isObjectHashable(AS_OBJ(key)->type)) {
RUNTIME_ERROR(stringFormat(vm, "$ type is not hashable.", varTypeName(key))); RUNTIME_ERROR(stringFormat(vm, "$ type is not hashable.", varTypeName(key)));
} else {
mapSet(vm, (Map*)AS_OBJ(on), key, value);
} }
varsetSubscript(vm, on, key, value); mapSet(vm, (Map*)AS_OBJ(on), key, value);
POP(); // value POP(); // value
POP(); // key POP(); // key
CHECK_ERROR();
DISPATCH(); DISPATCH();
} }
@ -702,7 +686,9 @@ PkInterpretResult vmRunScript(PKVM* vm, Script* _script) {
OPCODE(PUSH_BUILTIN_FN): OPCODE(PUSH_BUILTIN_FN):
{ {
Function* fn = getBuiltinFunction(vm, READ_SHORT()); uint16_t index = READ_SHORT();
ASSERT_INDEX(index, vm->builtins_count);
Function* fn = vm->builtins[index].fn;
PUSH(VAR_OBJ(&fn->_super)); PUSH(VAR_OBJ(&fn->_super));
DISPATCH(); DISPATCH();
} }
@ -727,12 +713,11 @@ PkInterpretResult vmRunScript(PKVM* vm, Script* _script) {
Var* callable = vm->fiber->sp - argc - 1; Var* callable = vm->fiber->sp - argc - 1;
if (IS_OBJ(*callable) && AS_OBJ(*callable)->type == OBJ_FUNC) { if (IS_OBJ(*callable) && AS_OBJ(*callable)->type == OBJ_FUNC) {
Function* fn = (Function*)AS_OBJ(*callable); Function* fn = (Function*)AS_OBJ(*callable);
// -1 argument means multiple number of args. // -1 argument means multiple number of args.
if (fn->arity != -1 && fn->arity != argc) { if (fn->arity != -1 && fn->arity != argc) {
String* arg_str = toString(vm, VAR_NUM(fn->arity), false); String* arg_str = toString(vm, VAR_NUM(fn->arity));
vmPushTempRef(vm, &arg_str->_super); vmPushTempRef(vm, &arg_str->_super);
String* msg = stringFormat(vm, "Expected excatly @ argument(s).", String* msg = stringFormat(vm, "Expected excatly @ argument(s).",
arg_str); arg_str);
@ -740,10 +725,6 @@ PkInterpretResult vmRunScript(PKVM* vm, Script* _script) {
RUNTIME_ERROR(msg); RUNTIME_ERROR(msg);
} }
// Now the function will never needed in the stack and it'll
// initialized with VAR_NULL as return value.
*callable = VAR_NULL;
// Next call frame starts here. (including return value). // Next call frame starts here. (including return value).
vm->fiber->ret = callable; vm->fiber->ret = callable;
@ -756,30 +737,125 @@ PkInterpretResult vmRunScript(PKVM* vm, Script* _script) {
// Pop function arguments except for the return value. // Pop function arguments except for the return value.
vm->fiber->sp = vm->fiber->ret + 1; vm->fiber->sp = vm->fiber->ret + 1;
CHECK_ERROR(); CHECK_ERROR();
DISPATCH(); //< This will save 2 jumps.
} else { } else {
pushCallFrame(vm, fn); pushCallFrame(vm, fn);
LOAD_FRAME(); //< Load the top frame to vm's execution variables. LOAD_FRAME(); //< Load the top frame to vm's execution variables.
DISPATCH(); //< This will save 2 jumps.
} }
} else { } else {
RUNTIME_ERROR(newString(vm, "Expected a function in call.")); RUNTIME_ERROR(newString(vm, "Expected a function in call."));
} }
DISPATCH();
}
OPCODE(ITER_TEST):
{
Var seq = PEEK(-3);
// Primitive types are not iterable.
if (!IS_OBJ(seq)) {
if (IS_NULL(seq)) {
RUNTIME_ERROR(newString(vm, "Null is not iterable."));
} else if (IS_BOOL(seq)) {
RUNTIME_ERROR(newString(vm, "Boolenan is not iterable."));
} else if (IS_NUM(seq)) {
RUNTIME_ERROR(newString(vm, "Number is not iterable."));
} else {
UNREACHABLE();
}
}
DISPATCH(); DISPATCH();
} }
OPCODE(ITER): OPCODE(ITER):
{ {
Var* iter_value = (vm->fiber->sp - 1); Var* value = (vm->fiber->sp - 1);
Var* iterator = (vm->fiber->sp - 2); Var* iterator = (vm->fiber->sp - 2);
Var* container = (vm->fiber->sp - 3); Var seq = PEEK(-3);
uint16_t jump_offset = READ_SHORT(); uint16_t jump_offset = READ_SHORT();
bool iterated = varIterate(vm, *container, iterator, iter_value); #define JUMP_ITER_EXIT() \
CHECK_ERROR(); do { \
if (!iterated) { IP += jump_offset; \
IP += jump_offset; DISPATCH(); \
} while (false)
ASSERT(IS_NUM(*iterator), OOPS);
double it = AS_NUM(*iterator); //< Nth iteration.
ASSERT(AS_NUM(*iterator) == (int32_t)trunc(it), OOPS);
Object* obj = AS_OBJ(seq);
switch (obj->type) {
case OBJ_STRING: {
uint32_t iter = (int32_t)trunc(it);
// TODO: // Need to consider utf8.
String* str = ((String*)obj);
if (iter >= str->length) JUMP_ITER_EXIT();
//TODO: vm's char (and reusable) strings.
*value = VAR_OBJ(newStringLength(vm, str->data + iter, 1));
*iterator = VAR_NUM((double)iter + 1);
} DISPATCH();
case OBJ_LIST: {
uint32_t iter = (int32_t)trunc(it);
VarBuffer* elems = &((List*)obj)->elements;
if (iter >= elems->count) JUMP_ITER_EXIT();
*value = elems->data[iter];
*iterator = VAR_NUM((double)iter + 1);
} DISPATCH();
case OBJ_MAP: {
uint32_t iter = (int32_t)trunc(it);
Map* map = (Map*)obj;
if (map->entries == NULL) JUMP_ITER_EXIT();
MapEntry* e = map->entries + iter;
for (; iter < map->capacity; iter++, e++) {
if (!IS_UNDEF(e->key)) JUMP_ITER_EXIT();
}
if (iter >= map->capacity) JUMP_ITER_EXIT();
*value = map->entries[iter].key;
*iterator = VAR_NUM((double)iter + 1);
} DISPATCH();
case OBJ_RANGE: {
double from = ((Range*)obj)->from;
double to = ((Range*)obj)->to;
if (from == to) JUMP_ITER_EXIT();
double current;
if (from <= to) { //< Straight range.
current = from + it;
} else { //< Reversed range.
current = from - it;
}
if (current == to) JUMP_ITER_EXIT();
*value = VAR_NUM(current);
*iterator = VAR_NUM(it + 1);
} DISPATCH();
case OBJ_SCRIPT:
case OBJ_FUNC:
case OBJ_FIBER:
case OBJ_USER:
TODO; break;
default:
UNREACHABLE();
} }
DISPATCH(); DISPATCH();
} }
@ -821,22 +897,17 @@ PkInterpretResult vmRunScript(PKVM* vm, Script* _script) {
{ {
// TODO: handle caller fiber. // TODO: handle caller fiber.
Var ret = POP(); // Set the return value.
*(frame->rbp) = POP();
// Pop the last frame. // Pop the last frame, and if no more call frames, we're done.
vm->fiber->frame_count--; if (--vm->fiber->frame_count == 0) {
// If no more call frames. We're done.
if (vm->fiber->frame_count == 0) {
vm->fiber->sp = vm->fiber->stack; vm->fiber->sp = vm->fiber->stack;
PUSH(ret);
return PK_RESULT_SUCCESS; return PK_RESULT_SUCCESS;
} }
// Set the return value. // Pop the params (locals should have popped at this point) and update
*(frame->rbp) = ret; // stack pointer.
// Pop the locals and update stack pointer.
vm->fiber->sp = frame->rbp + 1; // +1: rbp is returned value. vm->fiber->sp = frame->rbp + 1; // +1: rbp is returned value.
LOAD_FRAME(); LOAD_FRAME();
@ -979,7 +1050,7 @@ PkInterpretResult vmRunScript(PKVM* vm, Script* _script) {
{ {
// Don't pop yet, we need the reference for gc. // Don't pop yet, we need the reference for gc.
Var r = PEEK(-1), l = PEEK(-2); Var r = PEEK(-1), l = PEEK(-2);
Var value = varMultiply(vm, l, r); Var value = varDivide(vm, l, r);
POP(); POP(); // r, l POP(); POP(); // r, l
PUSH(value); PUSH(value);
@ -1004,8 +1075,9 @@ PkInterpretResult vmRunScript(PKVM* vm, Script* _script) {
OPCODE(BIT_XOR): OPCODE(BIT_XOR):
OPCODE(BIT_LSHIFT): OPCODE(BIT_LSHIFT):
OPCODE(BIT_RSHIFT): OPCODE(BIT_RSHIFT):
TODO;
OPCODE(EQEQ) : OPCODE(EQEQ):
{ {
Var r = POP(), l = POP(); Var r = POP(), l = POP();
PUSH(VAR_BOOL(isValuesEqual(l, r))); PUSH(VAR_BOOL(isValuesEqual(l, r)));

View File

@ -103,6 +103,20 @@ struct PKVM {
Fiber* fiber; Fiber* fiber;
}; };
// Push the object to temporary references stack.
forceinline void vmPushTempRef(PKVM* self, Object* obj) {
ASSERT(obj != NULL, "Cannot reference to NULL.");
ASSERT(self->temp_reference_count < MAX_TEMP_REFERENCE,
"Too many temp references");
self->temp_reference[self->temp_reference_count++] = obj;
}
// Pop the top most object from temporary reference stack.
forceinline void vmPopTempRef(PKVM* self) {
ASSERT(self->temp_reference_count > 0, "Temporary reference is empty to pop.");
self->temp_reference_count--;
}
// A realloc wrapper which handles memory allocations of the VM. // A realloc wrapper which handles memory allocations of the VM.
// - To allocate new memory pass NULL to parameter [memory] and 0 to // - To allocate new memory pass NULL to parameter [memory] and 0 to
// parameter [old_size] on failure it'll return NULL. // parameter [old_size] on failure it'll return NULL.
@ -114,19 +128,12 @@ struct PKVM {
// going to track deallocated bytes, instead use garbage collector to do it. // going to track deallocated bytes, instead use garbage collector to do it.
void* vmRealloc(PKVM* self, void* memory, size_t old_size, size_t new_size); void* vmRealloc(PKVM* self, void* memory, size_t old_size, size_t new_size);
// Push the object to temporary references stack.
void vmPushTempRef(PKVM* self, Object* obj);
// Pop the top most object from temporary reference stack.
void vmPopTempRef(PKVM* self);
// Create and return a new handle for the [value]. // Create and return a new handle for the [value].
PkHandle* vmNewHandle(PKVM* self, Var value); PkHandle* vmNewHandle(PKVM* self, Var value);
// Trigger garbage collection manually. // Trigger garbage collection manually.
void vmCollectGarbage(PKVM* self); void vmCollectGarbage(PKVM* self);
// Runs the script and return result. // Runs the script and return result.
PkInterpretResult vmRunScript(PKVM* vm, Script* script); PkInterpretResult vmRunScript(PKVM* vm, Script* script);

View File

@ -0,0 +1,15 @@
// Note that javascript in Node/chrome (V8) is JIT compiled
// which makes more faster than other bytecode interpreted
// VM language listed here.
var start = +new Date();
var N = 50000000;
var factors = []
for (var i = 0; i <= N; i++) {
if (N % i == 0) factors.push(i);
}
var end = +new Date();
console.log('elapsed: ' + (end - start)/1000 + 's');

View File

@ -0,0 +1,12 @@
from lang import clock
start = clock()
N = 50000000; factors = []
for i in 1..N+1
if N % i == 0
list_append(factors, i)
end
end
print("elapsed:", clock() - start, 's')

View File

@ -0,0 +1,10 @@
from time import process_time as clock
start = clock()
N = 50000000; factors = []
for i in range(1, N+1):
if N % i == 0:
factors.append(i)
print("elapsed:", clock() - start, 's')

View File

@ -0,0 +1,9 @@
start = Time.now
N = 50000000; factors = []
for i in 1...N+1
if N % i == 0
factors.append(i)
end
end
puts "elapsed: " + (Time.now - start).to_s + ' s'

View File

@ -0,0 +1,12 @@
var start = System.clock
var N = 50000000
var factors = []
for (i in 0..N) {
if (N % i == 0) {
factors.add(i)
}
}
System.print("elapsed: %(System.clock - start) s")

View File

@ -8,7 +8,7 @@ end
start = clock() start = clock()
for i in 0..10 for i in 0..10
print(fib(28)) print(fib(32))
end end
print('elapsed:', clock() - start, 's') print('elapsed:', clock() - start, 's')

View File

@ -1,10 +1,10 @@
import time from time import process_time as clock
def fib(n): def fib(n):
if n < 2: return n if n < 2: return n
return fib(n - 1) + fib(n - 2) return fib(n - 1) + fib(n - 2)
start = time.process_time() start = clock()
for i in range(0, 10): for i in range(0, 10):
print(fib(28)) print(fib(32))
print("elapsed: " + str(time.process_time() - start), ' s') print("elapsed: ", clock() - start, 's')

View File

@ -9,6 +9,6 @@ end
start = Time.now start = Time.now
for i in 0...10 for i in 0...10
puts fib(28) puts fib(32)
end end
puts "elapsed: " + (Time.now - start).to_s + ' s' puts "elapsed: " + (Time.now - start).to_s + ' s'

View File

@ -1,3 +1,5 @@
class Fib { class Fib {
static get(n) { static get(n) {
if (n < 2) return n if (n < 2) return n
@ -6,7 +8,7 @@ class Fib {
} }
var start = System.clock var start = System.clock
for (i in 0..10) { for (i in 0...10) {
System.print(Fib.get(28)) System.print(Fib.get(32))
} }
System.print("elapsed: %(System.clock - start)") System.print("elapsed: %(System.clock - start)")

View File

@ -1,10 +0,0 @@
var start = +new Date();
list = []
for (var i = 0; i < 10000000; i++) { list.push(i) }
sum = 0
for (var i = 0; i < list.length; i++) { sum += list[i]; }
console.log(sum)
var end = +new Date();
console.log('elapsed: ' + (end - start)/1000 + 's');

View File

@ -0,0 +1,22 @@
from lang import clock
from math import floor
def reverse_list(list)
count = floor(list.length / 2)
for i in 0..count
last_index = list.length - i - 1
last = list[last_index]
list[last_index] = list[i]
list[i] = last
end
return list
end
start = clock()
N = 20000000
l = (0..N).as_list
reverse_list(l)
print(clock() - start, 's')

View File

@ -0,0 +1,15 @@
from time import process_time as clock
from math import floor
def reverse_list(list):
count = floor(len(list) / 2)
for i in range(count):
last_index = len(list) - i - 1
list[i], list[last_index] = list[last_index], list[i]
return list
start = clock()
N = 20000000
l = list(range(N))
reverse_list(l)
print(clock() - start, 's')

View File

@ -0,0 +1,15 @@
def reverse_list(list)
(list.length >> 1).times do |i|
last_index = list.length - i - 1
list[i], list[last_index] = list[last_index], list[i]
end
end
start = Time.now
N = 20000000
list = (0...N).to_a
reverse_list(list)
puts "elapsed: " + (Time.now - start).to_s + ' s'

View File

@ -0,0 +1,15 @@
// Note that javascript in Node/chrome (V8) is JIT compiled
// which makes more faster than other bytecode interpreted
// VM language listed here.
var start = +new Date();
list = []
for (var i = 0; i < 10000000; i++) { list.push(i) }
sum = 0
for (var i = 0; i < list.length; i++) { sum += list[i]; }
console.log(sum)
var end = +new Date();
console.log('elapsed: ' + (end - start)/1000 + 's');

View File

@ -3,9 +3,13 @@ from lang import clock
start = clock() start = clock()
list = [] list = []
for i in 0..10000000 do list_append(list, i) end for i in 0..10000000
list_append(list, i)
end
sum = 0 sum = 0
for i in list do sum += i end for i in list
sum += i
end
print(sum) print(sum)
print("elapsed:", clock() - start) print("elapsed:", clock() - start)

View File

@ -8,4 +8,4 @@ sum = 0
for i in list: sum += i for i in list: sum += i
print(sum) print(sum)
print("elapsed:", clock() - start) print("elapsed:", clock() - start, 's')

View File

@ -0,0 +1,19 @@
from lang import clock
def is_prime(n)
if n < 2 then return false end
for i in 2..n
if n % i == 0 then return false end
end
return true
end
start = clock()
N = 60000; primes = []
for i in 0..N
if is_prime(i)
list_append(primes, i)
end
end
print("elapsed:", clock() - start, "s")

View File

@ -0,0 +1,16 @@
from time import process_time as clock
def is_prime(n):
if n < 2 : return False
for i in range(2, n):
if n % i == 0 : return False
return True
start = clock()
N = 60000; primes = []
for i in range(N):
if is_prime(i):
primes.append(i)
print("elapsed:", clock() - start, "s")

View File

@ -0,0 +1,19 @@
def is_prime(n)
if n < 2 then return false end
for i in 2...n
if n % i == 0 then return false end
end
return true
end
start = Time.now
N = 60000; primes = []
for i in 0...N
if is_prime(i)
primes.append(i)
end
end
puts "elapsed: " + (Time.now - start).to_s + " s"

View File

@ -0,0 +1,20 @@
var is_prime = Fn.new {|n|
if (n < 2) return false
for (i in 2...n) {
if (n % i == 0) return false
}
return true
}
var start = System.clock
var N = 60000
var primes = []
for (i in 0...N) {
if (is_prime.call(i)) {
primes.add(i)
}
}
System.print("elapsed: %(System.clock - start)")

View File

@ -14,4 +14,5 @@ def fib(n)
end end
fib(10) fib(10)
print(res)
assert(res == '0 1 1 2 3 5 8 13 21 34 ') assert(res == '0 1 1 2 3 5 8 13 21 34 ')

View File

@ -1,21 +1,25 @@
## Numericals tests. s = "foo"; s += "bar"
assert(1.0 + 2.0 == 3, 'Num addition failed') assert(s == "foobar")
assert( 1 + 2 * 3 == 7)
## Strings tests. assert((1 + 2)* 3 == 9)
assert("'\"'" == '\'"\'', 'Mixed string escape failed.') assert(42 % 40.0 == 2)
assert("testing" == "test" + "ing", 'String addition failed.') assert("'\"'" == '\'"\'')
assert("testing" == "test" + "ing")
## Lists test. ## Lists test.
l1 = [1, false, null, func print('hello') end] l1 = [1, false, null, func print('hello') end]
assert(is_null(l1[2]), 'List indexing failed.') assert(is_null(l1[2]))
l1[1] = true; assert(l1[1], 'List subscript set failed.') l1[1] = true; assert(l1[1])
## Builtin functions tests. ## Builtin functions tests.
h1 = hash("testing"); h2 = hash("test" + "ing") assert(to_string(42) == '42')
assert(h1 == h2, 'Hash function failed.')
assert(to_string(42) == '42', 'to_string() failed.')
## Core module test.
import math
h1 = math.hash("testing"); h2 = math.hash("test" + "ing")
assert(h1 == h2)
assert(math.ceil(1.1) == math.floor(2.9))
## Logical statement test ## Logical statement test
val = 0; a = false; b = true val = 0; a = false; b = true
@ -23,3 +27,13 @@ get_true = func return true end
if a and b then assert(false) end if a and b then assert(false) end
if a or b then val = 42 else assert(false) end assert(val == 42) if a or b then val = 42 else assert(false) end assert(val == 42)
if get_true() or false then val = 12 end assert(val == 12) if get_true() or false then val = 12 end assert(val == 12)
## Recursive to_string list/map
l = [1]
list_append(l, l)
assert(to_string(l) == '[1, [...]]')
m = {}
m['m'] = m
assert(to_string(m) == '{"m":{...}}')