From 2fd7b1f6e41e9e9af79279dbedafab59db871d76 Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Sat, 21 May 2022 14:21:52 +0530 Subject: [PATCH] More code enhancements (read bellow) - ByteBuffer, and Vector classes were created. However thier methods are limited, and to do. - Strings supports new line escape "\\n". - typename will return class name of an instance (was just 'Inst' fixed) - _repr() method added. if _str method doesn' exists on an instance, it'll try to call _repr() to make a string. - number.isint(), number.isbyte() method added - [], []= operator were added. --- src/core/common.h | 10 ++ src/core/compiler.c | 48 +++---- src/core/core.c | 93 ++++++++++---- src/core/core.h | 10 ++ src/core/internal.h | 10 -- src/core/public.c | 56 ++++----- src/core/utils.c | 34 +++++ src/core/utils.h | 11 ++ src/core/value.c | 24 +++- src/core/value.h | 4 +- src/core/vm.c | 8 +- src/include/pocketlang.h | 30 ++--- src/libs/libs.c | 2 + src/libs/libs.h | 99 ++------------- src/libs/std_math.c | 2 +- src/libs/std_types.c | 266 +++++++++++++++++++++++++++++++++++++++ tests/lang/basics.pk | 3 + tests/native/README.md | 6 +- tests/native/example2.c | 8 +- 19 files changed, 506 insertions(+), 218 deletions(-) create mode 100644 src/libs/std_types.c diff --git a/src/core/common.h b/src/core/common.h index aabe7a2..f887ad3 100644 --- a/src/core/common.h +++ b/src/core/common.h @@ -111,4 +111,14 @@ #define TODO __ASSERT(false, "TODO: It hasn't implemented yet.") #define OOPS "Oops a bug!! report please." +// Returns the docstring of the function, which is a static const char* defined +// just above the function by the DEF() macro below. +#define DOCSTRING(fn) _pk_doc_##fn + +// A macro to declare a function, with docstring, which is defined as +// _pk_doc_ = docstring; That'll used to generate function help text. +#define DEF(fn, docstring) \ + static const char* DOCSTRING(fn) = docstring; \ + static void fn(PKVM* vm) + #endif //PK_COMMON_H diff --git a/src/core/compiler.c b/src/core/compiler.c index 1699739..4725b84 100644 --- a/src/core/compiler.c +++ b/src/core/compiler.c @@ -664,28 +664,6 @@ static void setNextValueToken(Parser* parser, _TokenType type, Var value); static void setNextToken(Parser* parser, _TokenType type); static bool matchChar(Parser* parser, char c); -#define _BETWEEN(a, c, b) (((a) <= (c)) && ((c) <= (b))) -static inline bool _isCharHex(char c) { - return (_BETWEEN('0', c, '9') - || _BETWEEN('a', c, 'z') - || _BETWEEN('A', c, 'Z')); -} - -static inline uint8_t _charHexVal(char c) { - ASSERT(_isCharHex(c), OOPS); - - if (_BETWEEN('0', c, '9')) { - return c - '0'; - } else if (_BETWEEN('a', c, 'z')) { - return c - 'a' + 10; - } else if (_BETWEEN('A', c, 'Z')) { - return c - 'A' + 10; - } - UNREACHABLE(); - return 0; -} -#undef _BETWEEN - static void eatString(Compiler* compiler, bool single_quote) { Parser* parser = &compiler->parser; @@ -758,6 +736,7 @@ static void eatString(Compiler* compiler, bool single_quote) { case 'n': pkByteBufferWrite(&buff, parser->vm, '\n'); break; case 'r': pkByteBufferWrite(&buff, parser->vm, '\r'); break; case 't': pkByteBufferWrite(&buff, parser->vm, '\t'); break; + case '\n': break; // Just ignore the next line. // '$' In pocketlang string is used for interpolation. case '$': pkByteBufferWrite(&buff, parser->vm, '$'); break; @@ -767,22 +746,22 @@ static void eatString(Compiler* compiler, bool single_quote) { uint8_t val = 0; c = eatChar(parser); - if (!_isCharHex(c)) { + if (!utilIsCharHex(c)) { semanticError(compiler, makeErrToken(parser), "Invalid hex escape."); break; } - val = _charHexVal(c); + val = utilCharHexVal(c); c = eatChar(parser); - if (!_isCharHex(c)) { + if (!utilIsCharHex(c)) { semanticError(compiler, makeErrToken(parser), "Invalid hex escape."); break; } - val = (val << 4) | _charHexVal(c); + val = (val << 4) | utilCharHexVal(c); pkByteBufferWrite(&buff, parser->vm, val); @@ -791,7 +770,7 @@ static void eatString(Compiler* compiler, bool single_quote) { default: semanticError(compiler, makeErrToken(parser), "Invalid escape character."); - return; + break; } } else { pkByteBufferWrite(&buff, parser->vm, c); @@ -900,7 +879,7 @@ static void eatNumber(Compiler* compiler) { c = peekChar(parser); // The first digit should be hex digit. - if (!_isCharHex(c)) { + if (!utilIsCharHex(c)) { syntaxError(compiler, makeErrToken(parser), "Invalid hex literal."); return; @@ -908,7 +887,7 @@ static void eatNumber(Compiler* compiler) { do { // Consume the next digit. c = peekChar(parser); - if (!_isCharHex(c)) break; + if (!utilIsCharHex(c)) break; eatChar(parser); // Check the length of the binary literal. @@ -920,7 +899,7 @@ static void eatNumber(Compiler* compiler) { } // "Append" the next digit at the end. - hex = (hex << 4) | _charHexVal(c); + hex = (hex << 4) | utilCharHexVal(c); } while (true); @@ -2674,6 +2653,15 @@ static bool matchOperatorMethod(Compiler* compiler, "Expected keyword self for unary operator definition."); return false; } + if (match(compiler, TK_LBRACKET)) { + if (match(compiler, TK_RBRACKET)) { + if (match(compiler, TK_EQ)) _RET("[]=", 2); + _RET("[]", 1); + } + syntaxError(compiler, compiler->parser.previous, + "Invalid operator method symbol."); + return false; + } if (match(compiler, TK_PLUSEQ)) _RET("+=", 1); if (match(compiler, TK_MINUSEQ)) _RET("-=", 1); diff --git a/src/core/core.c b/src/core/core.c index 51e1520..928fe04 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -154,10 +154,7 @@ void initializeScript(PKVM* vm, Module* module) { /* INTERNAL FUNCTIONS */ /*****************************************************************************/ -// Returns the string value of the variable, a wrapper of toString() function -// but for instances it'll try to calll "_to_string" function and on error -// it'll return NULL. -static inline String* _varToString(PKVM* vm, Var self) { +String* varToString(PKVM* vm, Var self, bool repr) { if (IS_OBJ_TYPE(self, OBJ_INST)) { // The closure is retrieved from [self] thus, it doesn't need to be push @@ -165,10 +162,20 @@ static inline String* _varToString(PKVM* vm, Var self) { // from GC). Closure* closure = NULL; - String* name = newString(vm, LITS__str); // TODO: static vm string?. - vmPushTempRef(vm, &name->_super); // method. - bool has_method = hasMethod(vm, self, name, &closure); - vmPopTempRef(vm); // method. + bool has_method = false; + if (!repr) { + String* name = newString(vm, LITS__str); // TODO: static vm string?. + vmPushTempRef(vm, &name->_super); // name. + has_method = hasMethod(vm, self, name, &closure); + vmPopTempRef(vm); // name. + } + + if (!has_method) { + String* name = newString(vm, LITS__repr); // TODO: static vm string?. + vmPushTempRef(vm, &name->_super); // name. + has_method = hasMethod(vm, self, name, &closure); + vmPopTempRef(vm); // name. + } if (has_method) { Var ret = VAR_NULL; @@ -188,6 +195,7 @@ static inline String* _varToString(PKVM* vm, Var self) { // "fall throught" and call 'toString()' bellow. } + if (repr) return toRepr(vm, self); return toString(vm, self); } @@ -198,9 +206,9 @@ static inline bool _callUnaryOpMethod(PKVM* vm, Var self, const char* method_name, Var* ret) { Closure* closure = NULL; String* name = newString(vm, method_name); - vmPushTempRef(vm, &name->_super); // method. + vmPushTempRef(vm, &name->_super); // name. bool has_method = hasMethod(vm, self, name, &closure); - vmPopTempRef(vm); // method. + vmPopTempRef(vm); // name. if (!has_method) return false; @@ -215,9 +223,9 @@ static inline bool _callBinaryOpMethod(PKVM* vm, Var self, Var other, const char* method_name, Var* ret) { Closure* closure = NULL; String* name = newString(vm, method_name); - vmPushTempRef(vm, &name->_super); // method. + vmPushTempRef(vm, &name->_super); // name. bool has_method = hasMethod(vm, self, name, &closure); - vmPopTempRef(vm); // method. + vmPopTempRef(vm); // name. if (!has_method) return false; @@ -287,7 +295,7 @@ DEF(coreAssert, if (argc == 2) { if (AS_OBJ(ARG(2))->type != OBJ_STRING) { - msg = _varToString(vm, ARG(2)); + msg = varToString(vm, ARG(2), false); if (msg == NULL) return; //< Error at _to_string override. } else { @@ -380,7 +388,7 @@ DEF(coreToString, "str(value:var) -> string\n" "Returns the string representation of the value.") { - String* str = _varToString(vm, ARG(1)); + String* str = varToString(vm, ARG(1), false); if (str == NULL) RET(VAR_NULL); RET(VAR_OBJ(str)); } @@ -425,7 +433,7 @@ DEF(corePrint, for (int i = 1; i <= ARGC; i++) { if (i != 1) vm->config.stdout_write(vm, " "); - String* str = _varToString(vm, ARG(i)); + String* str = varToString(vm, ARG(i), false); if (str == NULL) RET(VAR_NULL); vm->config.stdout_write(vm, str->data); } @@ -447,7 +455,7 @@ DEF(coreInput, if (vm->config.stdin_read == NULL) return; if (argc == 1) { - String* str = _varToString(vm, ARG(1)); + String* str = varToString(vm, ARG(1), false); if (str == NULL) RET(VAR_NULL); vm->config.stdout_write(vm, str->data); } @@ -537,7 +545,7 @@ DEF(coreListJoin, pkByteBufferInit(&buff); for (uint32_t i = 0; i < list->elements.count; i++) { - String* str = _varToString(vm, list->elements.data[i]); + String* str = varToString(vm, list->elements.data[i], false); if (str == NULL) RET(VAR_NULL); vmPushTempRef(vm, &str->_super); // elem pkByteBufferAddString(&buff, vm, str->data, str->length); @@ -703,7 +711,7 @@ DEF(stdLangWrite, if (IS_OBJ_TYPE(arg, OBJ_STRING)) { str = (String*)AS_OBJ(arg); } else { - str = _varToString(vm, ARG(1)); + str = varToString(vm, ARG(1), false); if (str == NULL) RET(VAR_NULL); } @@ -769,7 +777,7 @@ static void _ctorString(PKVM* vm) { RET(VAR_OBJ(newStringLength(vm, NULL, 0))); return; } - String* str = _varToString(vm, ARG(1)); + String* str = varToString(vm, ARG(1), false); if (str == NULL) RET(VAR_NULL); RET(VAR_OBJ(str)); } @@ -828,6 +836,20 @@ DEF(_numberTimes, RET(VAR_NULL); } +DEF(_numberIsint, + "Number.isint() -> bool\n" + "Returns true if the number is a whold number, otherwise false.") { + double n = AS_NUM(SELF); + RET(VAR_BOOL(floor(n) == n)); +} + +DEF(_numberIsbyte, + "Number.isbyte() -> bool\n" + "Returns true if the number is an integer and is between 0x00 and 0xff.") { + double n = AS_NUM(SELF); + RET(VAR_BOOL((floor(n) == n) && (0x00 <= n && n <= 0xff))); +} + DEF(_listAppend, "List.append(value:var) -> List\n" "Append the [value] to the list and return the list.") { @@ -925,7 +947,10 @@ static void initializePrimitiveClasses(PKVM* vm) { vmPopTempRef(vm); /* fn. */ \ } while (false) + // TODO: write docs. ADD_METHOD(PK_NUMBER, "times", _numberTimes, 1); + ADD_METHOD(PK_NUMBER, "isint", _numberIsint, 0); + ADD_METHOD(PK_NUMBER, "isbyte", _numberIsbyte, 0); ADD_METHOD(PK_LIST, "append", _listAppend, 1); ADD_METHOD(PK_FIBER, "run", _fiberRun, -1); ADD_METHOD(PK_FIBER, "resume", _fiberResume, -1); @@ -998,7 +1023,8 @@ static inline Closure* clsGetMethod(Class* cls, String* name) { for (int i = 0; i < (int)cls_->methods.count; i++) { Closure* method_ = cls_->methods.data[i]; ASSERT(method_->fn->is_method, OOPS); - if (IS_CSTR_EQ(name, method_->fn->name, name->length)) { + const char* method_name = method_->fn->name; + if (IS_CSTR_EQ(name, method_name, strlen(method_name))) { return method_; } } @@ -1289,7 +1315,7 @@ Var varOpRange(PKVM* vm, Var v1, Var v2) { } if (IS_OBJ_TYPE(v1, OBJ_STRING)) { - String* str = _varToString(vm, v2); + String* str = varToString(vm, v2, false); if (str == NULL) return VAR_NULL; String* concat = stringJoin(vm, (String*) AS_OBJ(v1), str); return VAR_OBJ(concat); @@ -1742,7 +1768,7 @@ Var varGetSubscript(PKVM* vm, Var on, Var key) { VM_SET_ERROR(vm, stringFormat(vm, "Unhashable key '$'.", varTypeName(key))); } else { - String* key_repr = toRepr(vm, key); + String* key_repr = varToString(vm, key, true); vmPushTempRef(vm, &key_repr->_super); // key_repr. VM_SET_ERROR(vm, stringFormat(vm, "Key '@' not exists", key_repr)); vmPopTempRef(vm); // key_repr. @@ -1756,6 +1782,13 @@ Var varGetSubscript(PKVM* vm, Var on, Var key) { case OBJ_UPVALUE: UNREACHABLE(); // Not first class objects. + case OBJ_INST: { + Var ret; + if (_callBinaryOpMethod(vm, on, key, "[]", &ret)) { + return ret; + } + } break; + default: break; } @@ -1804,6 +1837,22 @@ void varsetSubscript(PKVM* vm, Var on, Var key, Var value) { case OBJ_UPVALUE: UNREACHABLE(); + case OBJ_INST: { + + Closure* closure = NULL; + String* name = newString(vm, "[]="); + vmPushTempRef(vm, &name->_super); // name. + bool has_method = hasMethod(vm, on, name, &closure); + vmPopTempRef(vm); // name. + + if (has_method) { + Var args[2] = { key, value }; + vmCallMethod(vm, on, closure, 2, args, NULL); + return; + } + + } break; + default: break; } diff --git a/src/core/core.h b/src/core/core.h index 8fc3877..61eb876 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -17,6 +17,7 @@ // restructre. The names of the macros are begin with LIST_ and the string. #define LITS__init "_init" #define LITS__str "_str" +#define LITS__repr "_repr" // Functions, methods, classes and other names which are intrenal / special to // pocketlang are starts with the following character (ex: @main, @literalFn). @@ -91,6 +92,15 @@ Closure* getSuperMethod(PKVM* vm, Var self, String* name); // otherwise and if the [method] argument is not NULL, method will be set. bool hasMethod(PKVM* vm, Var self, String* name, Closure** method); +// Returns the string value of the variable, a wrapper of toString() function +// but for instances it'll try to calll "_to_string" function and on error +// it'll return NULL. +// if parameter [repr] is true it'll return repr string of the value and for +// instances it'll call "_repr()" method. +// Note that if _str method does not exists it'll use _repr method for to +// string. +String* varToString(PKVM* vm, Var self, bool repr); + Var varPositive(PKVM* vm, Var v); // Returns +v. Var varNegative(PKVM* vm, Var v); // Returns -v. Var varNot(PKVM* vm, Var v); // Returns !v. diff --git a/src/core/internal.h b/src/core/internal.h index 2d03879..9b8cf59 100644 --- a/src/core/internal.h +++ b/src/core/internal.h @@ -113,16 +113,6 @@ /* REUSABLE INTERNAL MACROS */ /*****************************************************************************/ -// Returns the docstring of the function, which is a static const char* defined -// just above the function by the DEF() macro below. -#define DOCSTRING(fn) _pk_doc_##fn - -// A macro to declare a function, with docstring, which is defined as -// _pk_doc_ = docstring; That'll used to generate function help text. -#define DEF(fn, docstring) \ - static const char* DOCSTRING(fn) = docstring; \ - static void fn(PKVM* vm) - // Here we're switching the FNV-1a hash value of the name (cstring). Which is // an efficient way than having multiple if (attrib == "name"). From O(n) * k // to O(1) where n is the length of the string and k is the number of string diff --git a/src/core/public.c b/src/core/public.c index 2636714..122f6ce 100644 --- a/src/core/public.c +++ b/src/core/public.c @@ -49,11 +49,6 @@ "Slot index is too large. Did you forget to call pkReserveSlots()?."); \ } while (false) -// ARGC won't be the real arity if any slots allocated before calling argument -// validation calling this first is the callers responsibility. -#define VALIDATE_ARGC(arg) \ - ASSERT(arg > 0 && arg <= ARGC, "Invalid argument index.") - #define CHECK_FIBER_EXISTS(vm) \ do { \ ASSERT(vm->fiber != NULL, \ @@ -548,23 +543,23 @@ bool pkCheckArgcRange(PKVM* vm, int argc, int min, int max) { } // Set error for incompatible type provided as an argument. (TODO: got type). -#define ERR_INVALID_SLOT_TYPE(ty_name) \ +#define ERR_INVALID_SLOT_TYPE(slot, ty_name) \ do { \ char buff[STR_INT_BUFF_SIZE]; \ - sprintf(buff, "%d", arg); \ + sprintf(buff, "%d", slot); \ VM_SET_ERROR(vm, stringFormat(vm, "Expected a '$' at slot $.", \ ty_name, buff)); \ } while (false) // FIXME: If the user needs just the boolean value of the object, they should // use pkGetSlotBool(). -PK_PUBLIC bool pkValidateSlotBool(PKVM* vm, int arg, bool* value) { +PK_PUBLIC bool pkValidateSlotBool(PKVM* vm, int slot, bool* value) { CHECK_FIBER_EXISTS(vm); - VALIDATE_ARGC(arg); + VALIDATE_SLOT_INDEX(slot); - Var val = ARG(arg); + Var val = ARG(slot); if (!IS_BOOL(val)) { - ERR_INVALID_SLOT_TYPE("Boolean"); + ERR_INVALID_SLOT_TYPE(slot, "Boolean"); return false; } @@ -572,13 +567,13 @@ PK_PUBLIC bool pkValidateSlotBool(PKVM* vm, int arg, bool* value) { return true; } -PK_PUBLIC bool pkValidateSlotNumber(PKVM* vm, int arg, double* value) { +PK_PUBLIC bool pkValidateSlotNumber(PKVM* vm, int slot, double* value) { CHECK_FIBER_EXISTS(vm); - VALIDATE_ARGC(arg); + VALIDATE_SLOT_INDEX(slot); - Var val = ARG(arg); + Var val = ARG(slot); if (!IS_NUM(val)) { - ERR_INVALID_SLOT_TYPE("Number"); + ERR_INVALID_SLOT_TYPE(slot, "Number"); return false; } @@ -586,14 +581,14 @@ PK_PUBLIC bool pkValidateSlotNumber(PKVM* vm, int arg, double* value) { return true; } -PK_PUBLIC bool pkValidateSlotString(PKVM* vm, int arg, const char** value, +PK_PUBLIC bool pkValidateSlotString(PKVM* vm, int slot, const char** value, uint32_t* length) { CHECK_FIBER_EXISTS(vm); - VALIDATE_ARGC(arg); + VALIDATE_SLOT_INDEX(slot); - Var val = ARG(arg); + Var val = ARG(slot); if (!IS_OBJ_TYPE(val, OBJ_STRING)) { - ERR_INVALID_SLOT_TYPE("String"); + ERR_INVALID_SLOT_TYPE(slot, "String"); return false; } String* str = (String*)AS_OBJ(val); @@ -602,27 +597,27 @@ PK_PUBLIC bool pkValidateSlotString(PKVM* vm, int arg, const char** value, return true; } -bool pkValidateSlotType(PKVM* vm, int arg, PkVarType type) { +bool pkValidateSlotType(PKVM* vm, int slot, PkVarType type) { CHECK_FIBER_EXISTS(vm); - VALIDATE_ARGC(arg); - if (getVarType(ARG(arg)) != type) { - ERR_INVALID_SLOT_TYPE(getPkVarTypeName(type)); + VALIDATE_SLOT_INDEX(slot); + if (getVarType(ARG(slot)) != type) { + ERR_INVALID_SLOT_TYPE(slot, getPkVarTypeName(type)); return false; } return true; } -PK_PUBLIC bool pkValidateSlotInstanceOf(PKVM* vm, int arg, int cls) { +PK_PUBLIC bool pkValidateSlotInstanceOf(PKVM* vm, int slot, int cls) { CHECK_FIBER_EXISTS(vm); - VALIDATE_ARGC(arg); + VALIDATE_SLOT_INDEX(slot); VALIDATE_SLOT_INDEX(cls); - Var instance = ARG(arg), class_ = SLOT(cls); + Var instance = ARG(slot), class_ = SLOT(cls); if (!varIsType(vm, instance, class_)) { // If [class_] is not a valid class, it's already an error. if (VM_HAS_ERROR(vm)) return false; - ERR_INVALID_SLOT_TYPE(((Class*)AS_OBJ(class_))->name->data); + ERR_INVALID_SLOT_TYPE(slot, ((Class*)AS_OBJ(class_))->name->data); return false; } @@ -741,11 +736,12 @@ void pkSetSlotStringFmt(PKVM* vm, int index, const char* fmt, ...) { void pkSetSlotHandle(PKVM* vm, int index, PkHandle* handle) { CHECK_FIBER_EXISTS(vm); + CHECK_ARG_NULL(handle); VALIDATE_SLOT_INDEX(index); SET_SLOT(index, handle->value); } -bool pkSetAttribute(PKVM* vm, int instance, int value, const char* name) { +bool pkSetAttribute(PKVM* vm, int instance, const char* name, int value) { CHECK_FIBER_EXISTS(vm); CHECK_ARG_NULL(name); VALIDATE_SLOT_INDEX(instance); @@ -898,15 +894,11 @@ void pkGetClass(PKVM* vm, int instance, int index) { SET_SLOT(index, VAR_OBJ(getClass(vm, SLOT(instance)))); } -#undef CHECK_RUNTIME -#undef VALIDATE_ARGC #undef ERR_INVALID_ARG_TYPE #undef ARG #undef SLOT #undef SET_SLOT #undef ARGC -#undef CHECK_NULL -#undef CHECK_TYPE /*****************************************************************************/ /* INTERNAL */ diff --git a/src/core/utils.c b/src/core/utils.c index 276b175..7889053 100644 --- a/src/core/utils.c +++ b/src/core/utils.c @@ -40,6 +40,40 @@ bool utilIsDigit(char c) { return ('0' <= c && c <= '9'); } +#define _BETWEEN(a, c, b) (((a) <= (c)) && ((c) <= (b))) +bool utilIsCharHex(char c) { + return (_BETWEEN('0', c, '9') + || _BETWEEN('a', c, 'z') + || _BETWEEN('A', c, 'Z')); +} + +uint8_t utilCharHexVal(char c) { + assert(utilIsCharHex(c)); + + if (_BETWEEN('0', c, '9')) { + return c - '0'; + } else if (_BETWEEN('a', c, 'z')) { + return c - 'a' + 10; + } else if (_BETWEEN('A', c, 'Z')) { + return c - 'A' + 10; + } + + assert(false); // Unreachable. + return 0; +} + +char utilHexDigit(uint8_t value, bool uppercase) { + assert(_BETWEEN(0x0, value, 0xf)); + + if (_BETWEEN(0, value, 9)) return '0' + value; + + return (uppercase) + ? 'A' + (value - 10) + : 'a' + (value - 10); + +} +#undef _BETWEEN + // A union to reinterpret a double as raw bits and back. typedef union { uint64_t bits64; diff --git a/src/core/utils.h b/src/core/utils.h index 9ee8e51..01a7b42 100644 --- a/src/core/utils.h +++ b/src/core/utils.h @@ -23,6 +23,17 @@ bool utilIsName(char c); // Returns true if `c` is [0-9]. bool utilIsDigit(char c); +// Returns true if character is a hex digit. +// ie [a-zA-Z0-9]. +bool utilIsCharHex(char c); + +// Returns the decimal value of the hex digit. +// char should match [a-zA-Z0-9]. +uint8_t utilCharHexVal(char c); + +// Returns the values hex digit. The value must be 0x0 <= val < 0xf +char utilHexDigit(uint8_t value, bool uppercase); + // Return Reinterpreted bits of the double value. uint64_t utilDoubleToBits(double value); diff --git a/src/core/value.c b/src/core/value.c index b7a81b8..58ecf63 100644 --- a/src/core/value.c +++ b/src/core/value.c @@ -1212,6 +1212,11 @@ const char* varTypeName(Var v) { ASSERT(IS_OBJ(v), OOPS); Object* obj = AS_OBJ(v); + + if (obj->type == OBJ_INST) { + return ((Instance*)obj)->cls->name->data; + } + return getObjectTypeName(obj->type); } @@ -1350,17 +1355,26 @@ static void _toStringInternal(PKVM* vm, const Var v, pkByteBuffer* buff, } else { // If recursive return with quotes (ex: [42, "hello", 0..10]). pkByteBufferWrite(buff, vm, '"'); - for (const char* c = str->data; *c != '\0'; c++) { - switch (*c) { + for (uint32_t i = 0; i < str->length; i++) { + char c = str->data[i]; + switch (c) { case '"': pkByteBufferAddString(buff, vm, "\\\"", 2); break; case '\\': pkByteBufferAddString(buff, vm, "\\\\", 2); break; case '\n': pkByteBufferAddString(buff, vm, "\\n", 2); break; case '\r': pkByteBufferAddString(buff, vm, "\\r", 2); break; case '\t': pkByteBufferAddString(buff, vm, "\\t", 2); break; - default: - pkByteBufferWrite(buff, vm, *c); - break; + default: { + if (isprint(c)) pkByteBufferWrite(buff, vm, c); + else { + pkByteBufferAddString(buff, vm, "\\x", 2); + uint8_t byte = (uint8_t) c; + pkByteBufferWrite(buff, vm, utilHexDigit(((byte >> 4) & 0xf), + false)); + pkByteBufferWrite(buff, vm, utilHexDigit(((byte >> 0) & 0xf), + false)); + } + } break; } } pkByteBufferWrite(buff, vm, '"'); diff --git a/src/core/value.h b/src/core/value.h index 1033a45..8489585 100644 --- a/src/core/value.h +++ b/src/core/value.h @@ -749,7 +749,9 @@ const char* getPkVarTypeName(PkVarType type); // Returns the type name of the ObjectType enum value. const char* getObjectTypeName(ObjectType type); -// Returns the type name of the var [v]. +// Returns the type name of the var [v]. If [v] is an instance of a class +// the return pointer will be the class name string's data, which would be +// dangling if [v] is garbage collected. const char* varTypeName(Var v); // Returns the PkVarType of the first class varaible [v]. diff --git a/src/core/vm.c b/src/core/vm.c index 5460bda..0956c97 100644 --- a/src/core/vm.c +++ b/src/core/vm.c @@ -183,7 +183,7 @@ bool vmPrepareFiber(PKVM* vm, Fiber* fiber, int argc, Var* argv) { ASSERT(fiber->closure->fn->arity >= -1, OOPS " (Forget to initialize arity.)"); - if (argc != fiber->closure->fn->arity) { + if ((fiber->closure->fn->arity != -1) && argc != fiber->closure->fn->arity) { char buff[STR_INT_BUFF_SIZE]; sprintf(buff, "%d", fiber->closure->fn->arity); _ERR_FAIL(stringFormat(vm, "Expected exactly $ argument(s).", buff)); @@ -1128,9 +1128,9 @@ L_do_call: } } else { - RUNTIME_ERROR(stringFormat(vm, "$ $(@).", "Expected a callable to " + RUNTIME_ERROR(stringFormat(vm, "$ '$'.", "Expected a callable to " "call, instead got", - varTypeName(callable), toString(vm, callable))); + varTypeName(callable))); } // If we reached here it's a valid callable. @@ -1785,7 +1785,7 @@ L_do_call: if (vm->config.stdout_write != NULL) { Var tmp = PEEK(-1); if (!IS_NULL(tmp)) { - vm->config.stdout_write(vm, toRepr(vm, tmp)->data); + vm->config.stdout_write(vm, varToString(vm, tmp, true)->data); vm->config.stdout_write(vm, "\n"); } } diff --git a/src/include/pocketlang.h b/src/include/pocketlang.h index 31c13e7..8106c0b 100644 --- a/src/include/pocketlang.h +++ b/src/include/pocketlang.h @@ -283,26 +283,26 @@ PK_PUBLIC int pkGetArgc(const PKVM* vm); // that min <= max, and pocketlang won't validate this in release binary. PK_PUBLIC bool pkCheckArgcRange(PKVM* vm, int argc, int min, int max); -// Helper function to check if the argument at the [arg] slot is Boolean and +// Helper function to check if the argument at the [slot] slot is Boolean and // if not set a runtime error. -PK_PUBLIC bool pkValidateSlotBool(PKVM* vm, int arg, bool* value); +PK_PUBLIC bool pkValidateSlotBool(PKVM* vm, int slot, bool* value); -// Helper function to check if the argument at the [arg] slot is Number and +// Helper function to check if the argument at the [slot] slot is Number and // if not set a runtime error. -PK_PUBLIC bool pkValidateSlotNumber(PKVM* vm, int arg, double* value); +PK_PUBLIC bool pkValidateSlotNumber(PKVM* vm, int slot, double* value); -// Helper function to check if the argument at the [arg] slot is String and +// Helper function to check if the argument at the [slot] slot is String and // if not set a runtime error. -PK_PUBLIC bool pkValidateSlotString(PKVM* vm, int arg, +PK_PUBLIC bool pkValidateSlotString(PKVM* vm, int slot, const char** value, uint32_t* length); -// Helper function to check if the argument at the [arg] slot is of type +// Helper function to check if the argument at the [slot] slot is of type // [type] and if not sets a runtime error. -PK_PUBLIC bool pkValidateSlotType(PKVM* vm, int arg, PkVarType type); +PK_PUBLIC bool pkValidateSlotType(PKVM* vm, int slot, PkVarType type); -// Helper function to check if the argument at the [arg] slot is an instance +// Helper function to check if the argument at the [slot] slot is an instance // of the class which is at the [cls] index. If not set a runtime error. -PK_PUBLIC bool pkValidateSlotInstanceOf(PKVM* vm, int arg, int cls); +PK_PUBLIC bool pkValidateSlotInstanceOf(PKVM* vm, int slot, int cls); // Helper function to check if the instance at the [inst] slot is an instance // of the class which is at the [cls] index. The value will be set to [val] @@ -374,16 +374,16 @@ PK_PUBLIC void pkSetSlotHandle(PKVM* vm, int index, PkHandle* handle); /* POCKET FFI */ /*****************************************************************************/ -// Set the attribute with [name] of the instance at the [instance] slot to -// the value at the [value] index slot. Return true on success. -PK_PUBLIC bool pkSetAttribute(PKVM* vm, int instance, int value, - const char* name); - // Get the attribute with [name] of the instance at the [instance] slot and // place it at the [index] slot. Return true on success. PK_PUBLIC bool pkGetAttribute(PKVM* vm, int instance, const char* name, int index); +// Set the attribute with [name] of the instance at the [instance] slot to +// the value at the [value] index slot. Return true on success. +PK_PUBLIC bool pkSetAttribute(PKVM* vm, int instance, + const char* name, int value); + // Place the [self] instance at the [index] slot. PK_PUBLIC void pkPlaceSelf(PKVM* vm, int index); diff --git a/src/libs/libs.c b/src/libs/libs.c index e6290e1..7d0d6a1 100644 --- a/src/libs/libs.c +++ b/src/libs/libs.c @@ -9,6 +9,7 @@ #endif void registerModuleMath(PKVM* vm); +void registerModuleTypes(PKVM* vm); void registerModuleTime(PKVM* vm); void registerModuleIO(PKVM* vm); void registerModulePath(PKVM* vm); @@ -16,6 +17,7 @@ void registerModuleDummy(PKVM* vm); void registerLibs(PKVM* vm) { registerModuleMath(vm); + registerModuleTypes(vm); registerModuleTime(vm); registerModuleIO(vm); registerModulePath(vm); diff --git a/src/libs/libs.h b/src/libs/libs.h index 9ac858a..76a62f0 100644 --- a/src/libs/libs.h +++ b/src/libs/libs.h @@ -19,98 +19,15 @@ /* MODULES INTERNAL */ /*****************************************************************************/ -// We're re defining some core internal macros here which should be considered -// as macro re-definition in amalgamated source, so we're skipping it for -// amalgumated build. +// FIXME: +// Since this are part of the "standard" pocketlang libraries, we can include +// pocketlang internals here using the relative path, however it'll make these +// libraries less "portable" in a sence that these files cannot be just drag +// and dropped into another embedded application where is cannot find the +// relative include. +// #ifndef PK_AMALGAMATED - -#define TOSTRING(x) #x -#define STRINGIFY(x) TOSTRING(x) - -// CONCAT_LINE(X) will result evaluvated X<__LINE__>. -#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 build target (debug or release). Use ASSERT() for debug assertion and -// use __ASSERT() for TODOs. -#define __ASSERT(condition, message) \ - do { \ - if (!(condition)) { \ - fprintf(stderr, "Assertion failed: %s\n\tat %s() (%s:%i)\n", \ - message, __func__, __FILE__, __LINE__); \ - DEBUG_BREAK(); \ - abort(); \ - } \ - } while (false) - -#define NO_OP do {} while (false) - -#ifdef DEBUG - -#ifdef _MSC_VER - #define DEBUG_BREAK() __debugbreak() -#else - #define DEBUG_BREAK() __builtin_trap() -#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_INDEX(index, size) \ - ASSERT(index >= 0 && index < size, "Index out of bounds.") - -#define UNREACHABLE() \ - do { \ - fprintf(stderr, "Execution reached an unreachable path\n" \ - "\tat %s() (%s:%i)\n", __func__, __FILE__, __LINE__); \ - DEBUG_BREAK(); \ - abort(); \ - } while (false) - -#else - -#define STATIC_ASSERT(condition) NO_OP - -#define DEBUG_BREAK() NO_OP -#define ASSERT(condition, message) NO_OP -#define ASSERT_INDEX(index, size) NO_OP - -#if defined(_MSC_VER) - #define UNREACHABLE() __assume(0) -#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) - #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 - #define UNREACHABLE() NO_OP -#endif - -#endif // DEBUG - -// 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 please." - -// Returns the docstring of the function, which is a static const char* defined -// just above the function by the DEF() macro below. -#define DOCSTRING(fn) __doc_##fn - -// A macro to declare a function, with docstring, which is defined as -// ___doc_ = docstring; That'll used to generate function help text. -#define DEF(fn, docstring) \ - static const char* DOCSTRING(fn) = docstring; \ - static void fn(PKVM* vm) - +#include "../core/common.h" #endif // PK_AMALGAMATED /*****************************************************************************/ diff --git a/src/libs/std_math.c b/src/libs/std_math.c index 5e246c5..d830ccc 100644 --- a/src/libs/std_math.c +++ b/src/libs/std_math.c @@ -189,7 +189,7 @@ void registerModuleMath(PKVM* vm) { pkReserveSlots(vm, 2); pkSetSlotHandle(vm, 0, math); // slot[0] = math pkSetSlotNumber(vm, 1, PK_PI); // slot[1] = 3.14 - pkSetAttribute(vm, 0, 1, "PI"); // slot[0].PI = slot[1] + pkSetAttribute(vm, 0, "PI", 1); // slot[0].PI = slot[1] pkModuleAddFunction(vm, math, "floor", stdMathFloor, 1); pkModuleAddFunction(vm, math, "ceil", stdMathCeil, 1); diff --git a/src/libs/std_types.c b/src/libs/std_types.c new file mode 100644 index 0000000..c1820b0 --- /dev/null +++ b/src/libs/std_types.c @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2020-2022 Thakee Nathees + * Copyright (c) 2021-2022 Pocketlang Contributors + * Distributed Under The MIT License + */ + +#include + +#ifndef PK_AMALGAMATED +#include "libs.h" +#endif + +/*****************************************************************************/ +/* BYTE BUFFER */ +/*****************************************************************************/ + +#ifndef PK_AMALGAMATED +#include "../core/value.h" +#endif + +void* _bytebuffNew(PKVM* vm) { + pkByteBuffer* self = pkRealloc(vm, NULL, sizeof(pkByteBuffer)); + pkByteBufferInit(self); + return self; +} + +void _bytebuffDelete(PKVM* vm, void* buff) { + pkRealloc(vm, buff, 0); +} + +void _bytebuffReserve(PKVM* vm) { + double size; + if (!pkValidateSlotNumber(vm, 1, &size)) return; + + pkByteBuffer* self = pkGetSelf(vm); + pkByteBufferReserve(self, vm, (size_t) size); +} + +void _bytebuffClear(PKVM* vm) { + // TODO: Should I also zero or reduce the capacity? + pkByteBuffer* self = pkGetSelf(vm); + self->count = 0; +} + +// Returns the length of bytes were written. +void _bytebuffWrite(PKVM* vm) { + pkByteBuffer* self = pkGetSelf(vm); + + PkVarType type = pkGetSlotType(vm, 1); + + switch (type) { + case PK_BOOL: + pkByteBufferWrite(self, vm, pkGetSlotBool(vm, 1) ? 1 : 0); + pkSetSlotNumber(vm, 0, 1); + return; + + case PK_NUMBER: { + double n = pkGetSlotNumber(vm, 1); + if (floor(n) != n) { + pkSetRuntimeErrorFmt(vm, "Expected an integer, got float %g.", n); + return; + } + int64_t i = (int64_t) n; + if (i < 0x00 || i > 0xff) { + pkSetRuntimeErrorFmt(vm, "Expected integer in range " + "0x00 to 0xff, got %i.", i); + return; + } + + pkByteBufferWrite(self, vm, (uint8_t) i); + pkSetSlotNumber(vm, 0, 1); + return; + } + + case PK_STRING: { + uint32_t length; + const char* str = pkGetSlotString(vm, 1, &length); + pkByteBufferAddString(self, vm, str, length); + pkSetSlotNumber(vm, 0, (double) length); + return; + } + + default: + break; + } + + //< Pocketlang internal function. + const char* name = getPkVarTypeName(type); + pkSetRuntimeErrorFmt(vm, "Object %s cannot be written to ByteBuffer.", name); + +} + +void _bytebuffSubscriptGet(PKVM* vm) { + double index; + if (!pkValidateSlotNumber(vm, 1, &index)) return; + if (floor(index) != index) { + pkSetRuntimeError(vm, "Expected an integer but got number."); + return; + } + + pkByteBuffer* self = pkGetSelf(vm); + + if (index < 0 || index >= self->count) { + pkSetRuntimeError(vm, "Index out of bound"); + return; + } + + pkSetSlotNumber(vm, 0, self->data[(uint32_t)index]); + +} + +void _bytebuffSubscriptSet(PKVM* vm) { + double index, value; + if (!pkValidateSlotNumber(vm, 1, &index)) return; + if (!pkValidateSlotNumber(vm, 2, &value)) return; + + if (floor(index) != index) { + pkSetRuntimeError(vm, "Expected an integer but got float."); + return; + } + if (floor(value) != value) { + pkSetRuntimeError(vm, "Expected an integer but got float."); + return; + } + + pkByteBuffer* self = pkGetSelf(vm); + + if (index < 0 || index >= self->count) { + pkSetRuntimeError(vm, "Index out of bound"); + return; + } + + if (value < 0x00 || value > 0xff) { + pkSetRuntimeError(vm, "Value should be in range 0x00 to 0xff."); + return; + } + + self->data[(uint32_t) index] = (uint8_t) value; + +} + +void _bytebuffString(PKVM* vm) { + pkByteBuffer* self = pkGetSelf(vm); + pkSetSlotStringLength(vm, 0, self->data, self->count); +} + +/*****************************************************************************/ +/* VECTOR */ +/*****************************************************************************/ + +typedef struct { + double x, y, z; +} Vector; + +void* _vectorNew(PKVM* vm) { + Vector* vec = pkRealloc(vm, NULL, sizeof(Vector)); + memset(vec, 0, sizeof(Vector)); + return vec; +} + +void _vectorDelete(PKVM* vm, void* vec) { + pkRealloc(vm, vec, 0); +} + +void _vectorInit(PKVM* vm) { + int argc = pkGetArgc(vm); + if (!pkCheckArgcRange(vm, argc, 0, 3)) return; + + double x, y, z; + Vector* vec = pkGetSelf(vm); + + if (argc == 0) return; + if (argc >= 1) { + if (!pkValidateSlotNumber(vm, 1, &x)) return; + vec->x = x; + } + + if (argc >= 2) { + if (!pkValidateSlotNumber(vm, 2, &y)) return; + vec->y = y; + } + + if (argc == 3) { + if (!pkValidateSlotNumber(vm, 3, &z)) return; + vec->z = z; + } + +} + +void _vectorGetter(PKVM* vm) { + const char* name; uint32_t length; + if (!pkValidateSlotString(vm, 1, &name, &length)) return; + + Vector* vec = pkGetSelf(vm); + if (length == 1) { + if (*name == 'x') { + pkSetSlotNumber(vm, 0, vec->x); + return; + } else if (*name == 'y') { + pkSetSlotNumber(vm, 0, vec->y); + return; + } else if (*name == 'z') { + pkSetSlotNumber(vm, 0, vec->z); + return; + } + } +} + +void _vectorSetter(PKVM* vm) { + const char* name; uint32_t length; + if (!pkValidateSlotString(vm, 1, &name, &length)) return; + + Vector* vec = pkGetSelf(vm); + + if (length == 1) { + if (*name == 'x') { + double val; + if (!pkValidateSlotNumber(vm, 2, &val)) return; + vec->x = val; return; + } else if (*name == 'y') { + double val; + if (!pkValidateSlotNumber(vm, 2, &val)) return; + vec->y = val; return; + } else if (*name == 'z') { + double val; + if (!pkValidateSlotNumber(vm, 2, &val)) return; + vec->z = val; return; + } + } +} + +void _vectorRepr(PKVM* vm) { + Vector* vec = pkGetSelf(vm); + pkSetSlotStringFmt(vm, 0, "[%g, %g, %g]", vec->x, vec->y, vec->z); +} + +/*****************************************************************************/ +/* REGISTER TYPES */ +/*****************************************************************************/ + +void registerModuleTypes(PKVM* vm) { + PkHandle* types = pkNewModule(vm, "types"); + + PkHandle* cls_byte_buffer = pkNewClass(vm, "ByteBuffer", NULL, types, + _bytebuffNew, _bytebuffDelete); + + pkClassAddMethod(vm, cls_byte_buffer, "[]", _bytebuffSubscriptGet, 1); + pkClassAddMethod(vm, cls_byte_buffer, "[]=", _bytebuffSubscriptSet, 2); + pkClassAddMethod(vm, cls_byte_buffer, "reserve", _bytebuffReserve, 1); + pkClassAddMethod(vm, cls_byte_buffer, "clear", _bytebuffClear, 0); + pkClassAddMethod(vm, cls_byte_buffer, "write", _bytebuffWrite, 1); + pkClassAddMethod(vm, cls_byte_buffer, "string", _bytebuffString, 0); + pkReleaseHandle(vm, cls_byte_buffer); + + // TODO: add move mthods. + PkHandle* cls_vector = pkNewClass(vm, "Vector", NULL, types, + _vectorNew, _vectorDelete); + pkClassAddMethod(vm, cls_vector, "_init", _vectorInit, -1); + pkClassAddMethod(vm, cls_vector, "@getter", _vectorGetter, 1); + pkClassAddMethod(vm, cls_vector, "@setter", _vectorSetter, 2); + pkClassAddMethod(vm, cls_vector, "_repr", _vectorRepr, 0); + pkReleaseHandle(vm, cls_vector); + + pkRegisterModule(vm, types); + pkReleaseHandle(vm, types); +} diff --git a/tests/lang/basics.pk b/tests/lang/basics.pk index 5e68ac4..5d9c503 100644 --- a/tests/lang/basics.pk +++ b/tests/lang/basics.pk @@ -7,6 +7,9 @@ assert(42 % 40.0 == 2) assert("'\"'" == '\'"\'') assert("testing" == "test" + "ing") +assert("foo \ +bar" == "foo bar") + assert(-0b10110010 == -178 and 0b11001010 == 202) assert(0b1111111111111111 == 65535) assert( diff --git a/tests/native/README.md b/tests/native/README.md index a23eb14..90371e8 100644 --- a/tests/native/README.md +++ b/tests/native/README.md @@ -17,16 +17,16 @@ integrate pocket VM with your application #### `example0.c` - Contains how to run simple pocket script ``` -gcc example0.c -o example0 ../../src/*.c -I../../src/include -lm +gcc example0.c -o example0 ../../src/core/*.c ../../src/libs/*.c -I../../src/include -lm ``` #### `example1.c` - Contains how to pass values between pocket VM and C ``` -gcc example1.c -o example1 ../../src/*.c -I../../src/include -lm +gcc example1.c -o example1 ../../src/core/*.c ../../src/libs/*.c -I../../src/include -lm ``` #### `example2.c` - Contains how to create your own custom native type in C ``` -gcc example2.c -o example2 ../../src/*.c -I../../src/include -lm +gcc example2.c -o example2 ../../src/core/*.c ../../src/libs/*.c -I../../src/include -lm ``` diff --git a/tests/native/example2.c b/tests/native/example2.c index 3eab59c..3b06a57 100644 --- a/tests/native/example2.c +++ b/tests/native/example2.c @@ -43,16 +43,16 @@ typedef struct { } Vector; // Native instance allocation callback. -void* _newVec() { - Vector* vec = (Vector*)malloc(sizeof(Vector)); +void* _newVec(PKVM* vm) { + Vector* vec = pkRealloc(vm, NULL, sizeof(Vector)); vec->x = 0; vec->y = 0; return vec; } // Native instance de-allocatoion callback. -void _deleteVec(void* vector) { - free(vector); +void _deleteVec(PKVM* vm, void* vec) { + pkRealloc(vm, vec, 0); } void _vecGetter(PKVM* vm) {