mirror of
https://github.com/zekexiao/pocketlang.git
synced 2025-02-11 07:00:58 +08:00
Merge pull request #32 from ThakeeNathees/test-optimizations
Iterations were heavily optimized
This commit is contained in:
commit
897a08b3cf
@ -4,7 +4,7 @@
|
||||
<img src="https://user-images.githubusercontent.com/41085900/117528974-88fa8d00-aff2-11eb-8001-183c14786362.png" width="500" >
|
||||
</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.
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
- -v --version
|
||||
- emit opcodes
|
||||
- maybe write a similer .pyc file for pocket
|
||||
- --docs to generate docs from cstring.
|
||||
|
||||
- Ignore line with '\' character.
|
||||
- Implement resolve path in cli.
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
void stdPathAbspath(PKVM* vm) {
|
||||
PkVar path;
|
||||
if (!pkGetArgValue(vm, 1, PK_STRING, &path)) return;
|
||||
|
@ -118,5 +118,6 @@ int main(int argc, char** argv) {
|
||||
|
||||
PkInterpretResult result = pkInterpret(vm, argv[1]);
|
||||
pkFreeVM(vm);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -8,42 +8,56 @@
|
||||
#include "utils.h"
|
||||
#include "vm.h"
|
||||
|
||||
#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##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); \
|
||||
} \
|
||||
// TODO: Replace bufferFill with bufferWrite, and (maybe) shrink the buffer if
|
||||
// it's more than enough.
|
||||
|
||||
#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(Byte, byte, uint8_t)
|
||||
|
@ -9,28 +9,30 @@
|
||||
#include "common.h"
|
||||
#include "include/pocketlang.h"
|
||||
|
||||
#define DECLARE_BUFFER(M__NAME, M__NAME_L, M__TYPE) \
|
||||
typedef struct { \
|
||||
M__TYPE* data; \
|
||||
uint32_t count; \
|
||||
uint32_t capacity; \
|
||||
} M__NAME##Buffer; \
|
||||
\
|
||||
/* Initialize a new buffer int instance. */ \
|
||||
void M__NAME_L##BufferInit(M__NAME##Buffer* self); \
|
||||
\
|
||||
/* Clears the allocated elementes from the VM's realloc function. */ \
|
||||
void M__NAME_L##BufferClear(M__NAME##Buffer* self, \
|
||||
PKVM* vm); \
|
||||
\
|
||||
/* Fill the buffer at the end of it with provided data if the capacity */ \
|
||||
/* isn't enough using VM's realloc function. */ \
|
||||
void M__NAME_L##BufferFill(M__NAME##Buffer* self, PKVM* vm, \
|
||||
M__TYPE data, int count); \
|
||||
\
|
||||
/* 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); \
|
||||
#define DECLARE_BUFFER(M__NAME, M__NAME_L, M__TYPE) \
|
||||
typedef struct { \
|
||||
M__TYPE* data; \
|
||||
uint32_t count; \
|
||||
uint32_t capacity; \
|
||||
} M__NAME##Buffer; \
|
||||
\
|
||||
/* Initialize a new buffer int instance. */ \
|
||||
void M__NAME_L##BufferInit(M__NAME##Buffer* self); \
|
||||
\
|
||||
/* Clears the allocated elementes from the VM's realloc function. */ \
|
||||
void M__NAME_L##BufferClear(M__NAME##Buffer* self, PKVM* vm); \
|
||||
\
|
||||
/* Ensure the capacity is greater than [size], if not resize. */ \
|
||||
void M__NAME_L##BufferReserve(M__NAME##Buffer* self, PKVM* vm, size_t size); \
|
||||
\
|
||||
/* Fill the buffer at the end of it with provided data if the capacity */ \
|
||||
/* isn't enough using VM's realloc function. */ \
|
||||
void M__NAME_L##BufferFill(M__NAME##Buffer* self, PKVM* vm, \
|
||||
M__TYPE data, int count); \
|
||||
\
|
||||
/* 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)
|
||||
@ -39,6 +41,11 @@ DECLARE_BUFFER(Var, var, Var)
|
||||
DECLARE_BUFFER(String, string, String*)
|
||||
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
|
||||
|
||||
#endif // BUFFERS_TEMPLATE_H
|
104
src/common.h
104
src/common.h
@ -8,12 +8,28 @@
|
||||
|
||||
#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 <errno.h>
|
||||
#include <float.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.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.
|
||||
#define DEBUG_DUMP_COMPILED_CODE 0
|
||||
@ -21,12 +37,47 @@
|
||||
// Set this to dump stack frame before executing the next instruction.
|
||||
#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.
|
||||
|
||||
// The internal assertion macro, this will print error and break regardless of
|
||||
// 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).
|
||||
#define __ASSERT(condition, message) \
|
||||
do { \
|
||||
@ -60,9 +111,9 @@
|
||||
|
||||
#else
|
||||
|
||||
#define DEBUG_BREAK()
|
||||
#define ASSERT(condition, message) do { } while (false)
|
||||
#define ASSERT_INDEX(index, size) do {} while (false)
|
||||
#define DEBUG_BREAK() ((void*)0)
|
||||
#define ASSERT(condition, message) ((void*)0)
|
||||
#define ASSERT_INDEX(index, size) ((void*)0)
|
||||
|
||||
// Reference : https://github.com/wren-lang/
|
||||
#if defined( _MSC_VER )
|
||||
@ -70,45 +121,28 @@
|
||||
#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5))
|
||||
#define UNREACHABLE() __builtin_unreachable()
|
||||
#else
|
||||
#define UNREACHABLE()
|
||||
#define UNREACHABLE() ((void*)0)
|
||||
#endif
|
||||
|
||||
#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.
|
||||
#define TODO __ASSERT(false, "TODO: It hasn't implemented yet.")
|
||||
#define OOPS "Oops a bug!! report plese."
|
||||
|
||||
#define STRINGIFY(x) TOSTRING(x)
|
||||
#define TOSTRING(x) #x
|
||||
#define M_CONC(a, b) M_CONC_(a, b)
|
||||
#define M_CONC_(a, b) a##b
|
||||
#define STRINGIFY(x) TOSTRING(x)
|
||||
#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
|
||||
|
||||
// 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;
|
||||
/*****************************************************************************/
|
||||
/* INTERNAL TYPE DEFINES */
|
||||
/*****************************************************************************/
|
||||
|
||||
#if VAR_NAN_TAGGING
|
||||
typedef uint64_t Var;
|
||||
|
@ -5,9 +5,6 @@
|
||||
|
||||
#include "compiler.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "buffers.h"
|
||||
#include "utils.h"
|
||||
@ -107,7 +104,6 @@ typedef enum {
|
||||
TK_END, // end
|
||||
|
||||
TK_NULL, // null
|
||||
TK_SELF, // self
|
||||
TK_IN, // in
|
||||
TK_AND, // and
|
||||
TK_OR, // or
|
||||
@ -170,7 +166,6 @@ static _Keyword _keywords[] = {
|
||||
{ "func", 4, TK_FUNC },
|
||||
{ "end", 3, TK_END },
|
||||
{ "null", 4, TK_NULL },
|
||||
{ "self", 4, TK_SELF },
|
||||
{ "in", 2, TK_IN },
|
||||
{ "and", 3, TK_AND },
|
||||
{ "or", 2, TK_OR },
|
||||
@ -993,7 +988,6 @@ GrammarRule rules[] = { // Prefix Infix Infix Precedence
|
||||
/* TK_FUNC */ { exprFunc, NULL, NO_INFIX },
|
||||
/* TK_END */ NO_RULE,
|
||||
/* TK_NULL */ { exprValue, NULL, NO_INFIX },
|
||||
/* TK_SELF */ { exprValue, NULL, NO_INFIX },
|
||||
/* TK_IN */ { NULL, exprBinaryOp, PREC_IN },
|
||||
/* TK_AND */ { NULL, exprAnd, PREC_LOGICAL_AND },
|
||||
/* TK_OR */ { NULL, exprOr, PREC_LOGICAL_OR },
|
||||
@ -1408,7 +1402,6 @@ static void exprValue(Compiler* compiler, bool can_assign) {
|
||||
TokenType op = compiler->previous.type;
|
||||
switch (op) {
|
||||
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_FALSE: emitOpcode(compiler, OP_PUSH_FALSE); return;
|
||||
default:
|
||||
@ -1514,9 +1507,9 @@ static int compilerAddVariable(Compiler* compiler, const char* name,
|
||||
|
||||
static void compilerAddForward(Compiler* compiler, int instruction, Fn* fn,
|
||||
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 "
|
||||
"function declarations.", MAX_VARIABLES);
|
||||
"function declarations.", MAX_FORWARD_NAMES);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1543,7 +1536,7 @@ static int compilerAddConstant(Compiler* compiler, Var value) {
|
||||
varBufferWrite(literals, compiler->vm, value);
|
||||
} else {
|
||||
parseError(compiler, "A script should contain at most %d "
|
||||
"unique constants.", MAX_CONSTANTS);
|
||||
"unique constants.", MAX_CONSTANTS);
|
||||
}
|
||||
return (int)literals->count - 1;
|
||||
}
|
||||
@ -2180,11 +2173,10 @@ static void compileForStatement(Compiler* compiler) {
|
||||
int container = compilerAddVariable(compiler, "@container", 10, iter_line);
|
||||
compileExpression(compiler);
|
||||
|
||||
// Add iterator to locals. It would initially be null and once the loop
|
||||
// started it'll be an increasing integer indicating that the current
|
||||
// loop is nth.
|
||||
// Add iterator to locals. It's an increasing integer indicating that the
|
||||
// current loop is nth starting from 0.
|
||||
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
|
||||
// each character in a string etc.
|
||||
@ -2192,6 +2184,9 @@ static void compileForStatement(Compiler* compiler) {
|
||||
iter_line);
|
||||
emitOpcode(compiler, OP_PUSH_NULL);
|
||||
|
||||
// Start the iteration, and check if the sequence is iterable.
|
||||
emitOpcode(compiler, OP_ITER_TEST);
|
||||
|
||||
Loop loop;
|
||||
loop.start = (int)_FN->opcodes.count;
|
||||
loop.patch_count = 0;
|
||||
@ -2205,8 +2200,8 @@ static void compileForStatement(Compiler* compiler) {
|
||||
|
||||
compileBlockBody(compiler, BLOCK_LOOP);
|
||||
|
||||
emitLoopJump(compiler);
|
||||
patchJump(compiler, forpatch);
|
||||
emitLoopJump(compiler); //< Loop back to iteration.
|
||||
patchJump(compiler, forpatch); //< Patch exit iteration address.
|
||||
|
||||
// Patch break statement.
|
||||
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);
|
||||
}
|
||||
|
||||
// TODO: the stack already has a null remove one (at vm.c or here).
|
||||
emitOpcode(compiler, OP_PUSH_NULL);
|
||||
emitOpcode(compiler, OP_RETURN);
|
||||
emitOpcode(compiler, OP_END);
|
||||
|
231
src/core.c
231
src/core.c
@ -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.
|
||||
#define CHECK_GET_ARG_API_ERRORS() \
|
||||
__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."); \
|
||||
((void*)0)
|
||||
|
||||
#define ERR_INVALID_ARG_TYPE(m_type) \
|
||||
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]; \
|
||||
sprintf(buff, "%d", arg); \
|
||||
vm->fiber->error = stringFormat(vm, "Expected a " m_type \
|
||||
@ -141,14 +142,12 @@ void pkReturnValue(PKVM* vm, PkVar value) {
|
||||
RET(*(Var*)value);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
// Convert number var as int32_t. Check if it's number before using it.
|
||||
#define _AS_INTEGER(var) (int32_t)trunc(AS_NUM(var))
|
||||
/*****************************************************************************/
|
||||
/* CORE INTERNAL */
|
||||
/*****************************************************************************/
|
||||
|
||||
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->length = length;
|
||||
|
||||
@ -239,7 +238,6 @@ static inline bool validateIndex(PKVM* vm, int32_t index, int32_t size,
|
||||
/* BUILTIN FUNCTIONS API */
|
||||
/*****************************************************************************/
|
||||
|
||||
|
||||
Function* getBuiltinFunction(PKVM* vm, int index) {
|
||||
ASSERT_INDEX(index, vm->builtins_count);
|
||||
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(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) {
|
||||
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) {
|
||||
int argc = ARGC;
|
||||
if (argc != 1 && argc != 2) {
|
||||
@ -299,12 +304,13 @@ void coreAssert(PKVM* vm) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!toBool(ARG1)) {
|
||||
String* msg = NULL;
|
||||
|
||||
if (argc == 2) {
|
||||
if (AS_OBJ(ARG2)->type != OBJ_STRING) {
|
||||
msg = toString(vm, ARG2, false);
|
||||
msg = toString(vm, ARG2);
|
||||
} else {
|
||||
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.
|
||||
void coreHash(PKVM* vm) {
|
||||
if (IS_OBJ(ARG1)) {
|
||||
if (!isObjectHashable(AS_OBJ(ARG1)->type)) {
|
||||
RET(VAR_NULL);
|
||||
}
|
||||
}
|
||||
RET(VAR_NUM((double)varHashValue(ARG1)));
|
||||
}
|
||||
|
||||
PK_DOC(coreToString,
|
||||
"to_string(value:var) -> string\n"
|
||||
"Returns the string representation of the value.");
|
||||
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) {
|
||||
// If the host appliaction donesn't provide any write function, discard the
|
||||
// output.
|
||||
@ -344,7 +347,7 @@ void corePrint(PKVM* vm) {
|
||||
if (IS_OBJ(arg) && AS_OBJ(arg)->type == OBJ_STRING) {
|
||||
str = (String*)AS_OBJ(arg);
|
||||
} else {
|
||||
str = toString(vm, arg, false);
|
||||
str = toString(vm, arg);
|
||||
}
|
||||
|
||||
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) {
|
||||
str = (String*)AS_OBJ(arg);
|
||||
} else {
|
||||
str = toString(vm, arg, false);
|
||||
str = toString(vm, arg);
|
||||
}
|
||||
|
||||
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 */
|
||||
/*****************************************************************************/
|
||||
@ -526,6 +559,7 @@ void initializeCore(PKVM* vm) {
|
||||
// Initialize builtin functions.
|
||||
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_bool", coreIsBool, 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("assert", coreAssert, -1);
|
||||
INITALIZE_BUILTIN_FN("hash", coreHash, 1);
|
||||
INITALIZE_BUILTIN_FN("to_string", coreToString, 1);
|
||||
INITALIZE_BUILTIN_FN("print", corePrint, -1);
|
||||
|
||||
// string functions.
|
||||
// String functions.
|
||||
INITALIZE_BUILTIN_FN("str_lower", coreStrLower, 1);
|
||||
INITALIZE_BUILTIN_FN("str_upper", coreStrUpper, 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, "gc", stdLangGC, 0);
|
||||
moduleAddFunctionInternal(vm, lang, "write", stdLangWrite, -1);
|
||||
#ifdef DEBUG
|
||||
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:
|
||||
{
|
||||
if (o2->type == OBJ_STRING) {
|
||||
return VAR_OBJ(stringFormat(vm, "@@", (String*)o1, (String*)o2));
|
||||
return VAR_OBJ(stringJoin(vm, (String*)o1, (String*)o2));
|
||||
}
|
||||
} break;
|
||||
|
||||
@ -731,16 +771,35 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
|
||||
|
||||
case OBJ_MAP:
|
||||
{
|
||||
Var value = mapGet((Map*)obj, VAR_OBJ(&attrib->_super));
|
||||
if (IS_UNDEF(value)) {
|
||||
vm->fiber->error = stringFormat(vm, "Key (\"@\") not exists.", attrib);
|
||||
return VAR_NULL;
|
||||
}
|
||||
return value;
|
||||
TODO; // Not sure should I allow this(below).
|
||||
//Var value = mapGet((Map*)obj, VAR_OBJ(&attrib->_super));
|
||||
//if (IS_UNDEF(value)) {
|
||||
// vm->fiber->error = stringFormat(vm, "Key (\"@\") not exists.", attrib);
|
||||
// return VAR_NULL;
|
||||
//}
|
||||
//return value;
|
||||
}
|
||||
|
||||
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: {
|
||||
Script* scr = (Script*)obj;
|
||||
@ -897,7 +956,7 @@ Var varGetSubscript(PKVM* vm, Var on, Var key) {
|
||||
Var value = mapGet((Map*)obj, key);
|
||||
if (IS_UNDEF(value)) {
|
||||
|
||||
String* key_str = toString(vm, key, true);
|
||||
String* key_str = toString(vm, key);
|
||||
vmPushTempRef(vm, &key_str->_super);
|
||||
if (IS_OBJ(key) && !isObjectHashable(AS_OBJ(key)->type)) {
|
||||
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);
|
||||
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;
|
||||
}
|
@ -43,11 +43,5 @@ void varSetAttrib(PKVM* vm, Var on, String* name, Var value);
|
||||
Var varGetSubscript(PKVM* vm, Var on, Var key);
|
||||
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
|
||||
|
10
src/debug.c
10
src/debug.c
@ -3,10 +3,10 @@
|
||||
* Licensed under: MIT License
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "debug.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include "core.h"
|
||||
#include "vm.h"
|
||||
|
||||
// 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_SELF:
|
||||
case OP_PUSH_0:
|
||||
case OP_PUSH_TRUE:
|
||||
case OP_PUSH_FALSE:
|
||||
case OP_SWAP:
|
||||
@ -251,6 +251,8 @@ void dumpFunctionCode(PKVM* vm, Function* func) {
|
||||
printf("%5d (argc)\n", READ_SHORT());
|
||||
break;
|
||||
|
||||
case OP_ITER_TEST: NO_ARGS(); break;
|
||||
|
||||
case OP_ITER:
|
||||
case OP_JUMP:
|
||||
case OP_JUMP_IF:
|
||||
|
@ -14,7 +14,6 @@ void dumpValue(PKVM* vm, Var value);
|
||||
// Dump opcodes of the given function.
|
||||
void dumpFunctionCode(PKVM* vm, Function* func);
|
||||
|
||||
|
||||
// Dump the all the global values of the script.
|
||||
void dumpGlobalValues(PKVM* vm);
|
||||
|
||||
|
@ -10,8 +10,8 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/*****************************************************************************/
|
||||
@ -26,7 +26,7 @@ extern "C" {
|
||||
#define PK_VERSION_MINOR 1
|
||||
#define PK_VERSION_PATCH 0
|
||||
|
||||
// String representation of the value.
|
||||
// String representation of the version.
|
||||
#define PK_VERSION_STRING "0.1.0"
|
||||
|
||||
// Pocketlang visibility macros. define PK_DLL for using pocketlang as a
|
||||
@ -54,18 +54,18 @@ extern "C" {
|
||||
#define PK_PUBLIC
|
||||
#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 */
|
||||
/*****************************************************************************/
|
||||
|
||||
// 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,
|
||||
// heap, and manage memory allocations.
|
||||
typedef struct PKVM PKVM;
|
||||
|
@ -20,8 +20,8 @@ OPCODE(PUSH_CONSTANT, 2, 1)
|
||||
// Push null on the stack.
|
||||
OPCODE(PUSH_NULL, 0, 1)
|
||||
|
||||
// Push self on the stack. If the runtime don't have self it'll push null.
|
||||
OPCODE(PUSH_SELF, 0, 1)
|
||||
// Push number 0 on the stack.
|
||||
OPCODE(PUSH_0, 0, 1)
|
||||
|
||||
// Push true on the stack.
|
||||
OPCODE(PUSH_TRUE, 0, 1)
|
||||
@ -96,34 +96,27 @@ OPCODE(PUSH_BUILTIN_FN, 2, 1)
|
||||
// Pop the stack top.
|
||||
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
|
||||
// script in the script. If the script is imported for the first time (not
|
||||
// cached) the script's body will be executed.
|
||||
OPCODE(IMPORT, 0, 0)
|
||||
|
||||
// TODO: may be later.
|
||||
//OPCODE(CALL_0, 0, 0) //< Push null call null will be the return value.
|
||||
//OPCODE(CALL_1, 0, -1) //< Push null and arg1. arg1 will be popped.
|
||||
//OPCODE(CALL_2, 0, -2) //< And so on.
|
||||
//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)
|
||||
// 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.
|
||||
// params: n bytes argc.
|
||||
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
|
||||
// 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.
|
||||
// param: 1 byte iterate type (will be set by ITER_TEST at runtime).
|
||||
// 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.
|
||||
// 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(IN, 0, -1)
|
||||
|
||||
// TODO: literal list, map
|
||||
|
||||
// A sudo instruction which will never be called. A function's last opcode
|
||||
// used for debugging.
|
||||
OPCODE(END, 0, 0)
|
||||
|
@ -25,6 +25,13 @@ bool utilIsDigit(char c) {
|
||||
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) {
|
||||
_DoubleBitsConv bits;
|
||||
bits.num = value;
|
||||
|
@ -18,13 +18,6 @@ bool utilIsName(char c);
|
||||
// Returns true if `c` is [0-9].
|
||||
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.
|
||||
uint64_t utilDoubleToBits(double value);
|
||||
|
||||
|
228
src/var.c
228
src/var.c
@ -3,10 +3,10 @@
|
||||
* Licensed under: MIT License
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "utils.h"
|
||||
#include "var.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include "utils.h"
|
||||
#include "vm.h"
|
||||
|
||||
/*****************************************************************************/
|
||||
@ -257,6 +257,7 @@ static String* _allocateString(PKVM* vm, size_t length) {
|
||||
varInitObject(&string->_super, vm, OBJ_STRING);
|
||||
string->length = (uint32_t)length;
|
||||
string->data[length] = '\0';
|
||||
string->capacity = (uint32_t)(length + 1);
|
||||
return string;
|
||||
}
|
||||
|
||||
@ -274,12 +275,14 @@ String* newStringLength(PKVM* vm, const char* text, uint32_t length) {
|
||||
|
||||
List* newList(PKVM* vm, uint32_t size) {
|
||||
List* list = ALLOCATE(vm, List);
|
||||
vmPushTempRef(vm, &list->_super);
|
||||
varInitObject(&list->_super, vm, OBJ_LIST);
|
||||
varBufferInit(&list->elements);
|
||||
if (size > 0) {
|
||||
varBufferFill(&list->elements, vm, VAR_NULL, size);
|
||||
list->elements.count = 0;
|
||||
}
|
||||
vmPopTempRef(vm);
|
||||
return list;
|
||||
}
|
||||
|
||||
@ -760,140 +763,192 @@ bool isObjectHashable(ObjectType type) {
|
||||
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)) {
|
||||
return newStringLength(vm, "null", 4);
|
||||
ByteBufferAddString(buff, vm, "null", 4);
|
||||
return;
|
||||
|
||||
} else if (IS_BOOL(v)) {
|
||||
if (AS_BOOL(v)) {
|
||||
return newStringLength(vm, "true", 4);
|
||||
} else {
|
||||
return newStringLength(vm, "false", 5);
|
||||
}
|
||||
if (AS_BOOL(v)) ByteBufferAddString(buff, vm, "true", 4);
|
||||
else ByteBufferAddString(buff, vm, "false", 5);
|
||||
return;
|
||||
|
||||
} else if (IS_NUM(v)) {
|
||||
char buff[TO_STRING_BUFF_SIZE];
|
||||
int length = sprintf(buff, "%.14g", AS_NUM(v));
|
||||
ASSERT(length < TO_STRING_BUFF_SIZE, "Buffer overflowed.");
|
||||
return newStringLength(vm, buff, length);
|
||||
char num_buff[TO_STRING_BUFF_SIZE];
|
||||
int length = sprintf(num_buff, "%.14g", AS_NUM(v));
|
||||
__ASSERT(length < TO_STRING_BUFF_SIZE, "Buffer overflowed.");
|
||||
ByteBufferAddString(buff, vm, num_buff, length); return;
|
||||
|
||||
} else if (IS_OBJ(v)) {
|
||||
|
||||
Object* obj = AS_OBJ(v);
|
||||
switch (obj->type) {
|
||||
|
||||
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]).
|
||||
String* string = newStringLength(vm, ((String*)obj)->data, ((String*)obj)->length);
|
||||
if (!recursive) return string;
|
||||
vmPushTempRef(vm, &string->_super);
|
||||
String* repr = stringFormat(vm, "\"@\"", string);
|
||||
vmPopTempRef(vm);
|
||||
return repr;
|
||||
byteBufferWrite(buff, vm, '"');
|
||||
ByteBufferAddString(buff, vm, str->data, str->length);
|
||||
byteBufferWrite(buff, vm, '"');
|
||||
return;
|
||||
}
|
||||
|
||||
case OBJ_LIST:
|
||||
{
|
||||
List* list = (List*)obj;
|
||||
if (list->elements.count == 0) return newStringLength(vm, "[]", 2);
|
||||
|
||||
String* result = newStringLength(vm, "[", 1);
|
||||
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)
|
||||
if (list->elements.count == 0) {
|
||||
ByteBufferAddString(buff, vm, "[]", 2);
|
||||
return;
|
||||
}
|
||||
|
||||
result = stringFormat(vm, "@]", result);
|
||||
vmPopTempRef(vm); // result (last pointer)
|
||||
return result;
|
||||
// Check if the list is recursive.
|
||||
OuterSequence* seq = outer;
|
||||
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:
|
||||
{
|
||||
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);
|
||||
vmPushTempRef(vm, &result->_super); // result
|
||||
// Check if the map is recursive.
|
||||
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.
|
||||
do {
|
||||
|
||||
// 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)) {
|
||||
i++;
|
||||
if (i >= map->capacity) {
|
||||
if (++i >= map->capacity) {
|
||||
_done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (_done) break;
|
||||
|
||||
const char* fmt = (!_first) ? "@, @:@" : "@@:@";
|
||||
|
||||
String* key_str = toString(vm, map->entries[i].key, true);
|
||||
vmPushTempRef(vm, &key_str->_super); // key_str
|
||||
|
||||
String* val_str = toString(vm, map->entries[i].value, true);
|
||||
vmPushTempRef(vm, &val_str->_super); // val_str
|
||||
|
||||
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;
|
||||
if (!_first) {
|
||||
ByteBufferAddString(buff, vm, ", ", 2);
|
||||
_first = false;
|
||||
}
|
||||
toStringInternal(vm, map->entries[i].key, buff, &seq_map);
|
||||
byteBufferWrite(buff, vm, ':');
|
||||
toStringInternal(vm, map->entries[i].value, buff, &seq_map);
|
||||
i++;
|
||||
} while (i < map->capacity);
|
||||
|
||||
result = stringFormat(vm, "@}", result);
|
||||
vmPopTempRef(vm); // result (last pointer)
|
||||
return result;
|
||||
byteBufferWrite(buff, vm, '}');
|
||||
return;
|
||||
}
|
||||
|
||||
case OBJ_RANGE:
|
||||
{
|
||||
Range* range = (Range*)obj;
|
||||
|
||||
// FIXME: Validate I might need some review on the below one.
|
||||
char buff_from[50];
|
||||
snprintf(buff_from, 50, "%f", range->from);
|
||||
char buff_to[50];
|
||||
snprintf(buff_to, 50, "%f", range->to);
|
||||
|
||||
return stringFormat(vm, "[Range:$..$]", buff_from, buff_to);
|
||||
char buff_from[STR_NUM_BUFF_SIZE];
|
||||
int len_from = snprintf(buff_from, sizeof(buff_from), "%f", range->from);
|
||||
char buff_to[STR_NUM_BUFF_SIZE];
|
||||
int len_to = snprintf(buff_to, sizeof(buff_to), "%f", range->to);
|
||||
ByteBufferAddString(buff, vm, "[Range:", 7);
|
||||
ByteBufferAddString(buff, vm, buff_from, len_from);
|
||||
ByteBufferAddString(buff, vm, "..", 2);
|
||||
ByteBufferAddString(buff, vm, buff_to, len_to);
|
||||
byteBufferWrite(buff, vm, ']');
|
||||
return;
|
||||
}
|
||||
|
||||
case OBJ_SCRIPT: {
|
||||
Script* scr = ((Script*)obj);
|
||||
ByteBufferAddString(buff, vm, "[Module:", 8);
|
||||
if (scr->moudle != NULL) {
|
||||
return stringFormat(vm, "[Lib:@]", scr->moudle);
|
||||
ByteBufferAddString(buff, vm, scr->moudle->data, scr->moudle->length);
|
||||
} 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_USER: return newStringLength(vm, "[UserObj]", 9); // TODO;
|
||||
|
||||
case OBJ_FUNC: {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
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) {
|
||||
@ -976,6 +1031,23 @@ String* stringFormat(PKVM* vm, const char* fmt, ...) {
|
||||
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 length) {
|
||||
|
||||
|
39
src/var.h
39
src/var.h
@ -7,29 +7,19 @@
|
||||
#define VAR_H
|
||||
|
||||
/** @file
|
||||
* A simple single header dynamic type system library for small dynamic typed
|
||||
* languages using a technique called NaN-tagging (optional). The method is
|
||||
* inspired from the wren (https://wren.io/) an awsome language written by the
|
||||
* author of "Crafting Interpreters" Bob Nystrom and it's contrbuters.
|
||||
* A simple dynamic type system library for small dynamic typed languages using
|
||||
* a technique called NaN-tagging (optional). The method is inspired from the
|
||||
* wren (https://wren.io/) an awsome language written by Bob Nystrom the author
|
||||
* of "Crafting Interpreters" and it's contrbuters.
|
||||
* Reference:
|
||||
* https://github.com/wren-lang/wren/blob/main/src/vm/wren_value.h
|
||||
* https://leonardschuetz.ch/blog/nan-boxing/
|
||||
*
|
||||
* The previous implementation was to add a type field to every \ref var
|
||||
* and use smart pointers(C++17) to object with custom destructors,
|
||||
* which makes the programme in effect for small types such null, bool,
|
||||
* int and float.
|
||||
* The previous implementation was to add a type field to every var and use
|
||||
* smart pointers(C++17) to object with custom destructors, which makes the
|
||||
* programme inefficient for small types such null, bool, 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 "buffers.h"
|
||||
|
||||
@ -205,7 +195,7 @@ typedef enum {
|
||||
struct Object {
|
||||
ObjectType type; //< Type of the object in \ref var_Object_Type.
|
||||
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.
|
||||
};
|
||||
@ -267,6 +257,10 @@ struct Script {
|
||||
functions: [ fn1, fn2 ]
|
||||
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.
|
||||
UintBuffer global_names; //< Name map to index in globals.
|
||||
VarBuffer literals; //< Script literal constant values.
|
||||
@ -451,9 +445,8 @@ uint32_t varHashValue(Var v);
|
||||
// Return true if the object type is hashable.
|
||||
bool isObjectHashable(ObjectType type);
|
||||
|
||||
// Returns the string version of the value. Note: pass false as [_recursive]
|
||||
// It's for internal use (or may be I could make a wrapper around).
|
||||
String* toString(PKVM* vm, Var v, bool _recursive);
|
||||
// Returns the string version of the value.
|
||||
String* toString(PKVM* vm, Var v);
|
||||
|
||||
// Returns the truthy value of the var.
|
||||
bool toBool(Var v);
|
||||
@ -464,6 +457,10 @@ bool toBool(Var v);
|
||||
// @ - a String object
|
||||
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
|
||||
// return it's index in the buffer.
|
||||
uint32_t scriptAddName(Script* self, PKVM* vm, const char* name,
|
||||
|
248
src/vm.c
248
src/vm.c
@ -5,6 +5,7 @@
|
||||
|
||||
#include "vm.h"
|
||||
|
||||
#include <math.h>
|
||||
#include "core.h"
|
||||
#include "utils.h"
|
||||
|
||||
@ -12,6 +13,7 @@
|
||||
#include "debug.h" //< Wrap around debug macro.
|
||||
#endif
|
||||
|
||||
// Evaluvated to true if a runtime error set on the current fiber.
|
||||
#define HAS_ERROR() (vm->fiber->error != NULL)
|
||||
|
||||
// Initially allocated call frame capacity. Will grow dynamically.
|
||||
@ -31,7 +33,9 @@
|
||||
// allocated so far plus the fill factor of it.
|
||||
#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) {
|
||||
free(memory);
|
||||
return NULL;
|
||||
@ -82,7 +86,7 @@ PKVM* pkNewVM(pkConfiguration* config) {
|
||||
vm->gray_list_count = 0;
|
||||
vm->gray_list_capacity = MIN_CAPACITY;
|
||||
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->min_heap_size = MIN_HEAP_SIZE;
|
||||
vm->heap_fill_percent = HEAP_FILL_PERCENT;
|
||||
@ -142,18 +146,6 @@ void pkReleaseHandle(PKVM* vm, PkHandle* handle) {
|
||||
/* 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* handle = (PkHandle*)ALLOCATE(self, PkHandle);
|
||||
handle->value = value;
|
||||
@ -242,7 +234,7 @@ void pkSetUserData(PKVM* vm, void* 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));
|
||||
if (IS_UNDEF(scr)) return NULL;
|
||||
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
|
||||
// the provided result's string's on_done() will be called and, it's string will
|
||||
// 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;
|
||||
|
||||
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
|
||||
// compiled here it'll set [is_new_script] to true oterwise (using the cached
|
||||
// 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.
|
||||
Script* scr = getCoreLib(vm, path_name);
|
||||
@ -301,14 +293,12 @@ static Var importScript(PKVM* vm, String* path_name) {
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
static void ensureStackSize(PKVM* vm, int size) {
|
||||
static forceinline void growStack(PKVM* vm, int size) {
|
||||
Fiber* fiber = vm->fiber;
|
||||
|
||||
if (fiber->stack_size > size) return;
|
||||
ASSERT(fiber->stack_size <= size, OOPS);
|
||||
int new_size = utilPowerOf2Ceil(size);
|
||||
|
||||
Var* old_rbp = fiber->stack; //< Old stack base pointer.
|
||||
|
||||
fiber->stack = (Var*)vmRealloc(vm, fiber->stack,
|
||||
sizeof(Var) * fiber->stack_size,
|
||||
sizeof(Var) * new_size);
|
||||
@ -337,39 +327,37 @@ static void ensureStackSize(PKVM* vm, int size) {
|
||||
= 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->ret = MAP_PTR(fiber->ret);
|
||||
|
||||
// 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;
|
||||
frame->rbp = MAP_PTR(frame->rbp);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static inline void pushCallFrame(PKVM* vm, Function* fn) {
|
||||
ASSERT(!fn->is_native, "Native function shouldn't use call frames.");
|
||||
static forceinline void pushCallFrame(PKVM* vm, Function* fn) {
|
||||
ASSERT(!fn->is_native, "Native function shouldn't use call frames.");
|
||||
|
||||
// Grow the stack frame if needed.
|
||||
if (vm->fiber->frame_count + 1 > vm->fiber->frame_capacity) {
|
||||
int new_capacity = vm->fiber->frame_capacity * 2;
|
||||
vm->fiber->frames = (CallFrame*)vmRealloc(vm, vm->fiber->frames,
|
||||
sizeof(CallFrame) * vm->fiber->frame_capacity,
|
||||
sizeof(CallFrame) * new_capacity);
|
||||
vm->fiber->frame_capacity = new_capacity;
|
||||
}
|
||||
/* Grow the stack frame if needed. */
|
||||
if (vm->fiber->frame_count + 1 > vm->fiber->frame_capacity) {
|
||||
int new_capacity = vm->fiber->frame_capacity << 1;
|
||||
vm->fiber->frames = (CallFrame*)vmRealloc(vm, vm->fiber->frames,
|
||||
sizeof(CallFrame) * vm->fiber->frame_capacity,
|
||||
sizeof(CallFrame) * new_capacity);
|
||||
vm->fiber->frame_capacity = new_capacity;
|
||||
}
|
||||
|
||||
// Grow the stack if needed.
|
||||
int stack_size = (int)(vm->fiber->sp - vm->fiber->stack);
|
||||
int needed = stack_size + fn->fn->stack_size;
|
||||
// TODO: set stack overflow maybe?.
|
||||
ensureStackSize(vm, needed);
|
||||
/* Grow the stack if needed. */
|
||||
int needed = fn->fn->stack_size + (int)(vm->fiber->sp - vm->fiber->stack);
|
||||
if (vm->fiber->stack_size <= needed) growStack(vm, needed);
|
||||
|
||||
CallFrame* frame = &vm->fiber->frames[vm->fiber->frame_count++];
|
||||
frame->rbp = vm->fiber->ret;
|
||||
frame->fn = fn;
|
||||
frame->ip = fn->fn->opcodes.data;
|
||||
CallFrame* frame = vm->fiber->frames + vm->fiber->frame_count++;
|
||||
frame->rbp = vm->fiber->ret;
|
||||
frame->fn = fn;
|
||||
frame->ip = fn->fn->opcodes.data;
|
||||
}
|
||||
|
||||
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
|
||||
// provided string pointers.
|
||||
static PkInterpretResult interpretSource(PKVM* vm, pkStringPtr source,
|
||||
static forceinline PkInterpretResult interpretSource(PKVM* vm, pkStringPtr source,
|
||||
pkStringPtr path) {
|
||||
String* path_name = newString(vm, path.string);
|
||||
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)
|
||||
#endif
|
||||
|
||||
#define SWITCH(code) \
|
||||
L_vm_main_loop: \
|
||||
DEBUG_CALL_STACK(); \
|
||||
switch (code = (Opcode)READ_BYTE())
|
||||
|
||||
#define SWITCH() Opcode instruction; switch (instruction = (Opcode)READ_BYTE())
|
||||
#define OPCODE(code) case OP_##code
|
||||
#define DISPATCH() goto L_vm_main_loop
|
||||
|
||||
PUSH(VAR_NULL); // Return value of the script body.
|
||||
LOAD_FRAME();
|
||||
|
||||
Opcode instruction;
|
||||
SWITCH(instruction) {
|
||||
L_vm_main_loop:
|
||||
DEBUG_CALL_STACK();
|
||||
SWITCH() {
|
||||
|
||||
OPCODE(PUSH_CONSTANT):
|
||||
{
|
||||
@ -569,6 +554,10 @@ PkInterpretResult vmRunScript(PKVM* vm, Script* _script) {
|
||||
PUSH(VAR_NULL);
|
||||
DISPATCH();
|
||||
|
||||
OPCODE(PUSH_0):
|
||||
PUSH(VAR_NUM(0));
|
||||
DISPATCH();
|
||||
|
||||
OPCODE(PUSH_TRUE):
|
||||
PUSH(VAR_TRUE);
|
||||
DISPATCH();
|
||||
@ -579,11 +568,9 @@ PkInterpretResult vmRunScript(PKVM* vm, Script* _script) {
|
||||
|
||||
OPCODE(SWAP):
|
||||
{
|
||||
Var top1 = *(vm->fiber->sp - 1);
|
||||
Var top2 = *(vm->fiber->sp - 2);
|
||||
|
||||
*(vm->fiber->sp - 1) = top2;
|
||||
*(vm->fiber->sp - 2) = top1;
|
||||
Var tmp = *(vm->fiber->sp - 1);
|
||||
*(vm->fiber->sp - 1) = *(vm->fiber->sp - 2);
|
||||
*(vm->fiber->sp - 2) = tmp;
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
@ -621,15 +608,12 @@ PkInterpretResult vmRunScript(PKVM* vm, Script* _script) {
|
||||
|
||||
if (IS_OBJ(key) && !isObjectHashable(AS_OBJ(key)->type)) {
|
||||
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(); // key
|
||||
|
||||
CHECK_ERROR();
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
@ -702,7 +686,9 @@ PkInterpretResult vmRunScript(PKVM* vm, Script* _script) {
|
||||
|
||||
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));
|
||||
DISPATCH();
|
||||
}
|
||||
@ -727,12 +713,11 @@ PkInterpretResult vmRunScript(PKVM* vm, Script* _script) {
|
||||
Var* callable = vm->fiber->sp - argc - 1;
|
||||
|
||||
if (IS_OBJ(*callable) && AS_OBJ(*callable)->type == OBJ_FUNC) {
|
||||
|
||||
Function* fn = (Function*)AS_OBJ(*callable);
|
||||
|
||||
// -1 argument means multiple number of args.
|
||||
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);
|
||||
String* msg = stringFormat(vm, "Expected excatly @ argument(s).",
|
||||
arg_str);
|
||||
@ -740,10 +725,6 @@ PkInterpretResult vmRunScript(PKVM* vm, Script* _script) {
|
||||
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).
|
||||
vm->fiber->ret = callable;
|
||||
|
||||
@ -756,30 +737,125 @@ PkInterpretResult vmRunScript(PKVM* vm, Script* _script) {
|
||||
// Pop function arguments except for the return value.
|
||||
vm->fiber->sp = vm->fiber->ret + 1;
|
||||
CHECK_ERROR();
|
||||
DISPATCH(); //< This will save 2 jumps.
|
||||
|
||||
} else {
|
||||
pushCallFrame(vm, fn);
|
||||
LOAD_FRAME(); //< Load the top frame to vm's execution variables.
|
||||
DISPATCH(); //< This will save 2 jumps.
|
||||
}
|
||||
|
||||
} else {
|
||||
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();
|
||||
}
|
||||
|
||||
OPCODE(ITER):
|
||||
{
|
||||
Var* iter_value = (vm->fiber->sp - 1);
|
||||
Var* iterator = (vm->fiber->sp - 2);
|
||||
Var* container = (vm->fiber->sp - 3);
|
||||
Var* value = (vm->fiber->sp - 1);
|
||||
Var* iterator = (vm->fiber->sp - 2);
|
||||
Var seq = PEEK(-3);
|
||||
uint16_t jump_offset = READ_SHORT();
|
||||
|
||||
bool iterated = varIterate(vm, *container, iterator, iter_value);
|
||||
CHECK_ERROR();
|
||||
if (!iterated) {
|
||||
IP += jump_offset;
|
||||
|
||||
#define JUMP_ITER_EXIT() \
|
||||
do { \
|
||||
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();
|
||||
}
|
||||
|
||||
@ -821,22 +897,17 @@ PkInterpretResult vmRunScript(PKVM* vm, Script* _script) {
|
||||
{
|
||||
// TODO: handle caller fiber.
|
||||
|
||||
Var ret = POP();
|
||||
// Set the return value.
|
||||
*(frame->rbp) = POP();
|
||||
|
||||
// Pop the last frame.
|
||||
vm->fiber->frame_count--;
|
||||
|
||||
// If no more call frames. We're done.
|
||||
if (vm->fiber->frame_count == 0) {
|
||||
// Pop the last frame, and if no more call frames, we're done.
|
||||
if (--vm->fiber->frame_count == 0) {
|
||||
vm->fiber->sp = vm->fiber->stack;
|
||||
PUSH(ret);
|
||||
return PK_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
// Set the return value.
|
||||
*(frame->rbp) = ret;
|
||||
|
||||
// Pop the locals and update stack pointer.
|
||||
// Pop the params (locals should have popped at this point) and update
|
||||
// stack pointer.
|
||||
vm->fiber->sp = frame->rbp + 1; // +1: rbp is returned value.
|
||||
|
||||
LOAD_FRAME();
|
||||
@ -979,7 +1050,7 @@ PkInterpretResult vmRunScript(PKVM* vm, Script* _script) {
|
||||
{
|
||||
// Don't pop yet, we need the reference for gc.
|
||||
Var r = PEEK(-1), l = PEEK(-2);
|
||||
Var value = varMultiply(vm, l, r);
|
||||
Var value = varDivide(vm, l, r);
|
||||
POP(); POP(); // r, l
|
||||
PUSH(value);
|
||||
|
||||
@ -1004,8 +1075,9 @@ PkInterpretResult vmRunScript(PKVM* vm, Script* _script) {
|
||||
OPCODE(BIT_XOR):
|
||||
OPCODE(BIT_LSHIFT):
|
||||
OPCODE(BIT_RSHIFT):
|
||||
TODO;
|
||||
|
||||
OPCODE(EQEQ) :
|
||||
OPCODE(EQEQ):
|
||||
{
|
||||
Var r = POP(), l = POP();
|
||||
PUSH(VAR_BOOL(isValuesEqual(l, r)));
|
||||
|
21
src/vm.h
21
src/vm.h
@ -103,6 +103,20 @@ struct PKVM {
|
||||
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.
|
||||
// - To allocate new memory pass NULL to parameter [memory] and 0 to
|
||||
// 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.
|
||||
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].
|
||||
PkHandle* vmNewHandle(PKVM* self, Var value);
|
||||
|
||||
// Trigger garbage collection manually.
|
||||
void vmCollectGarbage(PKVM* self);
|
||||
|
||||
|
||||
// Runs the script and return result.
|
||||
PkInterpretResult vmRunScript(PKVM* vm, Script* script);
|
||||
|
||||
|
15
test/benchmark/factors/factors.js
Normal file
15
test/benchmark/factors/factors.js
Normal 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');
|
12
test/benchmark/factors/factors.pk
Normal file
12
test/benchmark/factors/factors.pk
Normal 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')
|
10
test/benchmark/factors/factors.py
Normal file
10
test/benchmark/factors/factors.py
Normal 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')
|
9
test/benchmark/factors/factors.rb
Normal file
9
test/benchmark/factors/factors.rb
Normal 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'
|
12
test/benchmark/factors/factors.wren
Normal file
12
test/benchmark/factors/factors.wren
Normal 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")
|
@ -8,7 +8,7 @@ end
|
||||
|
||||
start = clock()
|
||||
for i in 0..10
|
||||
print(fib(28))
|
||||
print(fib(32))
|
||||
end
|
||||
|
||||
print('elapsed:', clock() - start, 's')
|
||||
|
@ -1,10 +1,10 @@
|
||||
import time
|
||||
from time import process_time as clock
|
||||
|
||||
def fib(n):
|
||||
if n < 2: return n
|
||||
return fib(n - 1) + fib(n - 2)
|
||||
|
||||
start = time.process_time()
|
||||
start = clock()
|
||||
for i in range(0, 10):
|
||||
print(fib(28))
|
||||
print("elapsed: " + str(time.process_time() - start), ' s')
|
||||
print(fib(32))
|
||||
print("elapsed: ", clock() - start, 's')
|
||||
|
@ -9,6 +9,6 @@ end
|
||||
|
||||
start = Time.now
|
||||
for i in 0...10
|
||||
puts fib(28)
|
||||
puts fib(32)
|
||||
end
|
||||
puts "elapsed: " + (Time.now - start).to_s + ' s'
|
||||
|
@ -1,3 +1,5 @@
|
||||
|
||||
|
||||
class Fib {
|
||||
static get(n) {
|
||||
if (n < 2) return n
|
||||
@ -6,7 +8,7 @@ class Fib {
|
||||
}
|
||||
|
||||
var start = System.clock
|
||||
for (i in 0..10) {
|
||||
System.print(Fib.get(28))
|
||||
for (i in 0...10) {
|
||||
System.print(Fib.get(32))
|
||||
}
|
||||
System.print("elapsed: %(System.clock - start)")
|
@ -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');
|
22
test/benchmark/list_reverse/list_reverse.pk
Normal file
22
test/benchmark/list_reverse/list_reverse.pk
Normal 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')
|
||||
|
15
test/benchmark/list_reverse/list_reverse.py
Normal file
15
test/benchmark/list_reverse/list_reverse.py
Normal 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')
|
15
test/benchmark/list_reverse/list_reverse.rb
Normal file
15
test/benchmark/list_reverse/list_reverse.rb
Normal 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'
|
15
test/benchmark/loop/loop.js
Normal file
15
test/benchmark/loop/loop.js
Normal 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');
|
@ -3,9 +3,13 @@ from lang import clock
|
||||
start = clock()
|
||||
|
||||
list = []
|
||||
for i in 0..10000000 do list_append(list, i) end
|
||||
for i in 0..10000000
|
||||
list_append(list, i)
|
||||
end
|
||||
sum = 0
|
||||
for i in list do sum += i end
|
||||
for i in list
|
||||
sum += i
|
||||
end
|
||||
print(sum)
|
||||
|
||||
print("elapsed:", clock() - start)
|
@ -8,4 +8,4 @@ sum = 0
|
||||
for i in list: sum += i
|
||||
print(sum)
|
||||
|
||||
print("elapsed:", clock() - start)
|
||||
print("elapsed:", clock() - start, 's')
|
19
test/benchmark/primes/primes.pk
Normal file
19
test/benchmark/primes/primes.pk
Normal 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")
|
16
test/benchmark/primes/primes.py
Normal file
16
test/benchmark/primes/primes.py
Normal 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")
|
19
test/benchmark/primes/primes.rb
Normal file
19
test/benchmark/primes/primes.rb
Normal 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"
|
||||
|
20
test/benchmark/primes/primes.wren
Normal file
20
test/benchmark/primes/primes.wren
Normal 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)")
|
||||
|
@ -14,4 +14,5 @@ def fib(n)
|
||||
end
|
||||
|
||||
fib(10)
|
||||
print(res)
|
||||
assert(res == '0 1 1 2 3 5 8 13 21 34 ')
|
@ -1,21 +1,25 @@
|
||||
|
||||
## Numericals tests.
|
||||
assert(1.0 + 2.0 == 3, 'Num addition failed')
|
||||
|
||||
## Strings tests.
|
||||
assert("'\"'" == '\'"\'', 'Mixed string escape failed.')
|
||||
assert("testing" == "test" + "ing", 'String addition failed.')
|
||||
s = "foo"; s += "bar"
|
||||
assert(s == "foobar")
|
||||
assert( 1 + 2 * 3 == 7)
|
||||
assert((1 + 2)* 3 == 9)
|
||||
assert(42 % 40.0 == 2)
|
||||
assert("'\"'" == '\'"\'')
|
||||
assert("testing" == "test" + "ing")
|
||||
|
||||
## Lists test.
|
||||
l1 = [1, false, null, func print('hello') end]
|
||||
assert(is_null(l1[2]), 'List indexing failed.')
|
||||
l1[1] = true; assert(l1[1], 'List subscript set failed.')
|
||||
assert(is_null(l1[2]))
|
||||
l1[1] = true; assert(l1[1])
|
||||
|
||||
## Builtin functions tests.
|
||||
h1 = hash("testing"); h2 = hash("test" + "ing")
|
||||
assert(h1 == h2, 'Hash function failed.')
|
||||
assert(to_string(42) == '42', 'to_string() failed.')
|
||||
assert(to_string(42) == '42')
|
||||
|
||||
## 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
|
||||
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 or b then val = 42 else assert(false) end assert(val == 42)
|
||||
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":{...}}')
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user