Native types & File object implementations.

This commit is contained in:
Thakee Nathees 2021-06-21 18:32:20 +05:30
parent a21bd98945
commit 600f972927
29 changed files with 932 additions and 460 deletions

View File

@ -1,34 +0,0 @@
/*
* Copyright (c) 2020-2021 Thakee Nathees
* Distributed Under The MIT License
*/
// This file will include all source files from the cli sub-directories here
// (thirdparty/modules) into a single file. That'll make it easy to compile
// with the command `gcc cli/*.c ...` instead of having to add every single
// sub-directory to the list of source directory.
/*****************************************************************************/
/* THIRD PARTY */
/*****************************************************************************/
// Library : cwalk
// Source : https://github.com/likle/cwalk
// Doc : https://likle.github.io/cwalk/
// About : Path library for C/C++. Cross-Platform for Windows, MacOS and
// Linux. Supports UNIX and Windows path styles on those platforms.
#include "modules/thirdparty/cwalk/cwalk.c"
/*****************************************************************************/
/* CLI MODULES */
/*****************************************************************************/
#include "modules/path.c"
/*****************************************************************************/
/* MODULES REGISTER */
/*****************************************************************************/
void registerModules(PKVM* vm) {
registerModulePath(vm);
}

View File

@ -3,38 +3,40 @@
* Distributed Under The MIT License * Distributed Under The MIT License
*/ */
#ifndef COMMON_H // A collection of reusable macros that pocketlang use. This file doesn't have
#define COMMON_H // any dependencies, you can just drag and drop this file in your project if
// you want to use these macros.
#include <stdlib.h> #ifndef PK_COMMON_H
#include <string.h> #define PK_COMMON_H
#include <stdint.h>
#include <stdio.h>
#include <assert.h>
#include <pocketlang.h> #ifndef __has_builtin
#define __has_builtin(x) 0
#endif
#define CLI_NOTICE \ #if defined(__GNUC__)
"PocketLang " PK_VERSION_STRING " (https://github.com/ThakeeNathees/pocketlang/)\n" \ #pragma GCC diagnostic ignored "-Wint-to-pointer-cast"
"Copyright(c) 2020 - 2021 ThakeeNathees.\n" \ #pragma GCC diagnostic ignored "-Wunused-parameter"
"Free and open source software under the terms of the MIT license.\n" #elif defined(__clang__)
#pragma clang diagnostic ignored "-Wint-to-pointer-cast"
#pragma clang diagnostic ignored "-Wunused-parameter"
#endif
// Note that the cli itself is not a part of the pocketlang compiler, instead #include <stdio.h> //< Only needed here for ASSERT() macro and for release mode
// it's a host application to run pocketlang from the command line. We're //< TODO; macro use this to print a crash report.
// embedding the pocketlang VM and we can only use its public APIs, not any
// internals of it, including assertion macros. So we're re-defining those
// macros here (like if it's a new project).
/*****************************************************************************/ #define TOSTRING(x) #x
/* COMMON MACROS */ #define STRINGIFY(x) TOSTRING(x)
/*****************************************************************************/
// These macros below are copied from pocketlang at "src/common.h". See above // CONCAT_LINE(X) will result evaluvated X<__LINE__>.
// for not re-using these macros. #define __CONCAT_LINE_INTERNAL_R(a, b) a ## b
#define __CONCAT_LINE_INTERNAL_F(a, b) __CONCAT_LINE_INTERNAL_R(a, b)
#define CONCAT_LINE(X) __CONCAT_LINE_INTERNAL_F(X, __LINE__)
// 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. // use __ASSERT() for TODOs and assertions in public methods (to indicate that
// the host application did something wrong).
#define __ASSERT(condition, message) \ #define __ASSERT(condition, message) \
do { \ do { \
if (!(condition)) { \ if (!(condition)) { \
@ -55,6 +57,11 @@
#define DEBUG_BREAK() __builtin_trap() #define DEBUG_BREAK() __builtin_trap()
#endif #endif
// This will terminate the compilation if the [condition] is false, because of
// char _assertion_failure_<__LINE__>[-1] evaluated.
#define STATIC_ASSERT(condition) \
static char CONCAT_LINE(_assertion_failure_)[2*!!(condition) - 1]
#define ASSERT(condition, message) __ASSERT(condition, message) #define ASSERT(condition, message) __ASSERT(condition, message)
#define ASSERT_INDEX(index, size) \ #define ASSERT_INDEX(index, size) \
@ -70,41 +77,77 @@
#else #else
#define STATIC_ASSERT(condition) NO_OP
#define DEBUG_BREAK() NO_OP #define DEBUG_BREAK() NO_OP
#define ASSERT(condition, message) NO_OP #define ASSERT(condition, message) NO_OP
#define ASSERT_INDEX(index, size) NO_OP #define ASSERT_INDEX(index, size) NO_OP
// Reference : https://github.com/wren-lang/ #if defined(_MSC_VER)
#if defined( _MSC_VER )
#define UNREACHABLE() __assume(0) #define UNREACHABLE() __assume(0)
#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) #elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5))
#define UNREACHABLE() __builtin_unreachable() #define UNREACHABLE() __builtin_unreachable()
#elif defined(__EMSCRIPTEN__) || defined(__clang__)
#if __has_builtin(__builtin_unreachable)
#define UNREACHABLE() __builtin_unreachable()
#else
#define UNREACHABLE() NO_OP
#endif
#else #else
#define UNREACHABLE() NO_OP #define UNREACHABLE() NO_OP
#endif #endif
#endif // DEBUG #endif // DEBUG
#if defined( _MSC_VER ) #if defined(_MSC_VER)
#define forceinline __forceinline #define forceinline __forceinline
#else #else
#define forceinline __attribute__((always_inline)) #define forceinline __attribute__((always_inline))
#endif #endif
// Using __ASSERT() for make it to crash in release binary too. // Using __ASSERT() for make it crash in release binary too.
#define TODO __ASSERT(false, "TODO: It hasn't been implemented yet.") #define TODO __ASSERT(false, "TODO: It hasn't implemented yet.")
#define OOPS "Oops a bug!! report please." #define OOPS "Oops a bug!! report please."
#define TOSTRING(x) #x // The formated string to convert double to string. It'll be with the minimum
#define STRINGIFY(x) TOSTRING(x) // length string representation of either a regular float or a scientific
// notation (at most 15 decimal points).
// Reference: https://www.cplusplus.com/reference/cstdio/printf/
#define DOUBLE_FMT "%.16g"
/*****************************************************************************/ // Double number to string buffer size, used in sprintf with DOUBLE_FMT.
/* CLI DEFINES */ // A largest number : "-1.234567890123456e+308"
/*****************************************************************************/ // + 1 fot sign '+' or '-'
// + 16 fot significant digits
// + 1 for decimal point '.'
// + 1 for exponent char 'e'
// + 1 for sign of exponent
// + 3 for the exponent digits
// + 1 for null byte '\0'
#define STR_DBL_BUFF_SIZE 24
// FIXME: the vm user data of cli. // Integer number to string buffer size, used in sprintf with format "%d".
typedef struct { // The minimum 32 bit integer = -2147483648
bool repl_mode; // + 1 for sign '-'
} VmUserData; // + 10 for digits
// + 1 for null byte '\0'
#define STR_INT_BUFF_SIZE 12
#endif // COMMON_H // Integer number (double) to hex string buffer size.
// The maximum value an unsigned 64 bit integer can get is
// 0xffffffffffffffff which is 16 characters.
// + 16 for hex digits
// + 1 for sign '-'
// + 2 for '0x' prefix
// + 1 for null byte '\0'
#define STR_HEX_BUFF_SIZE 20
// Integer number (double) to bin string buffer size.
// The maximum value an unsigned 64 bit integer can get is 0b11111... 64 1s.
// + 64 for bin digits
// + 1 for sign '-'
// + 2 for '0b' prefix
// + 1 for null byte '\0'
#define STR_BIN_BUFF_SIZE 68
#endif //PK_COMMON_H

37
cli/internal.h Normal file
View File

@ -0,0 +1,37 @@
/*
* Copyright (c) 2020-2021 Thakee Nathees
* Distributed Under The MIT License
*/
#ifndef COMMON_H
#define COMMON_H
#include <pocketlang.h>
#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdio.h>
// Note that the cli itself is not a part of the pocketlang compiler, instead
// it's a host application to run pocketlang from the command line. We're
// embedding the pocketlang VM and we can only use its public APIs, not any
// internals of it, including assertion macros. So that we've copyied the
// "common.h" header. This can be moved to "src/include/common.h" and include
// as optional header, which is something I don't like because it makes
// pocketlang contain 2 headers (we'll always try to be minimal).
#include "common.h"
#define CLI_NOTICE \
"PocketLang " PK_VERSION_STRING " (https://github.com/ThakeeNathees/pocketlang/)\n" \
"Copyright(c) 2020 - 2021 ThakeeNathees.\n" \
"Free and open source software under the terms of the MIT license.\n"
// FIXME: the vm user data of cli.
typedef struct {
bool repl_mode;
} VmUserData;
#endif // COMMON_H

View File

@ -3,22 +3,15 @@
* Distributed Under The MIT License * Distributed Under The MIT License
*/ */
#include "common.h" #include "internal.h"
#include "modules.h"
// FIXME: Everything below here is temporary and for testing. // FIXME: Everything below here is temporary and for testing.
void repl(PKVM* vm, const PkCompileOptions* options); void repl(PKVM* vm, const PkCompileOptions* options);
const char* read_line(uint32_t* length); const char* read_line(uint32_t* length);
void registerModules(PKVM* vm);
// Path public functions (TODO: should I add a header for that?)
bool pathIsAbsolute(const char* path);
void pathGetDirName(const char* path, size_t* length);
size_t pathNormalize(const char* path, char* buff, size_t buff_size);
size_t pathJoin(const char* from, const char* path, char* buffer,
size_t buff_size);
// --------------------------------------- // ---------------------------------------
void onResultDone(PKVM* vm, PkStringPtr result) { void onResultDone(PKVM* vm, PkStringPtr result) {
@ -143,6 +136,10 @@ int main(int argc, char** argv) {
config.error_fn = errorFunction; config.error_fn = errorFunction;
config.write_fn = writeFunction; config.write_fn = writeFunction;
config.read_fn = readFunction; config.read_fn = readFunction;
config.free_inst_fn = freeObj;
config.inst_name_fn = getObjName;
config.load_script_fn = loadScript; config.load_script_fn = loadScript;
config.resolve_path_fn = resolvePath; config.resolve_path_fn = resolvePath;

405
cli/modules.c Normal file
View File

@ -0,0 +1,405 @@
/*
* Copyright (c) 2021 Thakee Nathees
* Distributed Under The MIT License
*/
#include "modules.h"
// Allocate a new module object of type [Ty].
#define NEW_OBJ(Ty) (Ty*)malloc(sizeof(Ty))
// Dellocate module object, allocated by NEW_OBJ(). Called by the freeObj
// callback.
#define FREE_OBJ(ptr) free(ptr)
void freeObj(PKVM* vm, void* instance) {
Obj* obj = (Obj*)instance;
// TODO: assert obj type is valid.
// If the file isn't closed, close it to flush it's buffer.
if (obj->type == OBJ_FILE) {
File* file = (File*)obj;
if (!file->closed) {
if (fclose(file->fp) != 0) { /* TODO: error! */ }
file->closed = true;
}
}
FREE_OBJ(obj);
}
const char* getObjName(uint32_t id) {
switch ((ObjType)id) {
case OBJ_FILE: return "File";
}
return NULL;
}
/*****************************************************************************/
/* PATH MODULE */
/*****************************************************************************/
#include "thirdparty/cwalk/cwalk.h"
#if defined(_WIN32) && (defined(_MSC_VER) || defined(__TINYC__))
#include "thirdparty/dirent/dirent.h"
#else
#include <dirent.h>
#endif
#if defined(_WIN32)
#include <windows.h>
#include <direct.h>
#define get_cwd _getcwd
#else
#include <unistd.h>
#define get_cwd getcwd
#endif
// The cstring pointer buffer size used in path.join(p1, p2, ...). Tune this
// value as needed.
#define MAX_JOIN_PATHS 8
/*****************************************************************************/
/* PUBLIC FUNCTIONS */
/*****************************************************************************/
bool pathIsAbsolute(const char* path) {
return cwk_path_is_absolute(path);
}
void pathGetDirName(const char* path, size_t* length) {
cwk_path_get_dirname(path, length);
}
size_t pathNormalize(const char* path, char* buff, size_t buff_size) {
return cwk_path_normalize(path, buff, buff_size);
}
size_t pathJoin(const char* path_a, const char* path_b, char* buffer,
size_t buff_size) {
return cwk_path_join(path_a, path_b, buffer, buff_size);
}
/*****************************************************************************/
/* PATH INTERNAL FUNCTIONS */
/*****************************************************************************/
static inline bool pathIsFileExists(const char* path) {
FILE* file = fopen(path, "r");
if (file != NULL) {
fclose(file);
return true;
}
return false;
}
// Reference: https://stackoverflow.com/a/12510903/10846399
static inline bool pathIsDirectoryExists(const char* path) {
DIR* dir = opendir(path);
if (dir) { /* Directory exists. */
closedir(dir);
return true;
} else if (errno == ENOENT) { /* Directory does not exist. */
} else { /* opendir() failed for some other reason. */
}
return false;
}
static inline bool pathIsExists(const char* path) {
return pathIsFileExists(path) || pathIsDirectoryExists(path);
}
static inline size_t pathAbs(const char* path, char* buff, size_t buff_size) {
char cwd[FILENAME_MAX];
if (get_cwd(cwd, sizeof(cwd)) == NULL) {
// TODO: handle error.
}
return cwk_path_get_absolute(cwd, path, buff, buff_size);
}
/*****************************************************************************/
/* PATH MODULES FUNCTIONS */
/*****************************************************************************/
static void _pathSetStyleUnix(PKVM* vm) {
bool value;
if (!pkGetArgBool(vm, 1, &value)) return;
cwk_path_set_style((value) ? CWK_STYLE_UNIX : CWK_STYLE_WINDOWS);
}
static void _pathGetCWD(PKVM* vm) {
char cwd[FILENAME_MAX];
if (get_cwd(cwd, sizeof(cwd)) == NULL) {
// TODO: Handle error.
}
pkReturnString(vm, cwd);
}
static void _pathAbspath(PKVM* vm) {
const char* path;
if (!pkGetArgString(vm, 1, &path, NULL)) return;
char abspath[FILENAME_MAX];
size_t len = pathAbs(path, abspath, sizeof(abspath));
pkReturnStringLength(vm, abspath, len);
}
static void _pathRelpath(PKVM* vm) {
const char* from, * path;
if (!pkGetArgString(vm, 1, &from, NULL)) return;
if (!pkGetArgString(vm, 2, &path, NULL)) return;
char abs_from[FILENAME_MAX];
pathAbs(from, abs_from, sizeof(abs_from));
char abs_path[FILENAME_MAX];
pathAbs(path, abs_path, sizeof(abs_path));
char result[FILENAME_MAX];
size_t len = cwk_path_get_relative(abs_from, abs_path,
result, sizeof(result));
pkReturnStringLength(vm, result, len);
}
static void _pathJoin(PKVM* vm) {
const char* paths[MAX_JOIN_PATHS + 1]; // +1 for NULL.
int argc = pkGetArgc(vm);
if (argc > MAX_JOIN_PATHS) {
pkSetRuntimeError(vm, "Cannot join more than " STRINGIFY(MAX_JOIN_PATHS)
"paths.");
return;
}
for (int i = 0; i < argc; i++) {
pkGetArgString(vm, i + 1, &paths[i], NULL);
}
paths[argc] = NULL;
char result[FILENAME_MAX];
size_t len = cwk_path_join_multiple(paths, result, sizeof(result));
pkReturnStringLength(vm, result, len);
}
static void _pathNormalize(PKVM* vm) {
const char* path;
if (!pkGetArgString(vm, 1, &path, NULL)) return;
char result[FILENAME_MAX];
size_t len = cwk_path_normalize(path, result, sizeof(result));
pkReturnStringLength(vm, result, len);
}
static void _pathBaseName(PKVM* vm) {
const char* path;
if (!pkGetArgString(vm, 1, &path, NULL)) return;
const char* base_name;
size_t length;
cwk_path_get_basename(path, &base_name, &length);
pkReturnString(vm, base_name);
}
static void _pathDirName(PKVM* vm) {
const char* path;
if (!pkGetArgString(vm, 1, &path, NULL)) return;
size_t length;
cwk_path_get_dirname(path, &length);
pkReturnStringLength(vm, path, length);
}
static void _pathIsPathAbs(PKVM* vm) {
const char* path;
if (!pkGetArgString(vm, 1, &path, NULL)) return;
pkReturnBool(vm, cwk_path_is_absolute(path));
}
static void _pathGetExtension(PKVM* vm) {
const char* path;
if (!pkGetArgString(vm, 1, &path, NULL)) return;
const char* ext;
size_t length;
if (cwk_path_get_extension(path, &ext, &length)) {
pkReturnStringLength(vm, ext, length);
} else {
pkReturnStringLength(vm, NULL, 0);
}
}
static void _pathExists(PKVM* vm) {
const char* path;
if (!pkGetArgString(vm, 1, &path, NULL)) return;
pkReturnBool(vm, pathIsExists(path));
}
static void _pathIsFile(PKVM* vm) {
const char* path;
if (!pkGetArgString(vm, 1, &path, NULL)) return;
pkReturnBool(vm, pathIsFileExists(path));
}
static void _pathIsDir(PKVM* vm) {
const char* path;
if (!pkGetArgString(vm, 1, &path, NULL)) return;
pkReturnBool(vm, pathIsDirectoryExists(path));
}
void registerModulePath(PKVM* vm) {
PkHandle* path = pkNewModule(vm, "path");
pkModuleAddFunction(vm, path, "setunix", _pathSetStyleUnix, 1);
pkModuleAddFunction(vm, path, "getcwd", _pathGetCWD, 0);
pkModuleAddFunction(vm, path, "abspath", _pathAbspath, 1);
pkModuleAddFunction(vm, path, "relpath", _pathRelpath, 2);
pkModuleAddFunction(vm, path, "join", _pathJoin, -1);
pkModuleAddFunction(vm, path, "normalize", _pathNormalize, 1);
pkModuleAddFunction(vm, path, "basename", _pathBaseName, 1);
pkModuleAddFunction(vm, path, "dirname", _pathDirName, 1);
pkModuleAddFunction(vm, path, "isabspath", _pathIsPathAbs, 1);
pkModuleAddFunction(vm, path, "getext", _pathGetExtension, 1);
pkModuleAddFunction(vm, path, "exists", _pathExists, 1);
pkModuleAddFunction(vm, path, "isfile", _pathIsFile, 1);
pkModuleAddFunction(vm, path, "isdir", _pathIsDir, 1);
pkReleaseHandle(vm, path);
}
/*****************************************************************************/
/* FILE MODULE */
/*****************************************************************************/
static void _fileOpen(PKVM* vm) {
// TODO: handle arg range using pocketlang native api.
// 1 <= argc <= 2
int argc = pkGetArgc(vm);
if (argc == 0) {
pkSetRuntimeError(vm, "Expected at least 1 argument");
return;
} else if (argc > 2) {
pkSetRuntimeError(vm, "Expected at least 2 arguments");
return;
}
const char* path;
if (!pkGetArgString(vm, 1, &path, NULL)) return;
const char* mode_str = "r";
FileAccessMode mode = FMODE_READ;
if (argc == 2) {
if (!pkGetArgString(vm, 2, &mode_str, NULL)) return;
// Check if the mode string is valid, and update the mode value.
do {
if (strcmp(mode_str, "r") == 0) { mode = FMODE_READ; break; }
if (strcmp(mode_str, "w") == 0) { mode = FMODE_WRITE; break; }
if (strcmp(mode_str, "a") == 0) { mode = FMODE_APPEND; break; }
if (strcmp(mode_str, "r+") == 0) { mode = FMODE_READ_EXT; break; }
if (strcmp(mode_str, "w+") == 0) { mode = FMODE_WRITE_EXT; break; }
if (strcmp(mode_str, "a+") == 0) { mode = FMODE_APPEND_EXT; break; }
// TODO: (fmt, ...) va_arg for runtime error public api.
// If we reached here, that means it's an invalid mode string.
pkSetRuntimeError(vm, "Invalid mode string");
return;
} while (false);
}
FILE* fp = fopen(path, mode_str);
if (fp != NULL) {
File* file = NEW_OBJ(File);
file->fp = fp;
file->mode = mode;
file->closed = false;
pkReturnInstNative(vm, (void*)file, OBJ_FILE);
} else {
pkReturnNull(vm);
}
}
static void _fileRead(PKVM* vm) {
File* file;
if (!pkGetArgInst(vm, 1, OBJ_FILE, (void**)&file)) return;
if (file->closed) {
pkSetRuntimeError(vm, "Cannot read from a closed file.");
return;
}
if ((file->mode != FMODE_READ) && ((_FMODE_EXT & file->mode) == 0)) {
pkSetRuntimeError(vm, "File is not readable.");
return;
}
// TODO: this is temproary.
char buff[2048];
fread((void*)buff, sizeof(char), sizeof(buff), file->fp);
pkReturnString(vm, (const char*)buff);
}
static void _fileWrite(PKVM* vm) {
File* file;
const char* text; uint32_t length;
if (!pkGetArgInst(vm, 1, OBJ_FILE, (void**)&file)) return;
if (!pkGetArgString(vm, 2, &text, &length)) return;
if (file->closed) {
pkSetRuntimeError(vm, "Cannot write to a closed file.");
return;
}
if ((file->mode != FMODE_WRITE) && ((_FMODE_EXT & file->mode) == 0)) {
pkSetRuntimeError(vm, "File is not writable.");
return;
}
fwrite(text, sizeof(char), (size_t)length, file->fp);
}
static void _fileClose(PKVM* vm) {
File* file;
if (!pkGetArgInst(vm, 1, OBJ_FILE, (void**)&file)) return;
if (file->closed) {
pkSetRuntimeError(vm, "File already closed.");
return;
}
if (fclose(file->fp) != 0) {
pkSetRuntimeError(vm, "fclose() failed!\n" \
" at " __FILE__ ":" STRINGIFY(__LINE__) ".");
}
file->closed = true;
}
void registerModuleFile(PKVM* vm) {
PkHandle* file = pkNewModule(vm, "File");
pkModuleAddFunction(vm, file, "open", _fileOpen, -1);
pkModuleAddFunction(vm, file, "read", _fileRead, 1);
pkModuleAddFunction(vm, file, "write", _fileWrite, 2);
pkModuleAddFunction(vm, file, "close", _fileClose, 1);
pkReleaseHandle(vm, file);
}
/*****************************************************************************/
/* REGISTER MODULES */
/*****************************************************************************/
void registerModules(PKVM* vm) {
registerModuleFile(vm);
registerModulePath(vm);
}

73
cli/modules.h Normal file
View File

@ -0,0 +1,73 @@
/*
* Copyright (c) 2021 Thakee Nathees
* Distributed Under The MIT License
*/
#include "internal.h"
/*****************************************************************************/
/* MODULE OBJECTS */
/*****************************************************************************/
// Type enums of cli module objects.
typedef enum {
OBJ_FILE = 1,
} ObjType;
// The abstract type of the objects.
typedef struct {
ObjType type;
} Obj;
// File access mode.
typedef enum {
FMODE_READ = (1 << 0),
FMODE_WRITE = (1 << 1),
FMODE_APPEND = (1 << 2),
_FMODE_EXT = (1 << 3),
FMODE_READ_EXT = (_FMODE_EXT | FMODE_READ),
FMODE_WRITE_EXT = (_FMODE_EXT | FMODE_WRITE),
FMODE_APPEND_EXT = (_FMODE_EXT | FMODE_APPEND),
} FileAccessMode;
// Str | If already exists | If does not exist |
// -----+-------------------+-------------------|
// 'r' | read from start | failure to open |
// 'w' | destroy contents | create new |
// 'a' | write to end | create new |
// 'r+' | read from start | error |
// 'w+' | destroy contents | create new |
// 'a+' | write to end | create new |
// A wrapper around the FILE* for the File module.
typedef struct {
Obj _super;
FILE* fp; // C file poinnter.
FileAccessMode mode; // Access mode of the file.
bool closed; // True if the file isn't closed yet.
} File;
/*****************************************************************************/
/* MODULE PUBLIC FUNCTIONS */
/*****************************************************************************/
// The free callback of the object, that'll called by pocketlang when a
// pocketlang native instance garbage collected.
void freeObj(PKVM* vm, void* instance);
// The native instance get_name callback used to get the name of a native
// instance from pocketlang. Here the id we're using is the ObjType enum.
const char* getObjName(uint32_t id);
// Registers all the cli modules.
void registerModules(PKVM* vm);
// 'path' moudle public functions used at various cli functions.
bool pathIsAbsolute(const char* path);
void pathGetDirName(const char* path, size_t* length);
size_t pathNormalize(const char* path, char* buff, size_t buff_size);
size_t pathJoin(const char* from, const char* path, char* buffer,
size_t buff_size);

View File

@ -1,248 +0,0 @@
/*
* Copyright (c) 2021 Thakee Nathees
* Distributed Under The MIT License
*/
#include <errno.h>
#include <pocketlang.h>
#include <stdio.h> /* defines FILENAME_MAX */
#include "thirdparty/cwalk/cwalk.h"
#if defined(_WIN32) && (defined(_MSC_VER) || defined(__TINYC__))
#include "thirdparty/dirent/dirent.h"
#else
#include <dirent.h>
#endif
#if defined(_WIN32)
#include <windows.h>
#include <direct.h>
#define get_cwd _getcwd
#else
#include <unistd.h>
#define get_cwd getcwd
#endif
// TODO: this macros should be moved to a general place of in cli.
#define TOSTRING(x) #x
#define STRINGIFY(x) TOSTRING(x)
// The cstring pointer buffer size used in path.join(p1, p2, ...). Tune this
// value as needed.
#define MAX_JOIN_PATHS 8
/*****************************************************************************/
/* PUBLIC FUNCTIONS */
/*****************************************************************************/
bool pathIsAbsolute(const char* path) {
return cwk_path_is_absolute(path);
}
void pathGetDirName(const char* path, size_t* length) {
cwk_path_get_dirname(path, length);
}
size_t pathNormalize(const char* path, char* buff, size_t buff_size) {
return cwk_path_normalize(path, buff, buff_size);
}
size_t pathJoin(const char* path_a, const char* path_b, char* buffer,
size_t buff_size) {
return cwk_path_join(path_a, path_b, buffer, buff_size);
}
/*****************************************************************************/
/* INTERNAL FUNCTIONS */
/*****************************************************************************/
static inline bool pathIsFileExists(const char* path) {
FILE* file = fopen(path, "r");
if (file != NULL) {
fclose(file);
return true;
}
return false;
}
// Reference: https://stackoverflow.com/a/12510903/10846399
static inline bool pathIsDirectoryExists(const char* path) {
DIR* dir = opendir(path);
if (dir) { /* Directory exists. */
closedir(dir);
return true;
} else if (errno == ENOENT) { /* Directory does not exist. */
} else { /* opendir() failed for some other reason. */
}
return false;
}
static inline bool pathIsExists(const char* path) {
return pathIsFileExists(path) || pathIsDirectoryExists(path);
}
static inline size_t pathAbs(const char* path, char* buff, size_t buff_size) {
char cwd[FILENAME_MAX];
if (get_cwd(cwd, sizeof(cwd)) == NULL) {
// TODO: handle error.
}
return cwk_path_get_absolute(cwd, path, buff, buff_size);
}
/*****************************************************************************/
/* MODULE FUNCTIONS */
/*****************************************************************************/
static void _pathSetStyleUnix(PKVM* vm) {
bool value;
if (!pkGetArgBool(vm, 1, &value)) return;
cwk_path_set_style((value)? CWK_STYLE_UNIX : CWK_STYLE_WINDOWS);
}
static void _pathGetCWD(PKVM* vm) {
char cwd[FILENAME_MAX];
if (get_cwd(cwd, sizeof(cwd)) == NULL) {
// TODO: Handle error.
}
pkReturnString(vm, cwd);
}
static void _pathAbspath(PKVM* vm) {
const char* path;
if (!pkGetArgString(vm, 1, &path)) return;
char abspath[FILENAME_MAX];
size_t len = pathAbs(path, abspath, sizeof(abspath));
pkReturnStringLength(vm, abspath, len);
}
static void _pathRelpath(PKVM* vm) {
const char *from, *path;
if (!pkGetArgString(vm, 1, &from)) return;
if (!pkGetArgString(vm, 2, &path)) return;
char abs_from[FILENAME_MAX];
pathAbs(from, abs_from, sizeof(abs_from));
char abs_path[FILENAME_MAX];
pathAbs(path, abs_path, sizeof(abs_path));
char result[FILENAME_MAX];
size_t len = cwk_path_get_relative(abs_from, abs_path,
result, sizeof(result));
pkReturnStringLength(vm, result, len);
}
static void _pathJoin(PKVM* vm) {
const char* paths[MAX_JOIN_PATHS + 1]; // +1 for NULL.
int argc = pkGetArgc(vm);
if (argc > MAX_JOIN_PATHS) {
pkSetRuntimeError(vm, "Cannot join more than " STRINGIFY(MAX_JOIN_PATHS)
"paths.");
return;
}
for (int i = 0; i < argc; i++) {
pkGetArgString(vm, i+1, &paths[i]);
}
paths[argc] = NULL;
char result[FILENAME_MAX];
size_t len = cwk_path_join_multiple(paths, result, sizeof(result));
pkReturnStringLength(vm, result, len);
}
static void _pathNormalize(PKVM* vm) {
const char* path;
if (!pkGetArgString(vm, 1, &path)) return;
char result[FILENAME_MAX];
size_t len = cwk_path_normalize(path, result, sizeof(result));
pkReturnStringLength(vm, result, len);
}
static void _pathBaseName(PKVM* vm) {
const char* path;
if (!pkGetArgString(vm, 1, &path)) return;
const char* base_name;
size_t length;
cwk_path_get_basename(path, &base_name, &length);
pkReturnString(vm, base_name);
}
static void _pathDirName(PKVM* vm) {
const char* path;
if (!pkGetArgString(vm, 1, &path)) return;
size_t length;
cwk_path_get_dirname(path, &length);
pkReturnStringLength(vm, path, length);
}
static void _pathIsPathAbs(PKVM* vm) {
const char* path;
if (!pkGetArgString(vm, 1, &path)) return;
pkReturnBool(vm, cwk_path_is_absolute(path));
}
static void _pathGetExtension(PKVM* vm) {
const char* path;
if (!pkGetArgString(vm, 1, &path)) return;
const char* ext;
size_t length;
if (cwk_path_get_extension(path, &ext, &length)) {
pkReturnStringLength(vm, ext, length);
} else {
pkReturnStringLength(vm, NULL, 0);
}
}
static void _pathExists(PKVM* vm) {
const char* path;
if (!pkGetArgString(vm, 1, &path)) return;
pkReturnBool(vm, pathIsExists(path));
}
static void _pathIsFile(PKVM* vm) {
const char* path;
if (!pkGetArgString(vm, 1, &path)) return;
pkReturnBool(vm, pathIsFileExists(path));
}
static void _pathIsDir(PKVM* vm) {
const char* path;
if (!pkGetArgString(vm, 1, &path)) return;
pkReturnBool(vm, pathIsDirectoryExists(path));
}
/*****************************************************************************/
/* REGISTER MODULE */
/*****************************************************************************/
void registerModulePath(PKVM* vm) {
PkHandle* path = pkNewModule(vm, "path");
pkModuleAddFunction(vm, path, "setunix", _pathSetStyleUnix, 1);
pkModuleAddFunction(vm, path, "getcwd", _pathGetCWD, 0);
pkModuleAddFunction(vm, path, "abspath", _pathAbspath, 1);
pkModuleAddFunction(vm, path, "relpath", _pathRelpath, 2);
pkModuleAddFunction(vm, path, "join", _pathJoin, -1);
pkModuleAddFunction(vm, path, "normalize", _pathNormalize, 1);
pkModuleAddFunction(vm, path, "basename", _pathBaseName, 1);
pkModuleAddFunction(vm, path, "dirname", _pathDirName, 1);
pkModuleAddFunction(vm, path, "isabspath", _pathIsPathAbs, 1);
pkModuleAddFunction(vm, path, "getext", _pathGetExtension, 1);
pkModuleAddFunction(vm, path, "exists", _pathExists, 1);
pkModuleAddFunction(vm, path, "isfile", _pathIsFile, 1);
pkModuleAddFunction(vm, path, "isdir", _pathIsDir, 1);
pkReleaseHandle(vm, path);
}

View File

@ -6,7 +6,7 @@
// The REPL (Read Evaluate Print Loop) implementation. // The REPL (Read Evaluate Print Loop) implementation.
// https://en.wikipedia.org/wiki/Read-eval-print_loop. // https://en.wikipedia.org/wiki/Read-eval-print_loop.
#include "common.h" #include "internal.h"
#include <ctype.h> // isspace #include <ctype.h> // isspace
#include "utils.h" #include "utils.h"
@ -20,7 +20,9 @@
const char* read_line(uint32_t* length) { const char* read_line(uint32_t* length) {
const int size = 1024; const int size = 1024;
char* mem = (char*)malloc(size); char* mem = (char*)malloc(size);
fgets(mem, size, stdin); if (fgets(mem, size, stdin) == NULL) {
// TODO: handle error.
}
size_t len = strlen(mem); size_t len = strlen(mem);
// FIXME: handle \r\n, this is temp. // FIXME: handle \r\n, this is temp.
@ -61,7 +63,7 @@ int repl(PKVM* vm, const PkCompileOptions* options) {
user_data->repl_mode = true; user_data->repl_mode = true;
// Print the copyright and license notice. // Print the copyright and license notice.
printf("%s\n", CLI_NOTICE); printf("%s", CLI_NOTICE);
// The main module that'll be used to compile and execute the input source. // The main module that'll be used to compile and execute the input source.
PkHandle* module = pkNewModule(vm, "$(REPL)"); PkHandle* module = pkNewModule(vm, "$(REPL)");

17
cli/thirdparty.c Normal file
View File

@ -0,0 +1,17 @@
/*
* Copyright (c) 2020-2021 Thakee Nathees
* Distributed Under The MIT License
*/
// This file will include all thirdparty source files from the thirdparty sub
// directories here into a single file. That'll make it easy to compile with
// the command `gcc cli/*.c ...` instead of having to add every single sub
// directory to the list of source directory.
// Library : cwalk
// Source : https://github.com/likle/cwalk
// Doc : https://likle.github.io/cwalk/
// About : Path library for C/C++. Cross-Platform for Windows, MacOS and
// Linux. Supports UNIX and Windows path styles on those platforms.
#include "thirdparty/cwalk/cwalk.c"

View File

@ -3,7 +3,7 @@
* Distributed Under The MIT License * Distributed Under The MIT License
*/ */
#include "common.h" #include "internal.h"
typedef struct { typedef struct {
uint8_t* data; uint8_t* data;

View File

@ -157,6 +157,17 @@ typedef void (*pkWriteFn) (PKVM* vm, const char* text);
// contain a line ending (\n or \r\n). // contain a line ending (\n or \r\n).
typedef PkStringPtr (*pkReadFn) (PKVM* vm); typedef PkStringPtr (*pkReadFn) (PKVM* vm);
// A function callback, that'll be called when a native instance (wrapper) is
// freed by by the garbage collector, to indicate that pocketlang is done with
// the native instance.
typedef void (*pkFreeInstFn) (PKVM* vm, void* instance);
// A function callback to get the name of the native instance from pocketlang,
// using it's [id]. The returned string won't be copied by pocketlang so it's
// expected to be alived since the instance is alive and recomended to return
// a C literal string.
typedef const char* (*pkInstNameFn) (uint32_t id);
// A function callback symbol for clean/free the pkStringResult. // A function callback symbol for clean/free the pkStringResult.
typedef void (*pkResultDoneFn) (PKVM* vm, PkStringPtr result); typedef void (*pkResultDoneFn) (PKVM* vm, PkStringPtr result);
@ -278,6 +289,9 @@ struct PkConfiguration {
pkWriteFn write_fn; pkWriteFn write_fn;
pkReadFn read_fn; pkReadFn read_fn;
pkFreeInstFn free_inst_fn;
pkInstNameFn inst_name_fn;
pkResolvePathFn resolve_path_fn; pkResolvePathFn resolve_path_fn;
pkLoadScriptFn load_script_fn; pkLoadScriptFn load_script_fn;
@ -337,7 +351,9 @@ PK_PUBLIC PkVar pkGetArg(const PKVM* vm, int arg);
PK_PUBLIC bool pkGetArgBool(PKVM* vm, int arg, bool* value); PK_PUBLIC bool pkGetArgBool(PKVM* vm, int arg, bool* value);
PK_PUBLIC bool pkGetArgNumber(PKVM* vm, int arg, double* value); PK_PUBLIC bool pkGetArgNumber(PKVM* vm, int arg, double* value);
PK_PUBLIC bool pkGetArgString(PKVM* vm, int arg, const char** value); PK_PUBLIC bool pkGetArgString(PKVM* vm, int arg,
const char** value, uint32_t* length);
PK_PUBLIC bool pkGetArgInst(PKVM* vm, int arg, uint32_t id, void** value);
PK_PUBLIC bool pkGetArgValue(PKVM* vm, int arg, PkVarType type, PkVar* value); PK_PUBLIC bool pkGetArgValue(PKVM* vm, int arg, PkVarType type, PkVar* value);
// The functions follow are used to set the return value of the current native // The functions follow are used to set the return value of the current native
@ -349,6 +365,9 @@ PK_PUBLIC void pkReturnNumber(PKVM* vm, double value);
PK_PUBLIC void pkReturnString(PKVM* vm, const char* value); PK_PUBLIC void pkReturnString(PKVM* vm, const char* value);
PK_PUBLIC void pkReturnStringLength(PKVM* vm, const char* value, size_t len); PK_PUBLIC void pkReturnStringLength(PKVM* vm, const char* value, size_t len);
PK_PUBLIC void pkReturnValue(PKVM* vm, PkVar value); PK_PUBLIC void pkReturnValue(PKVM* vm, PkVar value);
PK_PUBLIC void pkReturnHandle(PKVM* vm, PkHandle* handle);
PK_PUBLIC void pkReturnInstNative(PKVM* vm, void* data, uint32_t id);
// Returns the cstring pointer of the given string. Make sure if the [value] is // Returns the cstring pointer of the given string. Make sure if the [value] is
// a string before calling this function, otherwise it'll fail an assertion. // a string before calling this function, otherwise it'll fail an assertion.
@ -382,6 +401,12 @@ PK_PUBLIC PkHandle* pkNewModule(PKVM* vm, const char* name);
// Create and return a new fiber around the function [fn]. // Create and return a new fiber around the function [fn].
PK_PUBLIC PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn); PK_PUBLIC PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn);
// Create and return a native instance around the [data]. The [id] is the
// unique id of the instance, this would be used to check if two instances are
// equal and used to get the name of the instance using NativeTypeNameFn
// callback.
PK_PUBLIC PkHandle* pkNewInstNative(PKVM* vm, void* data, uint32_t id);
// TODO: The functions below will push the primitive values on the stack // TODO: The functions below will push the primitive values on the stack
// and return it's pointer as a PkVar. It's useful to convert your primitive // and return it's pointer as a PkVar. It's useful to convert your primitive
// values as pocketlang variables. // values as pocketlang variables.

View File

@ -6,7 +6,7 @@
#ifndef BUFFERS_TEMPLATE_H #ifndef BUFFERS_TEMPLATE_H
#define BUFFERS_TEMPLATE_H #define BUFFERS_TEMPLATE_H
#include "pk_common.h" #include "pk_internal.h"
// The macro 'DECLARE_BUFFER()' emulate the C++ template to declare and define // The macro 'DECLARE_BUFFER()' emulate the C++ template to declare and define
// different types of buffer objects. // different types of buffer objects.

View File

@ -3,79 +3,16 @@
* Distributed Under The MIT License * Distributed Under The MIT License
*/ */
// A collection of reusable macros that pocketlang use. This file doesn't have
// any dependencies, you can just drag and drop this file in your project if
// you want to use these macros.
#ifndef PK_COMMON_H #ifndef PK_COMMON_H
#define PK_COMMON_H #define PK_COMMON_H
#include "include/pocketlang.h" #ifndef __has_builtin
#define __has_builtin(x) 0
// Commonly used c standard headers across the sources. Don't include any #endif
// headers that are specific to a single source here, instead include them in
// their source files explicitly (can not be implicitly included by another
// header). And don't include any C standard headers in any of the pocketlang
// headers.
#include <assert.h>
#include <errno.h>
#include <float.h>
#include <stdarg.h>
#include <stdbool.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
// Set this to dump stack frame before executing the next instruction.
#define DEBUG_DUMP_CALL_STACK 0
// 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 maximum number of argument a pocketlang function supported to call. This
// value is arbitrary and feel free to change it. (Just used this limit for an
// internal buffer to store values before calling a new fiber).
#define MAX_ARGC 32
// 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] amount 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 */
/*****************************************************************************/
#if defined(__GNUC__) #if defined(__GNUC__)
#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" #pragma GCC diagnostic ignored "-Wint-to-pointer-cast"
@ -146,20 +83,23 @@
#define ASSERT(condition, message) NO_OP #define ASSERT(condition, message) NO_OP
#define ASSERT_INDEX(index, size) NO_OP #define ASSERT_INDEX(index, size) NO_OP
// Reference : https://github.com/wren-lang/
#if defined(_MSC_VER) #if defined(_MSC_VER)
#define UNREACHABLE() __assume(0) #define UNREACHABLE() __assume(0)
#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) #elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5))
#define UNREACHABLE() __builtin_unreachable() #define UNREACHABLE() __builtin_unreachable()
#elif defined(__EMSCRIPTEN__) #elif defined(__EMSCRIPTEN__) || defined(__clang__)
#if __has_builtin(__builtin_unreachable)
#define UNREACHABLE() __builtin_unreachable() #define UNREACHABLE() __builtin_unreachable()
#else
#define UNREACHABLE() NO_OP
#endif
#else #else
#define UNREACHABLE() NO_OP #define UNREACHABLE() NO_OP
#endif #endif
#endif // DEBUG #endif // DEBUG
#if defined( _MSC_VER ) #if defined(_MSC_VER)
#define forceinline __forceinline #define forceinline __forceinline
#else #else
#define forceinline __attribute__((always_inline)) #define forceinline __attribute__((always_inline))
@ -203,9 +143,7 @@
#define STR_HEX_BUFF_SIZE 20 #define STR_HEX_BUFF_SIZE 20
// Integer number (double) to bin string buffer size. // Integer number (double) to bin string buffer size.
// The maximum value an unsigned 64 bit integer can get is // The maximum value an unsigned 64 bit integer can get is 0b11111... 64 1s.
// 0b1111111111111111111111111111111111111111111111111111111111111111
// which is 64 characters.
// + 64 for bin digits // + 64 for bin digits
// + 1 for sign '-' // + 1 for sign '-'
// + 2 for '0b' prefix // + 2 for '0b' prefix

View File

@ -6,7 +6,7 @@
#ifndef COMPILER_H #ifndef COMPILER_H
#define COMPILER_H #define COMPILER_H
#include "pk_common.h" #include "pk_internal.h"
#include "pk_var.h" #include "pk_var.h"
typedef enum { typedef enum {

View File

@ -121,13 +121,13 @@ PkHandle* pkGetFunction(PKVM* vm, PkHandle* module,
__ASSERT(value != NULL, "Argument [value] was NULL."); \ __ASSERT(value != NULL, "Argument [value] was NULL."); \
} while (false) } while (false)
// Set error for incompatible type provided as an argument. // Set error for incompatible type provided as an argument. (TODO: got type).
#define ERR_INVALID_ARG_TYPE(m_type) \ #define ERR_INVALID_ARG_TYPE(m_type) \
do { \ do { \
char buff[STR_INT_BUFF_SIZE]; \ char buff[STR_INT_BUFF_SIZE]; \
sprintf(buff, "%d", arg); \ sprintf(buff, "%d", arg); \
VM_SET_ERROR(vm, stringFormat(vm, "Expected a " m_type \ VM_SET_ERROR(vm, stringFormat(vm, "Expected a '$' at argument $.", \
" at argument $.", buff)); \ m_type, buff)); \
} while (false) } while (false)
// pkGetArgc implementation (see pocketlang.h for description). // pkGetArgc implementation (see pocketlang.h for description).
@ -173,12 +173,14 @@ bool pkGetArgNumber(PKVM* vm, int arg, double* value) {
} }
// pkGetArgString implementation (see pocketlang.h for description). // pkGetArgString implementation (see pocketlang.h for description).
bool pkGetArgString(PKVM* vm, int arg, const char** value) { bool pkGetArgString(PKVM* vm, int arg, const char** value, uint32_t* length) {
CHECK_GET_ARG_API_ERRORS(); CHECK_GET_ARG_API_ERRORS();
Var val = ARG(arg); Var val = ARG(arg);
if (IS_OBJ_TYPE(val, OBJ_STRING)) { if (IS_OBJ_TYPE(val, OBJ_STRING)) {
*value = ((String*)AS_OBJ(val))->data; String* str = (String*)AS_OBJ(val);
*value = str->data;
if (length) *length = str->length;
} else { } else {
ERR_INVALID_ARG_TYPE("string"); ERR_INVALID_ARG_TYPE("string");
@ -188,6 +190,34 @@ bool pkGetArgString(PKVM* vm, int arg, const char** value) {
return true; return true;
} }
// pkGetArgInstance implementation (see pocketlang.h for description).
bool pkGetArgInst(PKVM* vm, int arg, uint32_t id, void** value) {
CHECK_GET_ARG_API_ERRORS();
Var val = ARG(arg);
bool is_native_instance = false;
if (IS_OBJ_TYPE(val, OBJ_INST)) {
Instance* inst = ((Instance*)AS_OBJ(val));
if (inst->is_native && inst->native_id == id) {
*value = inst->native;
is_native_instance = true;
}
}
if (!is_native_instance) {
const char* ty_name = "$(?)";
if (vm->config.inst_name_fn != NULL) {
ty_name = vm->config.inst_name_fn(id);
}
ERR_INVALID_ARG_TYPE(ty_name);
return false;
}
return true;
}
// pkGetArgValue implementation (see pocketlang.h for description). // pkGetArgValue implementation (see pocketlang.h for description).
bool pkGetArgValue(PKVM* vm, int arg, PkVarType type, PkVar* value) { bool pkGetArgValue(PKVM* vm, int arg, PkVarType type, PkVar* value) {
CHECK_GET_ARG_API_ERRORS(); CHECK_GET_ARG_API_ERRORS();
@ -234,6 +264,16 @@ void pkReturnValue(PKVM* vm, PkVar value) {
RET(*(Var*)value); RET(*(Var*)value);
} }
// pkReturnHandle implementation (see pocketlang.h for description).
void pkReturnHandle(PKVM* vm, PkHandle* handle) {
RET(handle->value);
}
// pkReturnInstNative implementation (see pocketlang.h for description).
void pkReturnInstNative(PKVM* vm, void* data, uint32_t id) {
RET(VAR_OBJ(newInstanceNative(vm, data, id)));
}
const char* pkStringGetData(const PkVar value) { const char* pkStringGetData(const PkVar value) {
const Var str = (*(const Var*)value); const Var str = (*(const Var*)value);
__ASSERT(IS_OBJ_TYPE(str, OBJ_STRING), "Value should be of type string."); __ASSERT(IS_OBJ_TYPE(str, OBJ_STRING), "Value should be of type string.");

View File

@ -6,7 +6,7 @@
#ifndef CORE_H #ifndef CORE_H
#define CORE_H #define CORE_H
#include "pk_common.h" #include "pk_internal.h"
#include "pk_var.h" #include "pk_var.h"
// Initialize core language, builtin function and core libs. // Initialize core language, builtin function and core libs.

View File

@ -6,7 +6,7 @@
#ifndef DEBUG_H #ifndef DEBUG_H
#define DEBUG_H #define DEBUG_H
#include "pk_common.h" #include "pk_internal.h"
#include "pk_var.h" #include "pk_var.h"
// Dump the value of the [value] without a new line at the end to the buffer // Dump the value of the [value] without a new line at the end to the buffer

78
src/pk_internal.h Normal file
View File

@ -0,0 +1,78 @@
/*
* Copyright (c) 2020-2021 Thakee Nathees
* Distributed Under The MIT License
*/
#ifndef PK_INTERNAL
#define PK_INTERNAL
#include "include/pocketlang.h"
#include "pk_common.h"
// Commonly 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 (can not be implicitly included by another
// header). And don't include any C standard headers in any of the pocketlang
// headers.
#include <assert.h>
#include <errno.h>
#include <float.h>
#include <stdarg.h>
#include <stdbool.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
// Set this to dump stack frame before executing the next instruction.
#define DEBUG_DUMP_CALL_STACK 0
// 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 maximum number of argument a pocketlang function supported to call. This
// value is arbitrary and feel free to change it. (Just used this limit for an
// internal buffer to store values before calling a new fiber).
#define MAX_ARGC 32
// 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] amount 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)
#endif // PK_INTERNAL

View File

@ -6,7 +6,7 @@
#ifndef UTILS_H #ifndef UTILS_H
#define UTILS_H #define UTILS_H
#include "pk_common.h" #include "pk_internal.h"
// Returns the smallest power of two that is equal to or greater than [n]. // Returns the smallest power of two that is equal to or greater than [n].
// Source : https://github.com/wren-lang/wren/blob/main/src/vm/wren_utils.h#L119 // Source : https://github.com/wren-lang/wren/blob/main/src/vm/wren_utils.h#L119

View File

@ -42,26 +42,53 @@ PkVarType pkGetValueType(const PkVar value) {
} }
PkHandle* pkNewString(PKVM* vm, const char* value) { PkHandle* pkNewString(PKVM* vm, const char* value) {
return vmNewHandle(vm, VAR_OBJ(newString(vm, value))); String* str = newString(vm, value);
vmPushTempRef(vm, &str->_super); // str
PkHandle* handle = vmNewHandle(vm, VAR_OBJ(str));
vmPopTempRef(vm); // str
return handle;
} }
PkHandle* pkNewStringLength(PKVM* vm, const char* value, size_t len) { PkHandle* pkNewStringLength(PKVM* vm, const char* value, size_t len) {
return vmNewHandle(vm, VAR_OBJ(newStringLength(vm, value, (uint32_t)len))); String* str = newStringLength(vm, value, (uint32_t)len);
vmPushTempRef(vm, &str->_super); // str
PkHandle* handle = vmNewHandle(vm, VAR_OBJ(str));
vmPopTempRef(vm); // str
return handle;
} }
PkHandle* pkNewList(PKVM* vm) { PkHandle* pkNewList(PKVM* vm) {
return vmNewHandle(vm, VAR_OBJ(newList(vm, MIN_CAPACITY))); List* list = newList(vm, MIN_CAPACITY);
vmPushTempRef(vm, &list->_super); // list
PkHandle* handle = vmNewHandle(vm, VAR_OBJ(list));
vmPopTempRef(vm); // list
return handle;
} }
PkHandle* pkNewMap(PKVM* vm) { PkHandle* pkNewMap(PKVM* vm) {
return vmNewHandle(vm, VAR_OBJ(newMap(vm))); Map* map = newMap(vm);
vmPushTempRef(vm, &map->_super); // map
PkHandle* handle = vmNewHandle(vm, VAR_OBJ(map));
vmPopTempRef(vm); // map
return handle;
} }
PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn) { PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn) {
__ASSERT(IS_OBJ_TYPE(fn->value, OBJ_FUNC), "Fn should be of type function."); __ASSERT(IS_OBJ_TYPE(fn->value, OBJ_FUNC), "Fn should be of type function.");
Fiber* fiber = newFiber(vm, (Function*)AS_OBJ(fn->value)); Fiber* fiber = newFiber(vm, (Function*)AS_OBJ(fn->value));
return vmNewHandle(vm, VAR_OBJ(fiber)); vmPushTempRef(vm, &fiber->_super); // fiber
PkHandle* handle = vmNewHandle(vm, VAR_OBJ(fiber));
vmPopTempRef(vm); // fiber
return handle;
}
PkHandle* pkNewInstNative(PKVM* vm, void* data, uint32_t id) {
Instance* inst = newInstanceNative(vm, data, id);
vmPushTempRef(vm, &inst->_super); // inst
PkHandle* handle = vmNewHandle(vm, VAR_OBJ(inst));
vmPopTempRef(vm); // inst
return handle;
} }
/*****************************************************************************/ /*****************************************************************************/
@ -511,6 +538,22 @@ Instance* newInstance(PKVM* vm, Class* ty, bool initialize) {
return inst; return inst;
} }
Instance* newInstanceNative(PKVM* vm, void* data, uint32_t id) {
Instance* inst = ALLOCATE(vm, Instance);
varInitObject(&inst->_super, vm, OBJ_INST);
inst->is_native = true;
inst->native_id = id;
if (vm->config.inst_name_fn != NULL) {
inst->name = vm->config.inst_name_fn(id);
} else {
inst->name = "$(?)";
}
inst->native = data;
return inst;
}
List* rangeAsList(PKVM* vm, Range* self) { List* rangeAsList(PKVM* vm, Range* self) {
List* list; List* list;
if (self->from < self->to) { if (self->from < self->to) {
@ -1006,7 +1049,10 @@ void freeObject(PKVM* vm, Object* self) {
Instance* inst = (Instance*)self; Instance* inst = (Instance*)self;
if (inst->is_native) { if (inst->is_native) {
TODO; if (vm->config.free_inst_fn != NULL) {
// TODO: Allow user to set error when freeing the object.
vm->config.free_inst_fn(vm, inst->native);
}
} else { } else {
Inst* ins = inst->ins; Inst* ins = inst->ins;
@ -1446,6 +1492,15 @@ static void _toStringInternal(PKVM* vm, const Var v, pkByteBuffer* buff,
pkByteBufferWrite(buff, vm, '='); pkByteBufferWrite(buff, vm, '=');
_toStringInternal(vm, ins->fields.data[i], buff, outer, repr); _toStringInternal(vm, ins->fields.data[i], buff, outer, repr);
} }
} else {
pkByteBufferWrite(buff, vm, ':');
char buff_addr[STR_HEX_BUFF_SIZE];
char* ptr = (char*)buff_addr;
(*ptr++) = '0'; (*ptr++) = 'x';
const int len = snprintf(ptr, sizeof(buff_addr) - 2,
"%08x", (unsigned int)(uintptr_t)inst->native);
pkByteBufferAddString(buff, vm, buff_addr, (uint32_t)len);
} }
pkByteBufferWrite(buff, vm, ']'); pkByteBufferWrite(buff, vm, ']');

View File

@ -7,7 +7,7 @@
#define VAR_H #define VAR_H
#include "pk_buffers.h" #include "pk_buffers.h"
#include "pk_common.h" #include "pk_internal.h"
/** @file /** @file
* A simple dynamic type system library for small dynamic typed languages using * A simple dynamic type system library for small dynamic typed languages using
@ -204,6 +204,7 @@ DECLARE_BUFFER(Class, Class*)
void pkByteBufferAddString(pkByteBuffer* self, PKVM* vm, const char* str, void pkByteBufferAddString(pkByteBuffer* self, PKVM* vm, const char* str,
uint32_t length); uint32_t length);
// Type enums of the pocketlang heap allocated types.
typedef enum { typedef enum {
OBJ_STRING, OBJ_STRING,
OBJ_LIST, OBJ_LIST,
@ -377,7 +378,10 @@ struct Instance {
Object _super; Object _super;
const char* name; //< Name of the type it belongs to. const char* name; //< Name of the type it belongs to.
bool is_native; //< True if it's a native type instance. bool is_native; //< True if it's a native type instance.
uint32_t native_id; //< Unique ID of this native instance.
union { union {
void* native; //< C struct pointer. // TODO: void* native; //< C struct pointer. // TODO:
Inst* ins; //< Module instance pointer. Inst* ins; //< Module instance pointer.
@ -442,6 +446,12 @@ Class* newClass(PKVM* vm, Script* scr, const char* name, uint32_t length);
// the buffer count = 0). Otherwise they'll be set to VAR_NULL. // the buffer count = 0). Otherwise they'll be set to VAR_NULL.
Instance* newInstance(PKVM* vm, Class* ty, bool initialize); Instance* newInstance(PKVM* vm, Class* ty, bool initialize);
// Allocate new native instance and with [data] as the native type handle and
// return Instance*. The [id] is the unique id of the instance, this would be
// used to check if two instances are equal and used to get the name of the
// instance using NativeTypeNameFn callback.
Instance* newInstanceNative(PKVM* vm, void* data, uint32_t id);
/*****************************************************************************/ /*****************************************************************************/
/* METHODS */ /* METHODS */
/*****************************************************************************/ /*****************************************************************************/

View File

@ -30,6 +30,9 @@ PkConfiguration pkNewConfiguration(void) {
config.write_fn = NULL; config.write_fn = NULL;
config.read_fn = NULL; config.read_fn = NULL;
config.free_inst_fn = NULL;
config.inst_name_fn = NULL;
config.load_script_fn = NULL; config.load_script_fn = NULL;
config.resolve_path_fn = NULL; config.resolve_path_fn = NULL;
config.user_data = NULL; config.user_data = NULL;

View File

@ -6,8 +6,8 @@
#ifndef VM_H #ifndef VM_H
#define VM_H #define VM_H
#include "pk_common.h"
#include "pk_compiler.h" #include "pk_compiler.h"
#include "pk_internal.h"
#include "pk_var.h" #include "pk_var.h"
// The maximum number of temporary object reference to protect them from being // The maximum number of temporary object reference to protect them from being

View File

@ -6,8 +6,9 @@
## uses of tabs and trailing white spaces, etc. ## uses of tabs and trailing white spaces, etc.
import os, sys, re import os, sys, re
from os.path import join, abspath, dirname
from os import listdir from os import listdir
from os.path import (
join, abspath, dirname, relpath)
## The absolute path of this file, when run as a script. ## The absolute path of this file, when run as a script.
## This file is not intended to be included in other files at the moment. ## This file is not intended to be included in other files at the moment.
@ -18,6 +19,11 @@ THIS_PATH = abspath(dirname(__file__))
def to_abs_paths(sources): def to_abs_paths(sources):
return map(lambda s: os.path.join(THIS_PATH, s), sources) return map(lambda s: os.path.join(THIS_PATH, s), sources)
## Converts the path from absolute path to relative path from the
## toplelve of the project.
def to_tolevel_path(path):
return relpath(path, join(THIS_PATH, '..'))
## A list of source files, to check if the fnv1a hash values match it's ## A list of source files, to check if the fnv1a hash values match it's
## corresponding cstring in the CASE_ATTRIB(name, hash) macro calls. ## corresponding cstring in the CASE_ATTRIB(name, hash) macro calls.
HASH_CHECK_LIST = [ HASH_CHECK_LIST = [
@ -29,7 +35,14 @@ HASH_CHECK_LIST = [
C_SOURCE_DIRS = [ C_SOURCE_DIRS = [
"../src/", "../src/",
"../cli/", "../cli/",
"../cli/modules/", ]
## A list of common header that just copied in different projects.
## These common header cannot be re-used because we're trying to achieve
## minimalistic with the count of the sources in pocketlang.
COMMON_HEADERS = [
"../src/pk_common.h",
"../cli/common.h",
] ]
## This global variable will be set to true if any check failed. ## This global variable will be set to true if any check failed.
@ -38,6 +51,7 @@ checks_failed = False
def main(): def main():
check_fnv1_hash(to_abs_paths(HASH_CHECK_LIST)) check_fnv1_hash(to_abs_paths(HASH_CHECK_LIST))
check_static(to_abs_paths(C_SOURCE_DIRS)) check_static(to_abs_paths(C_SOURCE_DIRS))
check_common_header_match(to_abs_paths(COMMON_HEADERS))
if checks_failed: if checks_failed:
sys.exit(1) sys.exit(1)
print("Static checks were passed.") print("Static checks were passed.")
@ -58,7 +72,7 @@ def check_fnv1_hash(sources):
if val == hash: continue if val == hash: continue
## Path of the file relative to top-level. ## Path of the file relative to top-level.
file_path = os.path.relpath(file, join(THIS_PATH, '..')) file_path = to_tolevel_path(file)
report_error(f"{location(file_path, line_no)} - hash mismatch. " report_error(f"{location(file_path, line_no)} - hash mismatch. "
f"hash('{name}') = {hash} not {val}") f"hash('{name}') = {hash} not {val}")
@ -77,7 +91,7 @@ def check_static(dirs):
fp = open(join(dir, file), 'r') fp = open(join(dir, file), 'r')
## Path of the file relative to top-level. ## Path of the file relative to top-level.
file_path = os.path.relpath(join(dir, file), join(THIS_PATH, '..')) file_path = to_tolevel_path(join(dir, file))
## This will be set to true if the last line is empty. ## This will be set to true if the last line is empty.
is_last_empty = False; line_no = 0 is_last_empty = False; line_no = 0
@ -109,6 +123,23 @@ def check_static(dirs):
fp.close() fp.close()
## Assert all the content of the headers list below are the same.
def check_common_header_match(headers):
headers = list(headers)
assert len(headers) >= 1
content = ''
with open(headers[0], 'r') as f:
content = f.read()
for i in range(1, len(headers)):
with open(headers[i], 'r') as f:
if f.read() != content:
main_header = to_tolevel_path(headers[0])
curr_header = to_tolevel_path(headers[i])
report_error("File content mismatch: \"%s\" and \"%s\"\n"
" These files contants should be the same."
%(main_header, curr_header))
## Returns a formated string of the error location. ## Returns a formated string of the error location.
def location(file, line): def location(file, line):