From f5e2f15d230de5b22a9a55f18805967d0d928dbc Mon Sep 17 00:00:00 2001
From: Thakee Nathees
Date: Sun, 8 May 2022 15:32:51 +0530
Subject: [PATCH] negative index, range slice and more
... string concat with .. operator
exponent operator with ** operator
---
docs/GettingStarted/LanguageManual.md | 2 +-
docs/README.md | 2 +-
scripts/run_tests.py | 1 +
src/pk_compiler.c | 57 ++++---
src/pk_core.c | 235 +++++++++++++++++++-------
src/pk_core.h | 1 +
src/pk_debug.c | 8 +-
src/pk_opcodes.h | 1 +
src/pk_vm.c | 13 ++
tests/lang/basics.pk | 7 +
tests/lang/builtin_fn.pk | 2 +
tests/lang/builtin_ty.pk | 53 ++++++
12 files changed, 291 insertions(+), 91 deletions(-)
create mode 100644 tests/lang/builtin_ty.pk
diff --git a/docs/GettingStarted/LanguageManual.md b/docs/GettingStarted/LanguageManual.md
index a80ef0a..f3fb937 100644
--- a/docs/GettingStarted/LanguageManual.md
+++ b/docs/GettingStarted/LanguageManual.md
@@ -224,7 +224,7 @@ fiber is finished check its attribute `is_done` and use the attribute `function`
which could be used to create a new fiber to "re-start" the fiber.
```ruby
-fb = Fiber foo()
+fb = Fiber fn()
for i in 0..5
yield(i)
end
diff --git a/docs/README.md b/docs/README.md
index 078c2e9..c38385a 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -11,7 +11,7 @@ which is less than 300KB and the language itself can be compile
diff --git a/scripts/run_tests.py b/scripts/run_tests.py
index 02eea24..2c099d3 100644
--- a/scripts/run_tests.py
+++ b/scripts/run_tests.py
@@ -21,6 +21,7 @@ TEST_SUITE = {
"Unit Tests": (
"lang/basics.pk",
"lang/builtin_fn.pk",
+ "lang/builtin_ty.pk",
"lang/class.pk",
"lang/closure.pk",
"lang/core.pk",
diff --git a/src/pk_compiler.c b/src/pk_compiler.c
index 2ebd6e4..bbd82e6 100644
--- a/src/pk_compiler.c
+++ b/src/pk_compiler.c
@@ -81,6 +81,7 @@ typedef enum {
TK_MINUS, // -
TK_STAR, // *
TK_FSLASH, // /
+ TK_STARSTAR, // **
TK_BSLASH, // \.
TK_EQ, // =
TK_GT, // >
@@ -96,6 +97,7 @@ typedef enum {
TK_STAREQ, // *=
TK_DIVEQ, // /=
TK_MODEQ, // %=
+ TK_POWEQ, // **=
TK_ANDEQ, // &=
TK_OREQ, // |=
@@ -226,6 +228,7 @@ typedef enum {
PREC_TERM, // + -
PREC_FACTOR, // * / %
PREC_UNARY, // - ! ~ not
+ PREC_EXPONENT, // **
PREC_CALL, // ()
PREC_SUBSCRIPT, // []
PREC_ATTRIB, // .index
@@ -1101,11 +1104,7 @@ static void lexToken(Compiler* compiler) {
case '>':
if (matchChar(parser, '>')) {
- if (matchChar(parser, '=')) {
- setNextToken(parser, TK_SRIGHTEQ);
- } else {
- setNextToken(parser, TK_SRIGHT);
- }
+ setNextTwoCharToken(parser, '=', TK_SRIGHT, TK_SRIGHTEQ);
} else {
setNextTwoCharToken(parser, '=', TK_GT, TK_GTEQ);
}
@@ -1113,11 +1112,7 @@ static void lexToken(Compiler* compiler) {
case '<':
if (matchChar(parser, '<')) {
- if (matchChar(parser, '=')) {
- setNextToken(parser, TK_SLEFTEQ);
- } else {
- setNextToken(parser, TK_SLEFT);
- }
+ setNextTwoCharToken(parser, '=', TK_SLEFT, TK_SLEFTEQ);
} else {
setNextTwoCharToken(parser, '=', TK_LT, TK_LTEQ);
}
@@ -1138,7 +1133,11 @@ static void lexToken(Compiler* compiler) {
return;
case '*':
- setNextTwoCharToken(parser, '=', TK_STAR, TK_STAREQ);
+ if (matchChar(parser, '*')) {
+ setNextTwoCharToken(parser, '=', TK_STARSTAR, TK_POWEQ);
+ } else {
+ setNextTwoCharToken(parser, '=', TK_STAR, TK_STAREQ);
+ }
return;
case '/':
@@ -1298,6 +1297,7 @@ static bool matchAssignment(Compiler* compiler) {
if (match(compiler, TK_STAREQ)) return true;
if (match(compiler, TK_DIVEQ)) return true;
if (match(compiler, TK_MODEQ)) return true;
+ if (match(compiler, TK_POWEQ)) return true;
if (match(compiler, TK_ANDEQ)) return true;
if (match(compiler, TK_OREQ)) return true;
if (match(compiler, TK_XOREQ)) return true;
@@ -1569,6 +1569,7 @@ GrammarRule rules[] = { // Prefix Infix Infix Precedence
/* TK_MINUS */ { exprUnaryOp, exprBinaryOp, PREC_TERM },
/* TK_STAR */ { NULL, exprBinaryOp, PREC_FACTOR },
/* TK_FSLASH */ { NULL, exprBinaryOp, PREC_FACTOR },
+ /* TK_STARSTAR */ { NULL, exprBinaryOp, PREC_EXPONENT },
/* TK_BSLASH */ NO_RULE,
/* TK_EQ */ NO_RULE,
/* TK_GT */ { NULL, exprBinaryOp, PREC_COMPARISION },
@@ -1582,6 +1583,7 @@ GrammarRule rules[] = { // Prefix Infix Infix Precedence
/* TK_STAREQ */ NO_RULE,
/* TK_DIVEQ */ NO_RULE,
/* TK_MODEQ */ NO_RULE,
+ /* TK_POWEQ */ NO_RULE,
/* TK_ANDEQ */ NO_RULE,
/* TK_OREQ */ NO_RULE,
/* TK_XOREQ */ NO_RULE,
@@ -1998,17 +2000,18 @@ static void exprBinaryOp(Compiler* compiler) {
do { emitOpcode(compiler, opcode); emitByte(compiler, 0); } while (false)
switch (op) {
- case TK_DOTDOT: emitOpcode(compiler, OP_RANGE); break;
- case TK_PERCENT: EMIT_BINARY_OP_INPLACE(OP_MOD); break;
- case TK_PLUS: EMIT_BINARY_OP_INPLACE(OP_ADD); break;
- case TK_MINUS: EMIT_BINARY_OP_INPLACE(OP_SUBTRACT); break;
- case TK_STAR: EMIT_BINARY_OP_INPLACE(OP_MULTIPLY); break;
- case TK_FSLASH: EMIT_BINARY_OP_INPLACE(OP_DIVIDE); break;
- case TK_AMP: EMIT_BINARY_OP_INPLACE(OP_BIT_AND); break;
- case TK_PIPE: EMIT_BINARY_OP_INPLACE(OP_BIT_OR); break;
- case TK_CARET: EMIT_BINARY_OP_INPLACE(OP_BIT_XOR); break;
- case TK_SRIGHT: EMIT_BINARY_OP_INPLACE(OP_BIT_RSHIFT); break;
- case TK_SLEFT: EMIT_BINARY_OP_INPLACE(OP_BIT_LSHIFT); break;
+ case TK_DOTDOT: emitOpcode(compiler, OP_RANGE); break;
+ case TK_PERCENT: EMIT_BINARY_OP_INPLACE(OP_MOD); break;
+ case TK_PLUS: EMIT_BINARY_OP_INPLACE(OP_ADD); break;
+ case TK_MINUS: EMIT_BINARY_OP_INPLACE(OP_SUBTRACT); break;
+ case TK_STAR: EMIT_BINARY_OP_INPLACE(OP_MULTIPLY); break;
+ case TK_FSLASH: EMIT_BINARY_OP_INPLACE(OP_DIVIDE); break;
+ case TK_STARSTAR: EMIT_BINARY_OP_INPLACE(OP_EXPONENT); break;
+ case TK_AMP: EMIT_BINARY_OP_INPLACE(OP_BIT_AND); break;
+ case TK_PIPE: EMIT_BINARY_OP_INPLACE(OP_BIT_OR); break;
+ case TK_CARET: EMIT_BINARY_OP_INPLACE(OP_BIT_XOR); break;
+ case TK_SRIGHT: EMIT_BINARY_OP_INPLACE(OP_BIT_RSHIFT); break;
+ case TK_SLEFT: EMIT_BINARY_OP_INPLACE(OP_BIT_LSHIFT); break;
#undef EMIT_BINARY_OP_INPLACE
case TK_GT: emitOpcode(compiler, OP_GT); break;
@@ -2238,6 +2241,10 @@ static void parsePrecedence(Compiler* compiler, Precedence precedence) {
return;
}
+ // Make a "backup" of the l value before parsing next operators to
+ // reset once it done.
+ bool l_value = compiler->l_value;
+
compiler->l_value = precedence <= PREC_LOWEST;
prefix(compiler);
@@ -2258,8 +2265,9 @@ static void parsePrecedence(Compiler* compiler, Precedence precedence) {
// TK_LPARAN '(' as infix is the call operator.
compiler->is_last_call = (op == TK_LPARAN);
-
}
+
+ compiler->l_value = l_value;
}
/*****************************************************************************/
@@ -2458,6 +2466,7 @@ static void emitAssignedOp(Compiler* compiler, TokenType assignment) {
case TK_STAREQ: EMIT_BINARY_OP_INPLACE(OP_MULTIPLY); break;
case TK_DIVEQ: EMIT_BINARY_OP_INPLACE(OP_DIVIDE); break;
case TK_MODEQ: EMIT_BINARY_OP_INPLACE(OP_MOD); break;
+ case TK_POWEQ: EMIT_BINARY_OP_INPLACE(OP_EXPONENT); break;
case TK_ANDEQ: EMIT_BINARY_OP_INPLACE(OP_BIT_AND); break;
case TK_OREQ: EMIT_BINARY_OP_INPLACE(OP_BIT_OR); break;
case TK_XOREQ: EMIT_BINARY_OP_INPLACE(OP_BIT_XOR); break;
@@ -2627,9 +2636,11 @@ static bool matchOperatorMethod(Compiler* compiler,
if (match(compiler, TK_STAR)) _RET("*", 1);
if (match(compiler, TK_STAREQ)) _RET("*=", 1);
if (match(compiler, TK_FSLASH)) _RET("/", 1);
+ if (match(compiler, TK_STARSTAR)) _RET("**", 1);
if (match(compiler, TK_DIVEQ)) _RET("/=", 1);
if (match(compiler, TK_PERCENT)) _RET("%", 1);
if (match(compiler, TK_MODEQ)) _RET("%=", 1);
+ if (match(compiler, TK_POWEQ)) _RET("**=", 1);
if (match(compiler, TK_AMP)) _RET("&", 1);
if (match(compiler, TK_ANDEQ)) _RET("&=", 1);
if (match(compiler, TK_PIPE)) _RET("|", 1);
diff --git a/src/pk_core.c b/src/pk_core.c
index ac450a2..52eb001 100644
--- a/src/pk_core.c
+++ b/src/pk_core.c
@@ -251,7 +251,7 @@ DEF(coreTypeName,
DEF(coreHelp,
"help([fn:Closure]) -> null\n"
- "This will write an error message to stdout and return null.") {
+ "It'll print the docstring the object and return.") {
int argc = ARGC;
if (argc != 0 && argc != 1) {
@@ -919,7 +919,6 @@ static void initializePrimitiveClasses(PKVM* vm) {
ADD_CTOR(PK_LIST, "@ctorList", _ctorList, -1);
ADD_CTOR(PK_MAP, "@ctorMap", _ctorMap, 0);
ADD_CTOR(PK_FIBER, "@ctorFiber", _ctorFiber, 1);
-
#undef ADD_CTOR
#define ADD_METHOD(type, name, ptr, arity_) \
@@ -1222,6 +1221,20 @@ Var varDivide(PKVM* vm, Var v1, Var v2, bool inplace) {
return VAR_NULL;
}
+Var varExponent(PKVM* vm, Var v1, Var v2, bool inplace) {
+ double n1, n2;
+ if (isNumeric(v1, &n1)) {
+ if (validateNumeric(vm, v2, &n2, RIGHT_OPERAND)) {
+ return VAR_NUM(pow(n1, n2));
+ }
+ return VAR_NULL;
+ }
+
+ CHECK_INST_BINARY_OP("**");
+ UNSUPPORTED_BINARY_OP("**");
+ return VAR_NULL;
+}
+
Var varBitAnd(PKVM* vm, Var v1, Var v2, bool inplace) {
CHECK_BITWISE_OP(&);
CHECK_INST_BINARY_OP("&");
@@ -1283,6 +1296,14 @@ Var varOpRange(PKVM* vm, Var v1, Var v2) {
if (IS_NUM(v1) && IS_NUM(v2)) {
return VAR_OBJ(newRange(vm, AS_NUM(v1), AS_NUM(v2)));
}
+
+ if (IS_OBJ_TYPE(v1, OBJ_STRING)) {
+ String* str = _varToString(vm, v2);
+ if (str == NULL) return VAR_NULL;
+ String* concat = stringJoin(vm, (String*) AS_OBJ(v1), str);
+ return VAR_OBJ(concat);
+ }
+
const bool inplace = false;
CHECK_INST_BINARY_OP("..");
UNSUPPORTED_BINARY_OP("..");
@@ -1512,14 +1533,6 @@ void varSetAttrib(PKVM* vm, Var on, String* attrib, Var value) {
"'$' object has no mutable attribute named '$'", \
varTypeName(on), attrib->data))
-#define ATTRIB_IMMUTABLE(name) \
-do { \
- if ((attrib->length == strlen(name) && strcmp(name, attrib->data) == 0)) { \
- VM_SET_ERROR(vm, stringFormat(vm, "'$' attribute is immutable.", name)); \
- return; \
- } \
-} while (false)
-
if (!IS_OBJ(on)) {
ERR_NO_ATTRIB(vm, on, attrib);
return;
@@ -1527,30 +1540,6 @@ do { \
Object* obj = AS_OBJ(on);
switch (obj->type) {
- case OBJ_STRING:
- ATTRIB_IMMUTABLE("length");
- ATTRIB_IMMUTABLE("lower");
- ATTRIB_IMMUTABLE("upper");
- ATTRIB_IMMUTABLE("strip");
- ERR_NO_ATTRIB(vm, on, attrib);
- return;
-
- case OBJ_LIST:
- ATTRIB_IMMUTABLE("length");
- ERR_NO_ATTRIB(vm, on, attrib);
- return;
-
- case OBJ_MAP:
- TODO;
- ERR_NO_ATTRIB(vm, on, attrib);
- return;
-
- case OBJ_RANGE:
- ATTRIB_IMMUTABLE("as_list");
- ATTRIB_IMMUTABLE("first");
- ATTRIB_IMMUTABLE("last");
- ERR_NO_ATTRIB(vm, on, attrib);
- return;
case OBJ_MODULE: {
Module* module = (Module*)obj;
@@ -1563,26 +1552,9 @@ do { \
} break;
case OBJ_FUNC:
- UNREACHABLE(); // Functions aren't first class objects.
- return;
-
- case OBJ_CLOSURE:
- ATTRIB_IMMUTABLE("arity");
- ATTRIB_IMMUTABLE("name");
- ERR_NO_ATTRIB(vm, on, attrib);
- return;
-
case OBJ_UPVALUE:
- UNREACHABLE(); // Upvalues aren't first class objects.
- return;
-
- case OBJ_FIBER:
- ERR_NO_ATTRIB(vm, on, attrib);
- return;
-
- case OBJ_CLASS:
- ERR_NO_ATTRIB(vm, on, attrib);
- return;
+ UNREACHABLE(); // Functions aren't first class objects.
+ break;
case OBJ_INST: {
@@ -1612,6 +1584,9 @@ do { \
return;
} break;
+
+ default:
+ break;
}
ERR_NO_ATTRIB(vm, on, attrib);
@@ -1621,6 +1596,101 @@ do { \
#undef ERR_NO_ATTRIB
}
+// Given a range. It'll "normalize" the range to slice an object (string or
+// list) set the [start] index [length] and [reversed]. On success it'll return
+// true.
+static bool _normalizeSliceRange(PKVM* vm, Range* range, uint32_t count,
+ int32_t* start, int32_t* length, bool* reversed) {
+ if ((floor(range->from) != range->from) ||
+ (floor(range->to) != range->to)) {
+ VM_SET_ERROR(vm, newString(vm, "Expected a whole number."));
+ return false;
+ }
+
+ int32_t from = (int32_t)range->from;
+ int32_t to = (int32_t)range->to;
+
+ if (from < 0) from = count + from;
+ if (to < 0) to = count + to;
+
+ *reversed = false;
+ if (to < from) {
+ int32_t tmp = to;
+ to = from;
+ from = tmp;
+ *reversed = true;
+ }
+
+ if (from < 0 || count <= (uint32_t) to) {
+
+ // Special case we allow 0..0 or 0..-1, -1..0, -1..-1 to be valid slice
+ // ranges for empty string/list, and will gives an empty string/list.
+ if (count == 0 && (from == 0 || from == -1) && (to == 0 || to == -1)) {
+ *start = 0;
+ *length = 0;
+ *reversed = false;
+ return true;
+ }
+
+ VM_SET_ERROR(vm, newString(vm, "Index out of bound."));
+ return false;
+ }
+
+ *start = from;
+ *length = to - from + 1;
+
+ return true;
+}
+
+// Slice the string with the [range] and reutrn it. On error it'll set
+// an error and return NULL.
+static String* _sliceString(PKVM* vm, String* str, Range* range) {
+
+ int32_t start, length; bool reversed;
+ if (!_normalizeSliceRange(vm, range, str->length,
+ &start, &length, &reversed)) {
+ return NULL;
+ }
+
+ // Optimize case.
+ if (start == 0 && length == str->length && !reversed) return str;
+
+ // TODO: check if length is 1 and return pre allocated character string.
+
+ String* slice = newStringLength(vm, str->data + start, length);
+ if (!reversed) return slice;
+
+ for (int32_t i = 0; i < length / 2; i++) {
+ char tmp = slice->data[i];
+ slice->data[i] = slice->data[length - i - 1];
+ slice->data[length - i - 1] = tmp;
+ }
+ slice->hash = utilHashString(slice->data);
+ return slice;
+}
+
+// Slice the list with the [range] and reutrn it. On error it'll set
+// an error and return NULL.
+static List* _sliceList(PKVM* vm, List* list, Range* range) {
+
+ int32_t start, length; bool reversed;
+ if (!_normalizeSliceRange(vm, range, list->elements.count,
+ &start, &length, &reversed)) {
+ return NULL;
+ }
+
+ List* slice = newList(vm, length);
+ vmPushTempRef(vm, &slice->_super); // slice.
+
+ for (int32_t i = 0; i < length; i++) {
+ int32_t ind = (reversed) ? start + length - 1 - i : start + i;
+ listAppend(vm, slice, list->elements.data[ind]);
+ }
+
+ vmPopTempRef(vm); // slice.
+ return slice;
+}
+
Var varGetSubscript(PKVM* vm, Var on, Var key) {
if (!IS_OBJ(on)) {
VM_SET_ERROR(vm, stringFormat(vm, "$ type is not subscriptable.",
@@ -1631,28 +1701,50 @@ Var varGetSubscript(PKVM* vm, Var on, Var key) {
Object* obj = AS_OBJ(on);
switch (obj->type) {
case OBJ_STRING: {
+
int64_t index;
String* str = ((String*)obj);
- if (!validateInteger(vm, key, &index, "List index")) {
+
+ if (isInteger(key, &index)) {
+ // Normalize index.
+ if (index < 0) index = str->length + index;
+ if (index >= str->length || index < 0) {
+ VM_SET_ERROR(vm, newString(vm, "String index out of bound."));
+ return VAR_NULL;
+ }
+ // FIXME: Add static VM characters instead of allocating here.
+ String* c = newStringLength(vm, str->data + index, 1);
+ return VAR_OBJ(c);
+ }
+
+ if (IS_OBJ_TYPE(key, OBJ_RANGE)) {
+ String* subs = _sliceString(vm, str, (Range*) AS_OBJ(key));
+ if (subs != NULL) return VAR_OBJ(subs);
return VAR_NULL;
}
- if (!validateIndex(vm, index, str->length, "String")) {
- return VAR_NULL;
- }
- String* c = newStringLength(vm, str->data + index, 1);
- return VAR_OBJ(c);
+
} break;
case OBJ_LIST: {
int64_t index;
pkVarBuffer* elems = &((List*)obj)->elements;
- if (!validateInteger(vm, key, &index, "List index")) {
+
+ if (isInteger(key, &index)) {
+ // Normalize index.
+ if (index < 0) index = elems->count + index;
+ if (index >= elems->count || index < 0) {
+ VM_SET_ERROR(vm, newString(vm, "List index out of bound."));
+ return VAR_NULL;
+ }
+ return elems->data[index];
+ }
+
+ if (IS_OBJ_TYPE(key, OBJ_RANGE)) {
+ List* sublist = _sliceList(vm, (List*)obj, (Range*)AS_OBJ(key));
+ if (sublist != NULL) return VAR_OBJ(sublist);
return VAR_NULL;
}
- if (!validateIndex(vm, index, elems->count, "List")) {
- return VAR_NULL;
- }
- return elems->data[index];
+
} break;
case OBJ_MAP: {
@@ -1676,6 +1768,9 @@ Var varGetSubscript(PKVM* vm, Var on, Var key) {
case OBJ_FUNC:
case OBJ_UPVALUE:
UNREACHABLE(); // Not first class objects.
+
+ default:
+ break;
}
VM_SET_ERROR(vm, stringFormat(vm, "$ type is not subscriptable.",
@@ -1696,9 +1791,16 @@ void varsetSubscript(PKVM* vm, Var on, Var key, Var value) {
int64_t index;
pkVarBuffer* elems = &((List*)obj)->elements;
if (!validateInteger(vm, key, &index, "List index")) return;
- if (!validateIndex(vm, index, elems->count, "List")) return;
+
+ // Normalize index.
+ if (index < 0) index = elems->count + index;
+ if (index >= elems->count || index < 0) {
+ VM_SET_ERROR(vm, newString(vm, "List index out of bound."));
+ return;
+ }
elems->data[index] = value;
return;
+
} break;
case OBJ_MAP: {
@@ -1714,6 +1816,9 @@ void varsetSubscript(PKVM* vm, Var on, Var key, Var value) {
case OBJ_FUNC:
case OBJ_UPVALUE:
UNREACHABLE();
+
+ default:
+ break;
}
VM_SET_ERROR(vm, stringFormat(vm, "$ type is not subscriptable.",
diff --git a/src/pk_core.h b/src/pk_core.h
index b85fdf4..5a1d95e 100644
--- a/src/pk_core.h
+++ b/src/pk_core.h
@@ -98,6 +98,7 @@ Var varAdd(PKVM* vm, Var v1, Var v2, bool inplace); // Returns v1 + v2.
Var varSubtract(PKVM* vm, Var v1, Var v2, bool inplace); // Returns v1 - v2.
Var varMultiply(PKVM* vm, Var v1, Var v2, bool inplace); // Returns v1 * v2.
Var varDivide(PKVM* vm, Var v1, Var v2, bool inplace); // Returns v1 / v2.
+Var varExponent(PKVM* vm, Var v1, Var v2, bool inplace); // Returns v1 ** v2.
Var varModulo(PKVM* vm, Var v1, Var v2, bool inplace); // Returns v1 % v2.
Var varBitAnd(PKVM* vm, Var v1, Var v2, bool inplace); // Returns v1 & v2.
diff --git a/src/pk_debug.c b/src/pk_debug.c
index 042f508..db83dc6 100644
--- a/src/pk_debug.c
+++ b/src/pk_debug.c
@@ -44,7 +44,12 @@ void reportCompileTimeError(PKVM* vm, const char* path, int line,
// Print the error message.
buff.count = 0;
- int size = vsnprintf(NULL, 0, fmt, args) + 1;
+
+ va_list args_copy;
+ va_copy(args_copy, args);
+ int size = vsnprintf(NULL, 0, fmt, args_copy) + 1;
+ va_end(args_copy);
+
ASSERT(size >= 0, "vnsprintf() failed.");
pkByteBufferReserve(&buff, vm, size);
vsnprintf((char*)buff.data, size, fmt, args);
@@ -641,6 +646,7 @@ void dumpFunctionCode(PKVM* vm, Function* func) {
case OP_SUBTRACT:
case OP_MULTIPLY:
case OP_DIVIDE:
+ case OP_EXPONENT:
case OP_MOD:
case OP_BIT_AND:
case OP_BIT_OR:
diff --git a/src/pk_opcodes.h b/src/pk_opcodes.h
index 4e79e5d..e0cb890 100644
--- a/src/pk_opcodes.h
+++ b/src/pk_opcodes.h
@@ -242,6 +242,7 @@ OPCODE(ADD, 1, -1)
OPCODE(SUBTRACT, 1, -1)
OPCODE(MULTIPLY, 1, -1)
OPCODE(DIVIDE, 1, -1)
+OPCODE(EXPONENT, 1, -1)
OPCODE(MOD, 1, -1)
OPCODE(BIT_AND, 1, -1)
diff --git a/src/pk_vm.c b/src/pk_vm.c
index c96be40..ae7c1ff 100644
--- a/src/pk_vm.c
+++ b/src/pk_vm.c
@@ -1579,6 +1579,19 @@ L_do_call:
DISPATCH();
}
+ OPCODE(EXPONENT):
+ {
+ // Don't pop yet, we need the reference for gc.
+ Var r = PEEK(-1), l = PEEK(-2);
+ uint8_t inplace = READ_BYTE(); ASSERT(inplace <= 1, OOPS);
+ Var result = varExponent(vm, l, r, inplace);
+ DROP(); DROP(); // r, l
+ PUSH(result);
+
+ CHECK_ERROR();
+ DISPATCH();
+ }
+
OPCODE(MOD):
{
// Don't pop yet, we need the reference for gc.
diff --git a/tests/lang/basics.pk b/tests/lang/basics.pk
index 445f182..5e68ac4 100644
--- a/tests/lang/basics.pk
+++ b/tests/lang/basics.pk
@@ -136,6 +136,13 @@ x = 99
x <<= 2
assert(x == 99 << 2)
+x = 4
+assert(2**3 == 8)
+assert(-2**2 == -4 and (-2)**2 == 4)
+assert((x**=4) == 256)
+assert(x**.5 == 16)
+assert(4**-1 == .25)
+
assert(.5 == 0.5)
assert(.333 == .333)
assert(.1 + 1 == 1.1)
diff --git a/tests/lang/builtin_fn.pk b/tests/lang/builtin_fn.pk
index 709fd4c..31541c8 100644
--- a/tests/lang/builtin_fn.pk
+++ b/tests/lang/builtin_fn.pk
@@ -5,3 +5,5 @@ assert(list_join([1, 2, 3]) == "123")
assert(list_join(["hello", " world"]) == "hello world")
assert(list_join([[], []]) == "[][]")
+## If we got here, that means all test were passed.
+print('All TESTS PASSED')
diff --git a/tests/lang/builtin_ty.pk b/tests/lang/builtin_ty.pk
new file mode 100644
index 0000000..3ab39ae
--- /dev/null
+++ b/tests/lang/builtin_ty.pk
@@ -0,0 +1,53 @@
+
+## Builtin types attributes and methods tests.
+
+###############################################################################
+## INTEGER
+###############################################################################
+
+sum = 0
+5.times fn(i)
+ sum += i
+end
+assert(sum == 0 + 1 + 2 + 3 + 4)
+
+###############################################################################
+## STRING
+###############################################################################
+
+s = "foobar"
+assert(s[-3] == 'b')
+
+assert("foo" .. 42 == "foo42")
+assert("" .. 1 .. 2 .. 3 == "123")
+
+assert("abcd"[1..1] == "b")
+assert("abcd"[-1..0] == "dcba")
+assert("abcd"[-3..3] == "bcd")
+assert("abcd"[-1..-3] == "dcb")
+
+assert(""[0..-1] == "")
+assert(""[-1..0] == "")
+
+###############################################################################
+## LIST
+###############################################################################
+
+l = [1, 2, 3, 4]
+assert (l[2] == 3)
+assert (l[-1] == 4)
+l[-2] = 42
+assert (l[2] == 42)
+
+l = [1, 2, 3, 4]
+assert(l[1..1] == [2])
+assert(l[-1..0] == [4, 3, 2, 1])
+assert(l[-3..3] == [2, 3, 4])
+assert(l[-1..-3] == [4, 3, 2])
+
+assert([][0..0] == [])
+assert([][-1..-1] == [])
+
+
+## If we got here, that means all test were passed.
+print('All TESTS PASSED')